-
-
Save Ilgrim/95a09ba9563502c5773073808cbfd62f to your computer and use it in GitHub Desktop.
SDL2 game loop forcing a certain frame rate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////// | |
// | |
// EDIT: Warning, this doesn't seem to work well. | |
// | |
////////////////////////////////////////////////// | |
// public domain | |
// Let's talk about game loops - it looks a bit hairy, but there is a method to this madness. | |
// | |
// Our general strategy is to try to enforce 60 thinks per second and as many frames per second | |
// as possible (up to 60). | |
// | |
// We do this by using a sliding window of 60 timestamps. These timestamps record when the | |
// previous 60 thinks happened. | |
// | |
// This means when we record a new think has happened, we can see how long the previous 60 | |
// thinks took. Note that this number isn't stored in seconds -- we are looking at raw counter | |
// values. | |
// | |
// Therefore, we want our 60 thinks to take the amount in sec (SDL_GetPerformanceFrequency). We | |
// can calculate how far off we are. | |
// | |
// If we are too slow, then we skip some frames. | |
// | |
// If we are too fast, then we use SDL_Delay to slow down. | |
// | |
// The next complication is: how much do we SDL_Delay by? We are at the mercy of the operating | |
// system, and SDL_Delay makes no guarantees about accuracy. | |
// | |
// Therefore, we sample SDL_Delay's *actual* delay. We time how long SDL_Delay takes for the | |
// requested delay of 0ms to 7ms. | |
// | |
// Once we have a baseline measurement, we can calculate which delay to use to take up a certain | |
// amount of count ticks. When we delay again, we measure it so that we can graudally update | |
// our estimated prediction for that particular delay amount. | |
// | |
// As a last resort, we busy wait until the right amount has elapsed. | |
#define TGT_FPS 60 | |
uint64_t sec = SDL_GetPerformanceFrequency(); | |
double secinv = 1.0 / sec; | |
uint64_t now, delta, now2, delta2; | |
int d; | |
// time the actual resolution of SDL_Delay(0..7) | |
uint64_t delay_guess[8] = {0}; | |
for (d = 0; d < 8; d++){ | |
now = SDL_GetPerformanceCounter(); | |
SDL_Delay(d); | |
delay_guess[d] = SDL_GetPerformanceCounter() - now; | |
} | |
uint64_t timestamp[TGT_FPS] = {0}; // sliding timestamp window | |
int ts = 0; | |
int skip_frame = 0; | |
int think_per_sec = 0; | |
int frame_per_sec = 0; | |
uint64_t one_frame = (uint64_t)(sec / (double)TGT_FPS); | |
uint64_t next_sec = now + sec; | |
while (true){ | |
// think | |
event_pump(); // pump all the SDL events | |
game_think(); // process input and update game state | |
think_per_sec++; | |
// render | |
if (skip_frame > 0) | |
skip_frame--; | |
if (skip_frame == 0){ | |
game_frame(); // render a frame | |
SDL_GL_SwapWindow(win); | |
frame_per_sec++; | |
} | |
// enforce TGT_FPS | |
now = SDL_GetPerformanceCounter(); | |
delta = now - timestamp[ts]; | |
if (delta > sec){ | |
// too slow, so try to skip some frames if we can | |
// if this isn't our first loop, and we aren't skipping frames, then we should skip some | |
if (timestamp[ts] > 0 && skip_frame == 0){ | |
skip_frame = (int)((delta - sec) / one_frame) + 1; | |
if (skip_frame > 4) | |
skip_frame = 4; | |
} | |
} | |
else{ | |
// we were too fast, so delay a little bit to slow down | |
delta = sec - delta; // delta now stores how much time we have to burn | |
delay_more: | |
if (delta >= delay_guess[0]){ | |
// unraveled binary search for best delay length | |
d = delta >= delay_guess[4] ? | |
(delta >= delay_guess[6] ? | |
(delta >= delay_guess[7] ? 7 : 6) : | |
(delta >= delay_guess[5] ? 5 : 4)) : | |
(delta >= delay_guess[2] ? | |
(delta >= delay_guess[3] ? 3 : 2) : | |
(delta >= delay_guess[1] ? 1 : 0)); | |
// perform the delay | |
SDL_Delay(d); | |
// measure the actual change | |
now2 = SDL_GetPerformanceCounter(); | |
delta2 = now2 - now; | |
now = now2; | |
// update our guess by inching our way towards new measurement | |
// new_guess = (old_guess * 15 + delta) / 16 | |
delay_guess[d] = ((delay_guess[d] << 4) - delay_guess[d] + delta2) >> 4; | |
// subtract actual change from delta | |
if (delta2 >= delta) | |
delta = 0; | |
else{ | |
delta -= delta2; | |
goto delay_more; | |
} | |
} | |
if (delta > 0){ | |
// busy wait the rest | |
now2 = now + delta; | |
while (now2 > now) | |
now = SDL_GetPerformanceCounter(); | |
} | |
} | |
// record moment | |
timestamp[ts] = now; | |
ts = (ts + 1) % TGT_FPS; | |
if (now >= next_sec){ | |
printf("Thinks per sec: %2d Frames per sec: %2d\n", think_per_sec, frame_per_sec); | |
think_per_sec = frame_per_sec = 0; | |
next_sec = now + sec; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment