Reputation: 13
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
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
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