Dmitru
Dmitru

Reputation: 13

Limit fps in loop c++

I have a simple fps limiter, it work:
Clock.h

#include <chrono>
using namespace std::chrono; 

class Clock 
{ 
    high_resolution_clock::time_point startTime;
public:
    Clock(); 
    duration<double> restart();
};

Clock.cpp


#include "Clock.h"

Clock::Clock()
: startTime(high_resolution_clock::now())
{

}

duration<double> Clock::restart()
{
    high_resolution_clock::time_point now = high_resolution_clock::now();
    duration<double> elapsed = now - startTime;
    startTime = now;

    return elapsed;
}

main.cpp

    duration<double> stepInterval(1.f / 60.f);
    duration<double> deltaTime(0);

    Clock clock;
    while(isRunning)
    {
        deltaTime += clock.restart();
        if(deltaTime > stepInterval)
        {
            std::cout << deltaTime.count() * 1000 << std::endl;
            deltaTime = seconds(0);
        }
    }

But in this case CPU has a high load. I tried another solutions with std::this_thread::sleep_for, but they does not work with little interval(like 16 ms). Any idea about implementing std::this_thread::sleep_for?

Upvotes: 1

Views: 1938

Answers (2)

Howard Hinnant
Howard Hinnant

Reputation: 218700

  • Don't use sleep_for. Use sleep_until instead. This will average your drift from the desired frame rate out to zero. This is done by sleeping until the start of the current plus the desired frame duration.

  • Use system_clock so that your frame rate stays accurate over a long period of time. No clock is perfect. But system_clock will occasionally be adjusted by small amounts to the correct time. This will have the impact of your frame rate being true to the desired rate over the length of a movie. Don't follow this advice if you're concerned about a process with admin privileges adjusting the system clock by gross amounts. In that case use steady_clock instead.

  • Create a 1/60 s duration type to eliminate floating point round off error.

  • Eliminate conversion constants like 1000 in your code to reduce the possibility of logic errors.

  • In your sleep_until, wake up a little early and then busy-loop until the desired time. You'll have to experiment with how much early. Too early and you burn too much CPU (and battery). Too little, and sleep_until occasionally wakes up too late when the CPU is under a high load.

Taken altogether this might look like:

template <class Clock, class Duration>
void
sleep_until(std::chrono::time_point<Clock, Duration> tp)
{
    using namespace std::chrono;
    std::this_thread::sleep_until(tp - 10us);
    while (tp >= Clock::now())
        ;
}

int
main()
{
    using namespace std;
    using namespace std::chrono;

    using framerate = duration<int, ratio<1, 60>>;
    auto tp = system_clock::now() + framerate{1};

    while (isRunning)
    {
        // do drawing
        ::sleep_until(tp);
        tp += framerate{1};
    }
}

Here's a more detailed sketch of the idea that includes "drawing" the current and average frame rate on each frame. It uses C++17's chrono::round function and this free, open-source C++20 chrono preview library for the "drawing".

#include "date/date.h"
#include <chrono>
#include <iostream>
#include <thread>

template <class Clock, class Duration>
void
sleep_until(std::chrono::time_point<Clock, Duration> tp)
{
    using namespace std::chrono;
    std::this_thread::sleep_until(tp - 10us);
    while (tp >= Clock::now())
        ;
}

int
main()
{
    using namespace std;
    using namespace std::chrono;

    using framerate = duration<int, ratio<1, 60>>;
    auto prev = system_clock::now();
    auto next = prev + framerate{1};
    int N = 0;
    system_clock::duration sum{0};

    while (true)
    {
        ::sleep_until(next);
        next += framerate{1};

        // do drawing
        using namespace date;
        auto now = system_clock::now();
        sum += now - prev;
        ++N;
        cerr << "This frame: " << round<milliseconds>(now-prev)
             << "  average: " << round<milliseconds>(sum/N) << '\n';
        prev = now;
    }
}

Output for me:

...
This frame: 16ms  average: 17ms
This frame: 15ms  average: 17ms
This frame: 19ms  average: 17ms
This frame: 16ms  average: 17ms
This frame: 17ms  average: 17ms
This frame: 16ms  average: 17ms
This frame: 17ms  average: 17ms
This frame: 17ms  average: 17ms
This frame: 16ms  average: 17ms
This frame: 16ms  average: 17ms
This frame: 16ms  average: 17ms
This frame: 18ms  average: 17ms
This frame: 16ms  average: 17ms

Upvotes: 4

Ali Askari
Ali Askari

Reputation: 535

This code may help you. It worked well for me (good precision):


#include <iostream>
#include <thread>
#include <chrono>
    
int main()
{
    std::chrono::high_resolution_clock::time_point prev = 
        std::chrono::high_resolution_clock::now();
    std::chrono::high_resolution_clock::time_point current = 
        std::chrono::high_resolution_clock::now();
    
    int i = 0; 
    
    while (true) {
    
        current = std::chrono::high_resolution_clock::now();
    
        if (std::chrono::duration_cast<std::chrono::microseconds>(current - prev).count() >= 16000) {
            prev = current;
            i++;
            std::cout << i << std::endl;
        } else {
            std::this_thread::sleep_for(std::chrono::microseconds(1));
        }  
    
    }  
    
    return 0; 
}
```    ‍‍‍‍‍‍‍

Upvotes: 0

Related Questions