Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 891
Michaelt LoL Avatar asked Dec 03 '19 21:12

Michaelt LoL


3 Answers

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.

like image 147
hacksoi Avatar answered Sep 26 '22 22:09

hacksoi


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
    }
}
like image 26
Ted Lyngmo Avatar answered Sep 22 '22 22:09

Ted Lyngmo


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://docs.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

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

like image 34
SuRGeoNix Avatar answered Sep 24 '22 22:09

SuRGeoNix