ajmiles got it in one.
This harmless looking code:
will cause a pipeline stall if the CPU reaches the SetData call for
frame #2 before the GPU finishes processing the DrawPrimitives call from
frame #1.
This occurs because resources such as vertex buffers and textures are
passed by reference, not by value. You can think of each resource as a
separate piece of paper filled with data:
- During LoadContent(), Charles writes 1 million numbers on a piece
of paper
- He labels this paper #42, then sets it to one side
- During Draw(), Charles encounters a "render terrain" instruction
- He writes "draw 1 million triangles, using position data from
paper #42" on his "instructions for George" list
This is efficient, because Charles does not have to bother touching
the actual vertex data while processing the draw instruction.
But what if the game later calls SetData on this same vertex buffer?
- Charles encounters an instruction that says "erase paper #42, then
write these new numbers onto it"
- Before he can proceed, Charles must check if there are any
references to the previous data still waiting in his "instructions for
George" list
- Even if George has already read the instructions, Charles must
interrupt him and say "hey, are you finished with paper #42 yet? Can I
have it back please?"
If the GPU is still using the resource, the second SetData call must
stall until it has finished.
There are several ways to avoid such a stall:
- Treat vertex buffers and textures as read-only: do not call
SetData outside your load methods. Beginners often think they need
dynamic SetData in places where it would be more efficient to leave the
source data alone and apply dynamic effects via a vertex or pixel
shader.
- If you are generating new vertex data every frame, use
DrawUserPrimitives instead of DrawPrimitives. This does not use a vertex
buffer: instead, the vertex data is copied directly into the main
"instructions for George" list, avoiding any possibility of a stall.
Think of it as pass-by-value instead of pass-by-reference.
- Double-buffer. Create a pair of identical resources, and alternate
which one you use per frame. This gives the GPU more time to finish
with each resource before the next time you SetData on it. Good for
dynamic texture scenarios such as video playback.
- Use SetDataOptions.NoOverwrite. This tells Charles "I know you
would normally have to wait for George here, but please just carry on
regardless. I promise I won't overwrite any part of the vertex buffer
that he might still be using". You must then keep track of which
specific vertices have recently been drawn, and only modify parts of the
buffer once you are 100% sure the GPU has finished with them. We used
this technique in our GPU particle system: check out the monster comment
in ParticleSystem.cs for an explanation.