0

I want to write a Game Loop and measure the time it took to render. At the end of the loop I want the Thread to sleep for a certain amount of time to not exhaust the CPU. Currently I want to run the loop 60 times a second.

Here is the minimal example I came up with:

#include <iostream>
#include <Windows.h>

class Clock {
public:
    Clock() : m_Start(), m_Elapsed(0) {}

    double AbsoluteTime() {
        LARGE_INTEGER current_time;
        QueryPerformanceCounter(&current_time);

        LARGE_INTEGER frequency;
        QueryPerformanceFrequency(&frequency);

        return static_cast<double>(current_time.QuadPart) / frequency.QuadPart;
    }

    void Start() {
        m_Start = AbsoluteTime();
    }

    double GetElapsed() {
        double current_time = AbsoluteTime();

        m_Elapsed = current_time - m_Start;
        return m_Elapsed;
    }

private:
    double m_Start;
    double m_Elapsed;
};

int main() {
    Clock clock;
    Clock fpsClock;
    int frameCount = 0;

    while (true) {
        clock.Start();

        // Do some rendering stuff....

        double elapsed = clock.GetElapsed();
        double delay = (1.0 / 60.0) - elapsed;

        if (delay > 0) {
            Sleep(static_cast<DWORD>(delay * 1000));
        }

        frameCount++;

        if (fpsClock.GetElapsed() > 1.0) {
            std::cout << "FPS: " << frameCount << std::endl;
            fpsClock.Start();
            frameCount = 0;
        }
    }

    return 0;
}

However when executing this code all I get is 33 Frames printing every second. I am really not sure why this happens. If I dont use the Sleep method at all I get over 4600 frames a second. So it cant be a performance issue. Has anybody has encountered this before or knows how to fix it?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Nicky
  • 121
  • 1
  • 7
  • The default timer resolution, meaning the minimum time you can sleep, is around 15ms. You can use `timeBeginPeriod` to attempt to modify this down to 1ms, but there are no guarantees with `Sleep`. It's a minimum, not a maximum. – Retired Ninja May 01 '23 at 18:30
  • One thing that immediately caught my eye is `clock.Start()` in your loop. You do __not__ want to start and stop the clock each frame. Instead, the new frame-end-target should be calculated using the previous frame-end-target time and the desired framerate. – tkausl May 01 '23 at 18:33
  • 3
    [Can i expect anything from Windows-API's Sleep function?](https://stackoverflow.com/questions/61655859/can-i-expect-anything-from-windows-apis-sleep-function) [Win32 Sleep() accuracy in game loop](https://stackoverflow.com/questions/48873395/win32-sleep-accuracy-in-game-loop) and many others have more details on this. – Retired Ninja May 01 '23 at 18:34
  • Also note, the [`QueryPerformanceFrequency()`](https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency) value never changes: "*The frequency of the performance counter is fixed at system boot and is consistent across all processors. Therefore, **the frequency need only be queried upon application initialization, and the result can be cached**.*" That will shave off a few CPU cycles avoiding an unnecessary kernel call per loop iteration. – Remy Lebeau May 01 '23 at 20:46
  • Ok thank you those are all helpful tips. – Nicky May 31 '23 at 09:07

2 Answers2

1

The default resolution of Sleep is limited as they said. The program outputs between 33 and 35 FPS for me. After setting the minimum resolution with timeBeginPeriod, the program outputs between 60 and 62 FPS. It's important that Sleep is not designed for absolute timing.

enter image description here

YangXiaoPo-MSFT
  • 1,589
  • 1
  • 4
  • 22
0

The Sleep function has limited resolution:

The system clock "ticks" at a constant rate. If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time. If dwMilliseconds is greater than one tick but less than two, the wait can be anywhere between one and two ticks, and so on. To increase the accuracy of the sleep interval, call the timeGetDevCaps function to determine the supported minimum timer resolution and the timeBeginPeriod function to set the timer resolution to its minimum. Use caution when calling timeBeginPeriod, as frequent calls can significantly affect the system clock, system power usage, and the scheduler.

I would recommend to use SetWaitableTimer and SleepEx functions.

Damir Tenishev
  • 1,275
  • 3
  • 14