Advertisement

60fps framelock done properly - without duplicated or skipped frames

Started by December 06, 2019 12:27 PM
11 comments, last by frob 4 years, 11 months ago

Vsync locks the framerate at the monitor's refresh rate, if 60hz 60fps, 144hz, 144fps.

Advertisement

Vsync locks the framerate at the monitor's refresh rate, if 60hz 60fps, 144hz, 144fps. It can be enabled/disabled by the user, you just have to make an option for it in the game.

Advertisement
d h k said:

Thanks for the answers so far. I should have provided a bit more info to justify my choice to lock the framerate at 60 for this specific game prototype, but decided to keep the original post as short as possible.

In a nutshell: I am fully aware of the benefits of high refresh rate monitors and framerate independent game loops. But this specific project is a 2D retro platformer using hand drawn sprite sheets and a very low screen resolution. So at a high refresh rate, I'd simply be drawing the same exact frame multiple times since animation frames are not interpolated and sprites can not move less than one on-screen pixel.

I understand that sleep() functions are imperfect but try to mitigate that fact in my code by calculating the next time_point based on the old one, instead of using chrono::steady_clock::now() every frame. This should eliminate the clock drift that over-long sleeps create. And my goal is to only present a new frame in time for every 60Hz monitor retrace, so I have a duration of time to do it and it doesn't need to happen at one exact time_point so I don't think clock precision is the killer here.

I tried to present to the screen after the wait - but that unfortunately does not appear to help the issue I described.

Any further ideas? Or is it simply not possible to output a fresh frame just in time for every 60Hz retrace of my monitor?

If you really want to do it this way, you could technically do busy waiting instead of sleeping, that would probably get you closer to what you want. On the other hand it will spin the core your main thread is on at 100%, so generally I wouldn't recommend doing it, and in theory you could still miss frames.

d h k said:
Or is it simply not possible to output a fresh frame just in time for every 60Hz retrace of my monitor?

The problem is that you are having two clocks, one of which you base your measurements on, and the other is the one your monitor refreshes on. There is no guarantee that the two clocks stay in exact lockstep sync, plus there is no guarante that your monitor's 60Hz refresh rate is actually physically 60Hz. It can be slightly off in either ways and can even fluctuate in minuscue amounts. (Borderline related stuff: Remember the time when last year european clocks run 6 minutes late, because Serbia and Kosovo got into a power dispute that affected the frequency of the entire european grid slightly, causing the clocks to run slower ever so slightly.)

The proper solution is what frob already mentioned, decoupling your game logic/updates from your rendering code, letting rendering run as fast as it can (either limited by vsync and a set refresh rate, or just letting it loose altogether), and running your game logic at a set fixed rate of your choosing.


This comes up periodically here and the term used for it is fixed timestep. Below is the solution I use. It's a modified version of what's been offered here and it does work for me on Windows. This runs in your main loop before the draw routine:

int numLoops = 0;

long msecInterval = 1000 / targetFps;
		
if (updatedTime == 0)
    updatedTime = PLAT_GetTime() - msecInterval;
			
unsigned long curTime = PLAT_GetTime();
		
while ((updatedTime < curTime) && (curTime - updatedTime) > msecInterval)
{
	fixedTick();
	
	updatedTime += msecInterval;
				
	numLoops++;
    
    if (numLoops >= 100)
		updatedTime = PLAT_GetTime();
}

targetFps is the desired frame rate (30, 60, 90, etc)

fixedTick() is the timed update function where game updates.

PLAT_GetTime() is below:

#include <windows.h>

long PLAT_GetTime()
{
	SYSTEMTIME time;
	GetSystemTime(&time);
	LONG time_ms = (time.wMinute * 1000 * 60) + (time.wSecond * 1000) + time.wMilliseconds;
	
	return time_ms;
}


The numLoops > 100 is a safeguard in case we get stuck in the loop. I don't know why but it does happen in my case sometimes.

Also, I have a suspicion that the above only works if we can render frames faster than we need to update them. In other words, I suspect it freezes when rendering gets too slow. Please feel free to offer improvements. I struggled with this for a while and while not perfect, at least it works, unlike the out-of-the box code I found here.

Physics and simulations should run at a fixed time step. There are plenty of bugs you can search for spanning decades where players experience different things when those run at different steps. They can run faster and reach unplayable speeds as new machines come out, or allow exploits like certain processor speeds passing through objects or landing on walls.

Rendering should happen as fast as possible, running independently of the world's simulation. Use a blocking operation like D3D's Present() to wait until graphics are rendered.

If you're looking for pseudocode:

Loop 
  while( simulation time < now ) 
    advance simulation by one step // Notice this may run zero or more times 
  render to back buffer 
  present the back buffer, which blocks until the screen is ready
End loop

You don't need to code any special delays in your code for varying frame rates, nor do you need to do any special code to account for missed frames. The blocking page flip function like Present() handles that work for you. If the monitor can render at 50Hz, 60Hz, 72Hz, 90Hz, 144Hz, or anything else, you need to do nothing different at all other than call the blocking display function.




This may help: https://medium.com/@tglaiel/how-to-make-your-game-run-at-60fps-24c61210fe75

This topic is closed to new replies.

Advertisement