How to make your game run at 60fps

Tyler Glaiel
17 min readMar 4, 2019

--

Here’s the deal. You got a game, and you want to run it at 60 fps on a 60hz monitor. Your computer is fast enough that both rendering and updating take a negligible amount of time, so you enable vsync and write your game loop like this:

while(running) {
update();
render();
display();
}

Easy! Your game now runs at 60fps and is smooth as butter. Done. Thanks for reading this post, you can follow me on twitter for more hot gamedev tips.

Ok obviously this isn’t good enough. What if someone is running on a lower spec computer that can’t render the game fast enough to handle 60fps? What if their monitor is one of those fancy new 144hz monitors? What if they forced vsync off in their driver settings?

So you think, well I gotta measure time in here somewhere and make sure I’m updating at the right frequency. It’s fairly simple to do, you just accumulate time every loop and do an update every time it goes over the threshold of 1/60 of a second.

while(running) {
deltaTime = CurrentTime()-OldTime;
oldTime = CurrentTime();
accumulator += deltaTime;
while(accumulator > 1.0/60.0){
update();
accumulator -= 1.0/60.0;
}
render();
display();
}

Done. Boom. Easy. In fact there’s a ton of games out there that ship with code that looks basically exactly like this. But it’s wrong. This works fine for regulating timing but introduces stuttering issues and other kinds of inconsistencies. A common one here is just that frames are not exactly 1/60th of a second even if you have vsync on, there’s a bit of noise in how long they take (and how precise the OS timer is). So you’d get situations where you render a frame, and the game doesn’t think it’s time to update again (because the accumulator is behind by a tiny minuscule amount) so you just repeat the same frame again, but now the game is a frame behind so it does a double update. Stutter!

So there’s a few existing solutions to fixing that stutter you can find with some google searching, for instance you could have your game use a variable timestep instead of a fixed timestep and just skip the accumulator junk in your timing code entirely. Or you can do a fixed timestep with an interpolated renderer, as described in the pretty famous “Fix Your Timestep” blog post from Glenn Fiedler. Or you can fudge your timer code to be a little bit more lenient, as described in the “Frame Timing Issues” blog post from Slick Entertainment (unfortunately the blog no longer exists).

Fuzzy Timing

Slick Entertainment’s method of “timing fuzziness” was the easiest to implement in my engine, as it didn’t require any changes within game logic or rendering, so I did that for The End is Nigh. It was about as plug and play as it gets. In summary, it basically just lets the game update “a little bit early”, so as to avoid timing inconsistency issues. If the game is vsynced this should let it just use the vsync as the main timer for the game, and you’d get a buttery smooth experience.

Basically, this is what the code for updating looks like now (the game “can run” at 62 fps, but it still treats each timestep as if it was 60fps. I’m not sure why it needs to clamp it to prevent the accumulator from going below 0, but it doesn’t work without that). You can interpret this as “the game updates in lockstep if its rendering between 60fps and 62fps”:

while(accumulator > 1.0/62.0){
update();
accumulator -= 1.0/60.0;
if(accumulator < 0) accumulator = 0;
}

If you’re vsynced, this basically just lets the game be in lock step with the monitor’s refresh rate, and you get a buttery smooth experience. The main issue here is you would run *slightly* fast if you were not vsynced, but it’s such a minor difference that nobody would notice.

Speedrunners. Speedrunners noticed. Shortly after the game was released they noticed that some people on the speedrun records list had worse in-game-times but slightly better measured times than others. And this was directly caused by the timing fuzziness and something forcing vsync off in the game (or running on a 144hz monitor). So it was clear I needed to disable that fuzziness if vsync was off.

Oh but there’s no way to check if vsync is off. There’s no OS call for it, and while you can request for vsync to be enabled or disabled from your application, it’s completely up to the OS and graphics driver on whether or not to actually enable it. The only thing you can do is render a bunch of frames and try to measure how long they take, and try to see if they all take about the same time. So that’s what I did for The End is Nigh. If it wasn’t vsynced at 60hz, it falls back to the original “strict 60 fps” frame timer. Plus I added a config file setting to force it to not use fuzziness (mainly there for speedrunners who want accurate times), and gave them an accurate in-game timer hook they could use for their autosplitter.

Some people still complained about occasional single frame stutters, but they seemed rare enough that they were probably just OS events or something. Not a big deal. Right?

Recently when reviewing my timer code I noticed something odd. The accumulator was drifting, every frame would take a little bit longer than 1/60th of a second, so periodically the game would think its a frame behind and do a double update. It turns out my current monitor is 59.94hz instead of 60hz. This meant that once every 1000 frames, it would need to do a double update to “catch up”. Simpleish fix though, instead of having the range of acceptable framerates be 60 to 62, you just make it 59 to 61 instead.

while(accumulator > 1.0/61.0){
update();
accumulator -= 1.0/59.0;
if(accumulator < 0) accumulator = 0;
}

The previously described issue about disabled vsync and high refresh rate monitors is still there, and the same solution still applies (fall back to the strict timer if the monitor is *not* vsynced at 60).

But how do I know this is an appropriate solution? How can I test this to make sure it works properly on all combinations of computers with different kinds of monitors, vsync on and vsync off, etc? It’s really hard to track this timer stuff in your head and figure out what causes desyncs and weird cycles and stuff.

The Monitor Simulator

While trying to figure out a robust solution for the “59.94hz monitor problem” I realized I can’t just trial and error this on my computer and expect it to be a robust solution. I needed a good way to test various attempts at writing a better timer and an easy way to see if they would cause stuttering or time drift on various monitor configurations.

Enter the Monitor Simulator. It’s a quick and dirty piece of code I wrote that simulates “how a monitor works” and basically prints out a bunch of numbers that tell me how stable whatever timer I’m testing is.

The original naive stuttery frame timer prints out this, for instance

20211012021011202111020211102012012102012[...]TOTAL UPDATES: 10001
TOTAL VSYNCS: 10002
TOTAL DOUBLE UPDATES: 2535
TOTAL SKIPPED RENDERS: 0
GAME TIME: 166.683
SYSTEM TIME: 166.7

It first prints a number each simulated vsync of how many times the game loop “updated” since the last vsync. Anything other than a bunch of 1s in a row is a stuttery experience. At the end it prints some collected statistics.

Using the “fuzzy timer” (with a range of 60–62fps) on a 59.94hz monitor, it prints out this

111111111111111111111111111111111111111111111[...]TOTAL UPDATES: 10000
TOTAL VSYNCS: 9991
TOTAL DOUBLE UPDATES: 10
TOTAL SKIPPED RENDERS: 0
GAME TIME: 166.667
SYSTEM TIME: 166.683

It takes a while to get a frame stutter, so it can be hard to notice where that happens in the mass of 1s. But the stats it prints clearly shows that it had a few double updates in there, and thus would be a stuttery experience. The fixed version (with a range of 59–61 fps) has 0 skipped or doubled updates.

I can also disable vsync. The rest of the output is irrelevant, but it can clearly show me how much “Time Drift” occurred (system time is off from where game time should be).

GAME TIME: 166.667
SYSTEM TIME: 169.102

This is why you need to switch back to the stricter timer if vsync is off. That discrepancy adds up over time.

If I set render time to .02 (so it takes “more than a frame” to render), I get stuttering again. Ideally this should make the game’s frame pattern be 202020202020, but it’s slightly uneven.

This timer does slightly better in that situation than the previous one, but its getting more and more complicated and harder to see how or why it works. But hey I can just shove tests at this simulator and see how they do, and then try to figure out why they work later. Trial and error baby!

while(accumulator >= 1.0/61.0){
simulate_update();
accumulator -= 1.0/60.0;
if(accumulator < 1.0/59.0–1.0/60.0) accumulator = 0;
}

Feel free to download the monitor simulator yourself and try various timing methods. Absolutely tweet at me if you find anything better.

I’m not 100% happy with my solution (it still requires that “detect vsync” hack, and it can still do a single stutter if it ever gets out of sync), but I think this is about as good as you’re going to get for trying to do a lockstep game loop. Part of the problem is its just really difficult to determine the parameters of what counts as “acceptable” here. It’s all about the tradeoff between time drift and doubled/skipped frames. If you shove a 60hz game on a 50hz PAL monitor… what even is the correct solution here? Do you stutter like crazy or do you run noticeably slower? Both options just feel bad.

Time Snapping

After posting this article originally I came up with another way to do a fixed timer that avoids the pitfalls of needing to know whether vsync is enabled or not, and is fairly robust and doesn’t care about sync issues like the previous method did.

Basically instead of having the accumulator try to account for inaccuracies in the timer, you just snap delta time to 1/60 if the previous frame was “about 1/60 of a second” *before* adding it to the accumulator. And likewise for other multiples of that.

if(abs(delta_frame_time - 1.0/120.0) < .0002){
delta_frame_time = 1.0/120.0;
}
if(abs(delta_frame_time - 1.0/60.0) < .0002){
delta_frame_time = 1.0/60.0;
}
if(abs(delta_frame_time - 1.0/30.0) < .0002){
delta_frame_time = 1.0/30.0;
}
accumulator += delta_frame_time;while(accumulator >= 1.0 / 60.0){
update();
accumulator -= 1.0 / 60.0;
}

The thresholds for what counts as “close enough to 1/60” are not thoroughly tested and tuned yet, but you can tweak those as you see fit anyway.

This is basically a reformulating of the “fuzzy timing” problem, basically backing up and rethinking what “fuzzy timing” is supposed to solve. And really at it’s core, fuzzy timing was meant to solve the issue of “the game is vsynced but frames don’t ever take exactly 1/60th of a second, there’s some error there”. So instead of a weirder timing method meant to account for that in the wrong place, this just solves that problem in the most direct way possible. Did the previous frame take about 1/60th of a second? It did? Ok then lets just pretend it took *exactly* 1/60th of a second. Likewise for 1/30 and 1/20 and 1/15 and 1/120, etc. If vsync is off, then you basically wont ever hit those values exactly so it never ends up rounding.

There is an issue with floating point accuracy here, I’ve added an addendum at the bottom that covers that.

Decoupled Rendering

The previous methods I’ve described are what I refer to as “lockstep rendering”. You update, then render, and whenever you render you’re always showing the most recently computed game state. Rendering and updating are coupled together.

But you can decouple them. That’s what the method in the Fix Your Timestep post described. I am not going to reiterate what’s in that post, so you should definitely give it a read. This is (as far as I can tell) the “industry standard” method used in AAA games and engines like unity or unreal (Tight action-oriented 2D games usually prefer lockstep though, because sometimes you just need the precision you get from that method).

In summary though, that post just describes the method where you update at a fixed framerate, but when you render you interpolate between the “current” game state and the “previous” game state using the current accumulator value as the measure of how much to interpolate by. This way you can render at whatever framerate you want, and update at whatever update rate you want, and it will always be smooth. No stutters, works universally.

while(running){
computeDeltaTimeSomehow();
accumulator += deltaTime;
while(accumulator >= 1.0/60.0){
previous_state = current_state;
current_state = update();
accumulator -= 1.0/60.0;
}
render_interpolated_somehow(previous_state, current_state, accumulator/(1.0/60.0));
display();
}

Boom. Easy. Problem solved.

Now to just get it so my game can render interpolated game states and… wait that’s actually not simple at all. This post just assumes that’s a thing you can do. Its easy enough to cache the previous transform of your game object and interpolate transforms, but games have a lot more state than just that. There’s animation states and object creation and destruction and a lot of other shit to take into consideration.

Plus in game logic you now have to care whether or not you’re teleporting an object or smoothly moving it to avoid the interpolator making wrong assumptions about the path a game object took to get where it is. Rotations can be a mess especially if you’re changing a rotation by more than 180 degrees in a single frame. How do you correctly handle objects being created or destroyed?

I’m currently working on this in my own engine, and basically just interpolate transforms and let everything else remain as it was before. You don’t really notice stuttering if something isn’t smoothly moving, so animations skipping frames and object creation/destruction being up to a frame off sync isn’t an issue if everything else is smooth.

It is weird however that this method basically has the game render up to 1 game state behind where the simulation currently is. It’s not really noticeable but it can compound with other sources of delay like input lag and monitor refresh rate, and anyone who wants the most responsive game experience (hey speedrunners) would probably much rather have the game be lockstep instead.

In my engine I’m just making this be an option. If you have a 60hz monitor and a fast computer, use lockstep with vsync on for the best experience. If you have a monitor with a weirder refresh rate, or a weaker computer that cant consistently render at 60, then turn on frame interpolation. I want to call this “unlock framerate” but am worried people think that just means “turn this on if you got a good computer”. That’s a problem to solve later though.

Now there *is* a method that sidesteps that problem though.

Variable Timestep Updates

I got a bunch of people asking why not just update with a variable timestep, and often see armchair programmers say “well if a game is programmed CORRECTLY they just update at arbitrary timesteps”.

while(running) {
deltaTime = CurrentTime()-OldTime;
oldTime = CurrentTime();
update(deltaTime);
render();
display();
}

No weird timing junk. No weird interpolated rendering. It’s simple and it works.

Boom. Easy. Problem solved. For good this time! Can’t get any better than this!

Now you just need to make your game logic work on arbitrary timesteps. Easy right, you just go through and change code that looks like this:

position += speed;

to this:

position += speed * deltaTime;

and you change code that looks like this:

speed += acceleration;
position += speed;

to this:

speed += acceleration * deltaTime;
position += speed * deltaTime;

and you change code that looks like this:

speed += acceleration;
speed *= friction;
position += speed;

to this:

Vec3D p0 = position;
Vec3D v0 = velocity;
Vec3D a = acceleration*(1.0/60.0);
double f = friction;
double n = dt*60;
double fN = pow(friction, n);
position = p0 + ((f*(a*(f*fN-f*(n+1)+n)+(f-1)*v0*(fN-1)))/((f-1)*(f-1)))*(1.0/60.0);
velocity = v0*fN+a*(f*(fN-1)/(f-1));

….

wait hold up
where the fuck did that come from?

Ok that last bit is literally cut and pasted from my engine utility code for “actual correct framerate independent move with speed-limiting friction” function and contains a little bit of extra cruft in there (those multiplies and divides by 60). But that is the “correct” variable timestep version of the previous snippit. I calculated it over the course of an hour or so with gratuitous help from wolfram alpha.

Now there’s going to be people saying why not just do:

speed += acceleration * deltaTime;
speed *= pow(friction, deltaTime);
position += speed * deltaTime;

And while something like that kinda works, it’s not actually correct. You can test it yourself. Do 2 updates of that with deltaTime set to 1, and do it once with deltaTime set to 2, and the results aren’t actually the same. Typically you want your game to run consistently, so having inconsistencies like this aren’t great. Its probably good enough if you know your deltaTimes are all around the same value, so then you need some code to make sure your updates are running at some kind of fixed rate and… oh. Right. We’re trying to do it the “CORRECT” way now.

If that tiny bit of code expands to that monstrous pile of math, imagine more complicated movement patterns involving multiple interacting objects and such. You can clearly see how doing it the “correct” way is infeasible. So the “rough approximation” is basically all you got. Lets ignore that for now and assume you actually do have the “actual correct” version of your movement functions. Good, right?

Well, no. Here’s an actual real life example of an issue I had with this in Bombernauts. You can jump about 1 tile high, and the game takes place on a grid of 1 tile blocks. Your feet need to clear the top of the block in order to land on it.

But since collision detection here is in discreet steps, if the game was running at a slower framerate your feet would sometimes not actually clear the top of the tile, even though the movement curve they followed was the same, and you would just slide down the wall instead.

This is obviously a solvable problem. But it illustrates the types of problems you encounter when trying to make your variable timestep game loop work correctly. You lose consistency and determinism, so you can just throw away the ability to do input replays or deterministic multiplayer and such. For a 2D action reflexy game, consistency matters a ton (hey speedrunners).

If you’re trying to regulate your timesteps so they aren’t too large or too small then you kinda lose the main benefit you get from doing variable timestep in the first place, and you may as well just use one of the other 2 methods I described here instead. It’s not worth it. There’s too much extra effort involved on the game logic side of things (making sure your movement math is correct) and it requires too many sacrifices in the determinism and consistency department. I would only use this method for something like a rhythm game (where movement equations are simple and you want the maximum responsiveness and smoothness possible). Otherwise gimme that fixed update.

Conclusion

You now know how to make your game run at a consistent 60fps. It’s trivially easy and there’s no reason anyone should have ever had any trouble with it before. There’s no other issues that could complicate this further. Thanks for reading you can follow me on twitter for more hot gamedev tips.

Addendums

I’m thrilled at the reception this blog post originally received, and I want to make sure this is the most up to date and thorough resource for this common gamedev problem out there. So I will update this article as new information is brought to my attention, and I’m also adding a few more pieces of information here that I couldn’t find the place for in the original article.

Hybrid Approaches

Unity (and other big engines) use a hybrid approach. Unity provides Update() and FixedUpdate() callbacks separately. Update uses variable time steps and FixedUpdate uses fixed time steps, plus it automatically interpolates stuff like physics states and animation states. If you mix and match both of those update callbacks without knowing how they work under the hood, you end up getting weird stuttering inconsistencies in your unity project. It’s a common problem I’ve seen in unity games, so even if you are using an engine, you still should understand how this all works.

1000hz Fixed Update

I’ve seen a few people mention to me their solution to this is to just update at a fixed rate of 1000 times per second. Because the difference between doing 1 and 2 updates per frame is a lot more noticeable than the difference between 16 and 17 updates a frame. You can do this if your game is pretty simple, but it does not scale well to more complicated projects.

Timing Anomalies

You do need to account for various timing anomalies when measuring frame code. If delta time is less than 0, that means that the system timer wrapped around. If it’s really high, you probably don’t want to fast forward your game a ton in one step, so you probably should cap it. If you just clamp deltaTime to between 0 and <maximum delta time> (this is 8/60 (7.5fps) for me), that should account for most anomalies.

Resyncing

In my engine I have a manual callback I can use to “resync” my timer code (set the accumulator to 0 and delta time to 1/60 the next time through the loop), which I do after loading a level or swapping scenes. You need this because you typically don’t want the game to start each level by immediately trying to make up the time it spent loading.

Spiral of Doom

If your game cannot *update* at 60hz, you end up in a spiral of doom where your game can never catch up to where it should be, and so it will do more and more updates every time until it eventually just freezes. Cap your accumulator to a maximum (I use 8/60 (7.5fps) as the max) and it should prevent that issue. This will not be a fun experience for whoever is trying to play the game, but at least it won’t freeze.

Floating Point Inaccuracies

In this article and my monitor simulator code, I was using doubles for clarity. Using doubles or floats introduces floating point error, like adding 1.0/60.0 60 times in a row will not actually end up being exactly 1. In my engine I actually use 64 bit integers for my timer code instead to sidestep this. SDL reports the system timer as a 64 bit int, so I just keep it in this format to avoid the loss you get from converting it to a double. In the game itself, this gets converted to double, but the timer code keeps it as an int.

Situations where interpolation is not an option

Interpolation is the industry standard method of doing this, because it’s a good robust solution that works great in the majority of use cases. But this has resulted in a few people acting as if there is no reason to even consider lockstep, and existing resources about this basically never even discuss it. Ignoring the extra difficulty involved in getting interpolation working, there are a few situations where interpolation just inherently isn’t an option, like emulators. It’s up to you to determine what your needs and options are here for your specific use case.

Averaging Delta Time

Some games and engines average together the previous few time deltas to smooth out any single frame spikes. For example, instead of a single slow frame resulting in 4 updates on the next frame, it would do 2 updates for the next 3 frames instead. You do this averaging when you compute delta time, before adding to the accumulator.

Update Multiplicity

An uneven framerate is worse than a slow framerate. For this reason I have a setting in my engine called “update multiplicity” that basically just makes the game always do a multiple of N updates at a time.

while(accumulator >= (1.0 / 60.0) * update_multiplicity){
for(int i = 0; i<update_multiplicity; i++){
simulate_update();
accumulator -= 1.0 / 60.0;
}
}

Setting update multiplicity to 2 basically says “Act as if this game is running at 30fps even if it isn’t”. For people with low powered machines this is preferable to alternating between 30 and 60 rapidly. There’s probably some way to detect uneven framerates and just fall back to this, but I just have it as a manual setting for now.

Sample Code

This is production code from my engine that I have commented. It uses many of the techniques and tricks I’ve illustrated in this post. You probably can’t just plug and play this into your engine, but I’ve decided to put this up for reference because sometimes you just wanna see what everything looks like together.

--

--