Reputation: 903
This is how I handle the game loop:
while (running) {
diff = duration_cast<milliseconds>(end - start).count();
start = clock::now();
dt = diff / (16.0);
handleInput(); // get input
update(dt); // game logic
render(); // render game
SDL_GL_SwapWindow(window); // swap frame buffer
end = clock::now();
}
It is intended to be a fixed timestep game locked to 60FPS (it's a re-made emulation of an SNES game) however it runs on 144 timestep on my 144hz screen, making it far too fast. Vsync can't solve this, so what can?
Upvotes: 4
Views: 3670
Reputation: 21
The SDL_Delay()
and SDL_GetTicks()
solution is less-than-desirable due to its imprecision.
We could hot-loop:
while (1)
if stopwatch.time() < 16666 // in usec
break
stopwatch.reset()
input()
update()
render()
But this is less-than-desirable as it'll eat up CPU cycles that could be put elsewhere or save energy.
We could act only even frames
while (1)
if frameCount = 1
frameCount = 0
else
frameCount++
input()
update()
render()
waitForVSync // aka SDL_GL_SwapWindow(window)
But this only gets us to 72 fps.
If we need to get as close to 60 FPS as possible, it may be best to use a hybrid of the above two approaches: hot-loop on even frames until we've gotten to 16.666 ms.
while (1)
if frameCount = 1
frameCount = 0
else
while stopWatch.time() < 16666 // in usec
break
stopwatch.reset()
frameCount++
input()
update()
render()
waitForVSync // aka SDL_GL_SwapWindow(window)
A bonus of this: you don't have to just skip odd frames, you could use them for various things. Perhaps update on odd frames, render on evens? Apply video filters on odd frames? Lots of possibilities.
A note: you should probably be looking at frame-times overall. This solution only works when framerates are at least 120 fps. When framerates drop below 20s, and you want to be more accurate than OS threads will give you, hot-looping is your best bet.
The other alternative is to tap into OS scheduling, which tends to be more accurate than OS thread sleeping. However, accuracy is still not guaranteed for all systems.
Upvotes: 1
Reputation: 5665
Here is a quick example of how game loop can be implemented:
int32_t tickInteval = 1000/FPS; // frequency in Hz to period in ms
uint32_t lastUpdateTime = 0;
int32_t deltaTime = 0;
while (running) { // running condition
uint32_t currentTime = SDL_GetTicks();
deltaTime = currentTime - lastUpdateTime;
int32_t timeToSleep = tickInteval - deltaTime;
if(timeToSleep > 0)
{
SDL_Delay(timeToSleep); // energy saving
}
update(deltaTime); // game logic
lastUpdateTime = currentTime;
}
I would recommend to look close on this topic.
UPD.
One might be concerned with uint32_t
overflow. And yes it will overflow. After almost two months of uninterrupted run of the game (49.7 days to be precise). What will happen then? currentTime
will be a very small positive integer, lastUpdateTime
will be a very large positive integer. But the subtraction of two will not overflow no matter what. Moreover if the difference does not fit into int32_t
it will be wrapped around modulo of UINT_MAX + 1
resulting in a small positive integer that will be the exact number of ticks these two values different (with regard to unsigned overflow of one).
Upvotes: 4