Michaelt LoL
Michaelt LoL

Reputation: 461

C++ How to make precise frame rate limit?

I'm trying to create a game using C++ and I want to create limit for fps but I always get more or less fps than I want. When I look at games that have fps limit it's always precise framerate. Tried using Sleep() std::this_thread::sleep_for(sleep_until). For example Sleep(0.01-deltaTime) to get 100 fps but ended up with +-90fps. How do these games handle fps so precisely when any sleeping isn't precise? I know I can use infinite loop that just checks if time passed but it's using full power of CPU but I want to decrease CPU usage by this limit without VSync.

Upvotes: 6

Views: 10622

Answers (6)

hacksoi
hacksoi

Reputation: 1397

Yes, sleep is usually inaccurate. That is why you sleep for less than the actual time it takes to finish the frame. For example, if you need 5 more milliseconds to finish the frame, then sleep for 4 milliseconds. After the sleep, simply do a spin-lock for the rest of the frame. Something like

float TimeRemaining = NextFrameTime - GetCurrentTime();
Sleep(ConvertToMilliseconds(TimeRemaining) - 1);
while (GetCurrentTime() < NextFrameTime) {};

Edit: as stated in another answer, timeBeginPeriod() should be called to increase the accuracy of Sleep(). Also, from what I've read, Windows will automatically call timeEndPeriod() when your process exits if you don't before then.

Upvotes: 4

SuRGeoNix
SuRGeoNix

Reputation: 617

The accepted answer sounds really bad. It would not be accurate and it would burn the CPU!

Thread.Sleep is not accurate because you have to tell it to be accurate (by default is about 15ms accurate - means that if you tell it to sleep 1ms it could sleep 15ms).

You can do this with Win32 API call to timeBeginPeriod & timeEndPeriod functions.

Check MSDN for more details -> https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

(I would comment on the accepted answer but still not having 50 reputation)

Upvotes: 5

Kaldaien
Kaldaien

Reputation: 66

Be very careful when implementing any wait that is based on scheduler sleep.

Most OS schedulers have higher latency turn-around for a wait with no well-defined interval or signal to bring the thread back into the ready-to-run state.

Sleeping isn't inaccurate per-se, you're just approaching the problem all wrong. If you have access to something like DXGI's Waitable Swapchain, you synchronize to the DWM's present queue and get really reliable low-latency timing.

You don't need to busy-wait to get accurate timing, a waitable timer will give you a sync object to reschedule your thread.

Whatever you do, do not use the currently accepted answer in production code. There's an edge case here you WANT TO AVOID, where Sleep (0) does not yield CPU time to higher priority threads. I've seen so many game devs try Sleep (0) and it's going to cause you major problems.

Upvotes: 3

Ripi2
Ripi2

Reputation: 7198

Use a timer.
Some OS's can provide special functions. For example, for Windows you can use SetTimer and handle its WM_TIMER messages.

Then calculate the frequency of the timer. 100 fps means that the timer must fire an event each 0.01 seconds.

At the event handler for this timer-event you can do your rendering.

In case the rendering is slower than the desired frequency then use a syncro flag OpenGL sync and discard the timer-event if the previous rendering is not complete.

Upvotes: 0

Ted Lyngmo
Ted Lyngmo

Reputation: 118077

You could record the time point when you start, add a fixed duration to it and sleep until the calculated time point occurs at the end (or beginning) of every loop. Example:

#include <chrono>
#include <iostream>
#include <ratio>
#include <thread>

template<std::intmax_t FPS>
class frame_rater {
public:
    frame_rater() :                 // initialize the object keeping the pace
        time_between_frames{1},     // std::ratio<1, FPS> seconds
        tp{std::chrono::steady_clock::now()}
    {}

    void sleep() {
        // add to time point
        tp += time_between_frames;

        // and sleep until that time point
        std::this_thread::sleep_until(tp);
    }

private:
    // a duration with a length of 1/FPS seconds
    std::chrono::duration<double, std::ratio<1, FPS>> time_between_frames;

    // the time point we'll add to in every loop
    std::chrono::time_point<std::chrono::steady_clock, decltype(time_between_frames)> tp;
};

// this should print ~10 times per second pretty accurately
int main() {
    frame_rater<10> fr; // 10 FPS
    while(true) {
        std::cout << "Hello world\n";
        fr.sleep();                   // let it sleep any time remaining
    }
}

Upvotes: 7

Terens Tare
Terens Tare

Reputation: 157

You may set a const fps variable to your desired frame rate, then you can update your game if the elapsed time from last update is equal or more than 1 / desired_fps. This will probably work. Example:

const /*or constexpr*/ int fps{60};

// then at update loop.
while(running)
{
    // update the game timer.
    timer->update();

    // check for any events.

    if(timer->ElapsedTime() >= 1 / fps)
    {
        // do your updates and THEN renderer.
    }
}

Upvotes: -1

Related Questions