I've noticed this ever since I started playing games, and now see it in my own experiments. If I don't do any capping my game runs at 200~250 FPS, with random drops to 150FPS, but if I cap the FPS to 60 using a simple precision timer + sleep method, the game will sometimes (much more frequently than I want) render at ~40FPS during complex scenes.

I suspect this is simply because my process gets dequeued by the OS during the time I used to spend rendering the other 140 frames, meaning there's more context switches going on, as well as allowing other processes to steal all the cores and do CPU-intensive tasks in them. At the same time, this doesn't feel right, and one would think modern OS's are able to do scheduling that is good enough to avoid that.

So, my question is, are there any techniques to optimize my game for constant FPS rather than high-but-with-lots-of-variation FPS? Should I just make sure my game logic is being updated in a constant rate and don't worry about render rates?

Please post a minimal example of the code you use to limit the framerate.
–
EricDec 23 '12 at 21:07

There is a chance that the low frame time drops are similar in both scenarios, but they are swallowed more by averaging with a lot of high FPS values, and just more obvious by averaging with low FPS values.
–
Maik SemderDec 23 '12 at 21:14

5

I guess it's mostly because you are using sleep method
–
Ali.SDec 23 '12 at 21:15

2 Answers
2

You understood the concept very well. You even decoupled the render cycles from the logic/simulation update cycles.

I would suggest to try this, rename maxFrameTime to desiredTimeStep or timeStep.

And change this:

while (thisFrame > 0.)

To this:

while (thisFrame >= desiredTimeStep)

What is Spiral of Death? Well, when you do thing like this:

thisFrame = now - lastFrame;

What happen when the player pauses the game for a lot of time and when resuming the delta between now and lastFrame is a really big number? because the game was not updating (consuming that time) for a lot of time and it accumulates. The result is that your while (thisFrame >= desiredTimeStep) will run hundreds or even thousands of iterations until thisFrame is less than desiredTimeStep (lees than 0. in your original code). This can cause all types of undesired effects, like an enemy that was far away come and kill the player without the player can do anything about it because the game is running hundreds of updates before render, and the player don't get and updated view of what is happening.

Tip: you can implement this protection as a simple if statement. After calculation of thisFrame and before entering the inner while loop, check if thisFrame is more than your maximum allowed frame time, and if greater cap it to you maximum allowed. The reason to tell you to rename your maxFrameTime constant to desiredTimeStep is that maxFrameTime is a name more intuitive to implement protection again spiral of death, while desiredTimeStep is a more intuitive name to refer to the expected step between frames. Note that the maximum allowed frame time must be various times greater than desiredTimeStep, to allow resync when the game lagged for some reason but preventing hundreds of iterations of the inner loop. I'm happy with 250000000 nanoseconds.

Note: pause by player may not be the better example of when spiral of death can occur, because your game may be stopping only the inner loop when paused and not the outer loop, allowing the thisFrame to continue being calculated even when paused. So you avoid spiral of death that way, but it still can occur if your game lag for some reason (you opened a heavy program while game still running in a machine that can run your game smoothly only if no other applications are running at the same time).

Depending on how you're measuring FPS, your individual frame time may be spiking as high in both the "150FPS" and "40FPS" cases (missing the 16ms limit for a smooth 60fps and, assuming a 60hz refresh rate, having an effective display time of 33ms / 30fps for that individual frame.) This may be partially obscured by the higher framerate version having enqueued more updated frames, and thus still having something to swap in on the next vsync. Some back of the envelope math: 250fps ~ 4ms/frame. If 13 of those spike up to 33ms instead, we'll still get 150 frames in a second: Solving x for (1000-x*33) / 4 = (150-x)

A huge chunk of this can be caused by your code blocking (creating textures, doing file IO, paging data back in, locking in-use textures, even simply allocating while another thread holds the relevant lock...) or even just having a lot to do (unfortunately syncronized timers, sudden bouts of pathfinding, etc...). If you want to try to iron these out, rule 1 is to use a profiler. Rule 2 is to use a profiler. Rule 3 is to use multiple profilers (both to inspect possible graphics API related hitching with vendor GPU profiling tools, and to inspect inspect other possible causes with standard CPU time profiling. (You may wish to create a setup where the profiling results of spiking frames are automatically recorded and the rest discarded... having lots of snapshots will help you determine if it's spiking in the same place all the time or not.)

I'll further note that most games cap to 60FPS by way of vsync, not timer. Even the highest precision timers can have problems on some systems, be it unreliability (because e.g. CPU cores are clocked at different rates) or low precision (even if your API exposes microseconds, that doesn't mean you're getting microsecond precision -- you might not be even getting millisecond precision, depending on the API and hardware). While I wouldn't bet money on it, it's entirely possible your timing code is currently your entire problem when targeting a smooth 60fps. I'd wager it's at least part of your problem, however.

Very nicely put. I'm only experimenting with gamedev and have almost no experience with graphics programming, but it's reassuring to see that the optimization methods are very similar – profiling, profiling, profiling :). As for the last note, in all the cases I noticed this the FPS cap wasn't vsync (at least id Tech, GoldSrc and Source allow capping the FPS at arbitrary values with vsync disabled).
–
Reuben MoraisDec 23 '12 at 21:45

I'll also note your new pseudo code doesn't do render framerate limiting unless Update sleeps (hopefully it doesn't), and update framerate code appears flawed (if each frame is 17ms long, your inner while loop will execute and call Update() twice every frame, resulting in near 120fps. On the flip side if rendering falls bellow 60fps, Update() will execute at the same framerate as render framerate.) I believe this covers what you actually want: gafferongames.com/game-physics/fix-your-timestep
–
MaulingMonkeyDec 23 '12 at 21:51