Advertisement

Limiting framerate in a game (using std::chrono)

Started by January 05, 2022 01:28 PM
5 comments, last by frob 2 years, 11 months ago

trying to limit my framerate (FPS) to a set maximum …my game loop is something like this:

using namespace chrono;
auto startTime=system_clock::now();

while(true)
{
	//do game stuff...
	//render frame...
	
	 auto endTime = system_clock::now();
	 duration<double, std::milli> delta = endTime - startTime;
     startTime = EndTime;

     double frameTime =  delta.count();
     
     //I have tried to cap framerate using something like this:
     
     double FPSMAX=1000.0/60.0;
     if (delta.count() < FPSMAX)
     {
     	duration<double, std::milli> delta_ms(FPSMAX - delta.count() );
        auto delta_ms_duration = duration_cast<milliseconds>delta_ms		   	
	    this_thread::sleep_for(milliseconds(delta_ms_duration.count()));
     }
}

The timer part works fine and I use the “frameTime” variable to multiply with movement speeds and so on. The problem is when I try the framerate capping part…while it does seem to cap FPS everything becomes jerky… My FPS shows about 60 FPS but something is definitely wrong here.

You are using the wrong clock. system_clock is for date/time-related things, and not for timings. use steady_clock instead. I had the same issues when using system_clock for synchronizing thread-based audio-playback, where everything was choppy until I used the other clock.

Advertisement

Your delta calculation is wrong as it should track how long the last frame took instead you're tracking how long last + current frame take.

You might need a second timer which tracks how long the current frame actually took and then set the thread for sleep for as long as there are remaining milliseconds left.

using namespace chrono;
auto startTime=system_clock::now();

const double FPSMAX=1000.0/60.0;

while(true)
{
	 auto endTime = system_clock::now();
	 duration<double, std::milli> delta = endTime - startTime;
     startTime = EndTime;
     
     auto frameStart = system_clock::now();
     
	//do game stuff...
	//render frame...

     auto frameEnd = system_clock::now();
     delta = endTime - startTime;

     if (delta.count() < FPSMAX)
     {
	    this_thread::sleep_for(FPSMAX - delta.count());
     }
}

It is however not 100% accurate but should give 60 frames in average

@Juliean hi, thanks for the help! however, even when i do it your way it still seems jerky when i cap the fps.. my fps counter sys about 60 fps and its stable but movement is jerky still.. maybe i need to adjust my movement code or something

The typical route is to let the hardware limit it.

Further, you're trying to set it to 60 frames per second. That was based on the standards used about 40 years ago when CRT televisions were the displays and NTSC signals were used. While modern monitors are capable of using 60 FPS, that's one of the lowest common values used. 72 Hz, 75 Hz, 90 Hz, 120 Hz, 144 Hz, 165 Hz, 240 Hz, and others are all increasingly common. If you somehow lock your game into drawing at 60 Hz and the windowless display is on a 90 Hz display, you're going to be jittery to the player, twice then skipping a frame, updating twice then skipping a frame, etc.

Assuming you're drawing on a double-buffered display (again, the typical approach) your call to flip page buffers is a blocking call. It will pause execution until the hardware is ready for the next screen, then will wake up when the transition is complete. That might be 14 milliseconds, it might be ten microseconds, it will end up being whatever the hardware says it is. You can do other processing in other threads, but let the call to flip the pages be your timer for when a graphics frame has actually ticked.

And a note on frames per second, as a single number it is a pretty bad indicator of frame time. It's a number many gamers use for convenience, but for developers it is not especially useful.

If you're looking for stability the minimum you need are three numbers for how long it takes to actually do the work: min time, max time, average time.

As a quick example, imagine some scenarios:

16 ms : 16 ms : 16 ms — This means each frame is taking exactly 16 milliseconds to process. So you have no room for growth, but you are steady at 60 frames per second (barely).

1 ms : 943 ms : 16 ms — This is also 60 frames per second, but it means one frame takes almost the entire second and the other 59 frames are nearly instant.

5 ms : 27 ms : 16 ms — This is also 60 frames per second, some frames are taking about 5 ms to process so they have time to do more work, but your worst frames need to reduce their workload by about half to prevent stuttering.

More advanced profiling tools can record timing per frame, using tools like heat graphs, core lanes, bar charts, plots, and assorted other visualizations to help you dig deeper into actual timings.

This topic is closed to new replies.

Advertisement