Article : Multi-threading your XNA
5. Conclusions
So that's about it, for now. In this tutorial, I tried to explain and show how to make a multi-threaded game architecture in XNA. The focus of the tutorial was separating the updating and drawing code on parallel threads, and keeping data correctly synchronized between these two threads. We saw an example of using this architecture in practice, and we could see the performance we gained by multi-threading.
However, this is not the only way to use multi-threading. There are plenty other ways not covered in this tutorial. You can use it for an animated loading screens (which can be seen in the GameStateManagement sample on creators.xna.com), complex asynchronous A.I. computations, loading data during gameplay without a loading screen, etc. All these require different achitectures for the code, and come with their own pitfalls and sensible spots, but they are doable. The results vary from a little extra polish that makes your game feel right, to more intelligent enemies, or to that extra bit of performance we're all craving for.
6. Downloads
The code for the framework alone can be downloaded here: MultithreadingFramework.zip.
The code for the Balls example can be dowloaded here: MultithreadedBalls.zip.
7. References
- Gamefest 2007 - Multicore Programming, Two Years Later, by Ian Lewis
- Gamefest 2008 - XNA Game Studio Performance 2008: Multithreading and GPU, by Frank Savage
- Threading in C#, by Joseph Albahari
- Multi-Threading in .Net, Jon Skeet
- Gamasutra - Multithreaded Game Engine Architectures, by Ville Mönkkönen
- Gamasutra - Threading 3D Game Engine Basics, by Henry Gabb and Adam Lake
- Multithreaded Rendering and Physics Simulation, by Rajshree Chabukswar, Adam T. Lake, and Mary R. Lee
- XNA - Multithreaded Rendering and Physics Simulation, by Nerdy Inverse
November 28th, 2009 - 06:06
Hi Catalin, I originally went down a very similar research path as what you are describing in your threaded-rendering system.
However I did some prototyping and came to the realization that the GPU is already running asynchronously, and only forces a CPU-side block if the previous frame hasnt finished when the next .Begin() is called. This result plus the added complexity of caching game-state led me to abandon this system.
What we are planning to do on our engine is to use a seperate cpu thread to process vertex data (batch instancing), but that’s really no different than multithreading other cpu-side modules.
November 28th, 2009 - 07:41
Hi Jason.
Yes, the GPU does indeed run asynchronously from the CPU. However, the speed of the GPU is rarely the main performance bottleneck. Most time, the problem is with the communication between the CPU and the GPU which happens at each DrawXXX() call. Having a high number of draw calls eats up lots of CPU time, and that is what I am trying to reduce in this article. So what I am threading is not actually the GPU-drawing, but the CPU invoking of draw calls (which happens at driver-level).
In most multi-threading approaches, you either try to distribute the non-graphics CPU-work on multiple threads, or try to distribute the GPU-communication work done by the CPU.
But yes, the approach is not what I’d recommend for a truly advanced system. It is good for learning purposes, and it is definitely a robust way to add threading to your game and obtain a good performance boost, but at the cost of extra memory needed for the two buffers.
For a more complex threading system, there are some other ways to do it, as presented in some of the papers I linked to at the bottom of the article. Each method has it’s advantages, and you can’t always decide that one way is better then others. It usually depends on the structure of the rest of the engine.
November 28th, 2009 - 07:46
Nice one. It will help me a lot.
Thanks,
Timo
November 30th, 2009 - 03:12
yah, I think your system is a great intro to the very complex world of multithreading. Like I said, I originally went down a very similar path to what you are describing, so I do feel that there are benefits, but in my situation the drawbacks outweighed them.
October 9th, 2010 - 20:25
a simpler approach might be:
a) each GameComponent maintains two DrawState objects
b) Draw() method ONLY uses information in current DrawState object to draw
c) Update() keeps only ONE version of “UpdateState”
d) single point of control for flip flop using Game.CurrentDrawStateIndex = 0 or 1
e) GameComponent.Draw() { CurrentState = DrawState[Game.CurrentDrawStateIndex]; }
this is easy to design – anything that Draw() requires goes into DrawState
November 22nd, 2010 - 17:37
When I try using Multiple Threads, the controls don’t seem to work, however they do work if I use a single thread (uncomment the line in the draw method and comment out the one in load). Any idea why this is?
December 13th, 2010 - 05:41
I should notice something: because of the way xna handles input, Keyboard input can be received only from the main thread, due to this you have to create your own input handler to fix this