Don't shuffle vertices around. Shuffle pointers to vertices.
You say that you have a vector in which to store raw vertex data and memory, so go along with that, and keep a second vector that contains pointers to elements in your vector of vertices (your memory buffer). Incidentally, this is the same way I figured out how to cull hardware-instanced meshes individually in the same draw call.
- Update all the particles' positions
- Cull the non-visible particles
- Assign pointers to visible particles to the beginning of a vector
- Copy vertex data referenced by the pointers to the vertex buffer
- Draw vertex buffer with no. of visible particles as no. of vertices
You will add the following items to your particle system:
const int maxParticles = 2000;
// your other ParticleSystem components...
particleData and visibleParticles are of the same length, defined by maxParticles. Initially, populate visibleParticles with pointers to all of particlesData just to avoid null data. visibleParticles will be partially overwritten in each frame anyways.
After updating the position of each particle, cull every vertex in particleData, and keep two numbers to increment. The first is visibleCount which will be reset to zero each frame before culling begins. A second number is inside the loop to increment by one for each vertex. For every vertex that is in view, assign a pointer to particleData.at(i) to visibleParticles.at(visibleCount), then increment visibleCount by 1.
Here's a trimmed down example of how a culling procedure might look for rearranging visible particles:
visibleCount = 0;
for (int i = 0; i < maxParticles; i++)
visibleParticles.at(visibleCount) = &particleData.at(i);
Keep in mind that I am describing a brute-force culling method with linear time. If you are using quad-tree or other spatial culling algorithm, you would need to give a unique number identifier to each particle and use that to in place of i when assigning pointers.
Now, visibleParticles is at least partially updated with different pointers, but when it's time to render, you'll only be using the ones from the start of the vector to visibleCount. Still, you'll be able to copy the entire thing into the buffer.
Lock the vertex buffer, memcpy the visibleParticles vector into the buffer, and unlock. You now have all the updated vertices updated with one memcpy call. memcpy should be okay here, since you're guaranteed not to change the number of maximum particle elements in the vector.
Finally, draw the particles by using the value of visibleCount for the numVertices parameter in DrawIndexedPrimitive.