We have a game. We want to know whether it is limited by CPU or GPU performance. There are three possibilities:
There is no direct way to measure this, but we can work it out by applying some detective skills.
First off, we must get the game into a state where we will be able to notice even small changes in performance. This means adding a framerate display, and using a special profiling project configuration that disables vsync and selects variable timestep mode (you don't want those settings while actually playing the game, but they give more precise results while you are profiling it).
Next, run your game under a CPU profiler. Using the inclusive view (the one that includes all the functions called by any given function, not just the function itself), note how much time was spent inside each of the Big Three methods: Update, Draw, and Present. If you see much time in Draw or Present, that tells us nothing, because this time could be the result of either CPU or GPU overhead, but if all your CPU is inside Update, that proves you must be CPU bound. Congratulations, you reached the end of the investigation!
Most often, the profiler will show some combination of Update, Draw, and Present. If there is a lot of time in Present, this is a hint you might be GPU bound (in which case the CPU is having to wait for the GPU to catch up), but it isn't absolute proof: it is also possible your game might just be causing a lot of CPU translation work for the driver.
To gain more information, we can change something about the game, then observe whether the framerate changes in response.
Paradoxically, even though we are interested in measuring graphics performance, changing our graphics code is not a good way to do that, because drawing calls require work on both the CPU and GPU. Sure, if we temporarily commented out half our drawing code, the framerate will probably go up, but that tells us nothing about which processor was the bottleneck!
To get a useful measurement we must change something that affects one processor but cannot possibly alter the other. The Update method is a good place to start, since that only involves the CPU.
What happens if you add this at the top of your Update?
Thread.Sleep(1);
Does your framerate change? If not, you are GPU bound. Even better, you can gradually increase the time delay until the framerate does change, which will tell you exactly how long the CPU was idle.
If the sleep call does affect the framerate, you must be either CPU bound or evenly balanced. To distinguish between those possibilities, go the other way and try reducing the amount of CPU work.
An easy way to reduce the CPU load is to skip the entire Update method (caveat: this only works if your Update takes a greater than negligible amount of time, which you can check using the CPU profiler). We can't permanently remove Update, because that would also affect our Draw performance (there won't be anything to draw if the Update never got around to spawning any monsters!) but after we run the game normally for a while, we can later skip some Update calls to get a clean measurement.
I like to bind this to a spare key in my profiling builds:
protected override void Update(GameTime gameTime) { #if PROFILE if (currentKeyboardState.IsKeyDown(Keys.PageUp)) Thread.Sleep(1); if (currentKeyboardState.IsKeyDown(Keys.PageDown)) return; #endif ....
This lets me play the game until I reach a "typical" point, then press these keys to see how the framerate changes.
If the sleep has no effect, I must be GPU bound.
If skipping Update speeds things up, I must be CPU bound.
If skipping Update has no effect but sleeping does slow things down, the two must be evenly balanced.