If your app doesn't do much more then maybe you can get away with 200+FPS?
That said, an sf::Image is a very expensive resource and transferring it to the GPU per frame seems wasteful if not required.
And it, of course, isn't!
You mention the rectangle primitives and it could actually provide you with a solution. Namely, you update the rectangles each frame instead of the texture. However, moving six vertices per particle also seems wasteful if they never change size or shape.
Then, a solution could be to use a vertex array. As the name suggests, this is an array of (single) vertices. This would be one vertex per particle.
You would transfer all of the vertices at once to the GPU (when you draw them) so you can draw them to a render texture that would, then, act as your current texture. The difference here is that you are only transferring the vertices, not the image/texture, as the texture is already in graphics memory and you just draw to it with the vertices.
You may also find that this can be improved using a vertex buffer but if you're definitely transferring every frame, it may not help much.
One question to ask is how much information are you transferring per pixel/particle? Are you simply just changing the colour (per pixel) to represent a type of particle (or even just particle or not)? If so, you don't even need the entire texture! You can encode the data into your texture and if you could store that information per pixel in just one byte, you could compress the information and reduce the image size by 4 (a quarter of its size) by storing 4 pixels/particles in a row in just one image/texture pixel. This significantly reduces the amount of data being transferring.
You do, of course, need to be able to decode this data and probably the best way would be to use a
shader. This puts more work on the GPU calculations but reduces CPU->GPU transfer data.
Since we're on the subject of shaders, you could also
send a chunk of data directly to the shader and have it process it for you. Again, if the particle can be represented by some limited values, you could send an array of numbers to represent those (I don't think SFML can do an (large) array of ints - just floats - so you may need to do some extra, but relatively simple, calculations).
If, however, a particle is a simple on/off, it should be easy enough to encode lots of those particles into a single
Ivec4
(4 integers) and then send them to the shader using one or more
SetUniform[Ivec4]Actually, although the shader is probably the best route to take (to replace the image/texture transfer altogether), if you can represent a particle/pixel by a single boolean, you could encode 32 pixels into a single pixel of you texture and this would allow you to reduce your image size by 32!
If a solution you want isn't directly listed here, I hope what I've said can at least inspire you to find some other way!