..
#gamedev

Forcing Frame-Rate to the Game

In this post, I'm gonna show you how to force the game frame-rate.

Basically, we need that game runs in 30 or 60 frames per seconds.

This post will be very simple.

The first step is to create utility functions that compute the wall clock and time elapsed.

We'll use the QueryPerformanceCounter to get the wall clock and the QueryPerformanceFrequency for mapping to seconds.

The next code retrieves the wall clock.

inline static
LARGE_INTEGER win32GetWallClock()
{
    LARGE_INTEGER counter;
    QueryPerformanceCounter(&counter);
    return counter;
}

And the next code retrieves the seconds elapsed from A to B timestamp.

inline static
f64 win32GetSecondsElapsed(LARGE_INTEGER start, LARGE_INTEGER end)
{
    return (f64)(end.QuadPart - start.QuadPart) / (f64) perfFrequency;
}

Ok, the next step is to define the target seconds per frame. This value is based on monitor refresh frequency.

We'll assume that the target seconds per frame is half of monitor frequency.

int monitorRefreshHz = 60; // get your monitor hz here!
int gameUpdateHz = monitorRefreshHz / 2;
float targetSecPerFrame = 1.0f / gameUpdateHz;

Now, before the flip (buffer swap), let's force a frame-rate.

Get the seconds elapsed between the work time and last frame counter.

LARGE_INTEGER workCounter = win32GetWallClock();
f64 workSecondsElapsed = win32GetSecondsElapsed(lastCounter, workCounter);
f64 secondsElapsedForFrame = workSecondsElapsed;

Now, if the time elapsed is less than target, we must wait until hit the target.

This 'wait' can be achieve by the Sleep function, that turns the current thread idle.

But, we must check if this machine can sleep, otherwise, we'll consume the CPU time only using the while loop.

UINT desireScheduleMS = 1;
bool isSleep = timeBeginPeriod(desireScheduleMS) == TIMERR_NOERROR;

The above code makes the operation system to be more granularity, I mean, it allows the 1 milliseconds for schedule the thread.

if (secondsElapsedForFrame < targetSecPerFrame) {
    if (isSleep) {
        int fixedLatencyMS = 2;
        DWORD sleepMS = (1000 * (targetSecPerFrame -
                                    secondsElapsedForFrame)) - fixedLatencyMS;
        if (sleepMS > 0) {
            Sleep(sleepMS);
        }
    }
}

The fixedLatencyMS variable ensure that we don't sleep the whole time. For me, works fine.

Now, after the Sleep, if there is still time, let's consume it with while loop.

while(secondsElapsedForFrame < targetSecPerFrame) {
    LARGE_INTEGER nextCounter = win32GetWallClock();
    secondsElapsedForFrame = win32GetSecondsElapsed(lastCounter,
                                                    nextCounter);
}

We'll just get out of the loop if we can achieve the frame rate.

LARGE_INTEGER endCounter = win32GetWallClock();
f64 secondsPerFrame = win32GetSecondsElapsed(lastCounter, endCounter);
lastCounter = endCounter;

At the end of frame, the lastCounter receive the current wall clock endCounter.

The program must fixed at 33.33ms or 16.66ms.

That's it!

This is a frame rate that you need to the game.