2020-06-14 23:26:56 +00:00
|
|
|
//
|
|
|
|
// VSyncPredictor.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 14/06/2020.
|
|
|
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifndef VSyncPredictor_hpp
|
|
|
|
#define VSyncPredictor_hpp
|
|
|
|
|
|
|
|
#include "TimeTypes.hpp"
|
2020-06-24 02:59:12 +00:00
|
|
|
#include <cmath>
|
2020-06-26 03:59:44 +00:00
|
|
|
#include <cstdio>
|
2020-06-14 23:26:56 +00:00
|
|
|
|
|
|
|
namespace Time {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
For platforms that provide no avenue into vsync tracking other than block-until-sync,
|
2020-06-26 03:59:44 +00:00
|
|
|
this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and
|
|
|
|
(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
|
2020-06-14 23:26:56 +00:00
|
|
|
*/
|
|
|
|
class VSyncPredictor {
|
|
|
|
public:
|
2020-06-26 03:59:44 +00:00
|
|
|
/*!
|
|
|
|
Announces to the predictor that the work of producing an output frame has begun.
|
|
|
|
*/
|
2020-06-14 23:26:56 +00:00
|
|
|
void begin_redraw() {
|
|
|
|
redraw_begin_time_ = nanos_now();
|
|
|
|
}
|
|
|
|
|
2020-06-26 03:59:44 +00:00
|
|
|
/*!
|
|
|
|
Announces to the predictor that the work of producing an output frame has ended;
|
|
|
|
the predictor will use the amount of time between each begin/end pair to modify
|
|
|
|
its expectations as to how long it takes to draw a frame.
|
|
|
|
*/
|
2020-06-14 23:26:56 +00:00
|
|
|
void end_redraw() {
|
2020-06-24 02:59:12 +00:00
|
|
|
redraw_period_.post(nanos_now() - redraw_begin_time_);
|
2020-06-14 23:26:56 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 03:59:44 +00:00
|
|
|
/*!
|
|
|
|
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
|
|
|
|
machine calls retrace is now. The predictor uses these notifications to estimate output
|
|
|
|
frame rate.
|
|
|
|
*/
|
2020-06-14 23:26:56 +00:00
|
|
|
void announce_vsync() {
|
|
|
|
const auto vsync_time = nanos_now();
|
|
|
|
if(last_vsync_) {
|
2020-06-24 02:59:12 +00:00
|
|
|
vsync_period_.post(vsync_time - last_vsync_);
|
2020-06-14 23:26:56 +00:00
|
|
|
}
|
|
|
|
last_vsync_ = vsync_time;
|
|
|
|
}
|
|
|
|
|
2020-06-26 03:59:44 +00:00
|
|
|
/*!
|
|
|
|
Adds a record of how much jitter was experienced in scheduling; these values will be
|
|
|
|
factored into the @c suggested_draw_time if supplied.
|
|
|
|
|
|
|
|
A positive number means the timer occurred late. A negative number means it occurred early.
|
|
|
|
*/
|
|
|
|
void add_timer_jitter(Time::Nanos jitter) {
|
|
|
|
timer_jitter_.post(jitter);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Announces to the vsync predictor that output is now paused. This ends frame period
|
|
|
|
calculations until the next announce_vsync() restarts frame-length counting.
|
|
|
|
*/
|
2020-06-23 02:36:36 +00:00
|
|
|
void pause() {
|
|
|
|
last_vsync_ = 0;
|
|
|
|
}
|
|
|
|
|
2020-06-26 03:59:44 +00:00
|
|
|
/*!
|
|
|
|
@return The time at which redrawing should begin, given the predicted frame period, how
|
|
|
|
long it appears to take to draw a frame and how much jitter there is in scheduling
|
|
|
|
(if those figures are being supplied).
|
|
|
|
*/
|
2020-06-14 23:26:56 +00:00
|
|
|
Nanos suggested_draw_time() {
|
2020-06-26 03:59:44 +00:00
|
|
|
const auto mean = vsync_period_.mean() - redraw_period_.mean() - timer_jitter_.mean();
|
|
|
|
const auto variance = vsync_period_.variance() + redraw_period_.variance() + timer_jitter_.variance();
|
2020-06-24 02:59:12 +00:00
|
|
|
|
|
|
|
// Permit three standard deviations from the mean, to cover 99.9% of cases.
|
2020-06-26 03:59:44 +00:00
|
|
|
const auto period = mean - Nanos(3.0f * sqrt(float(variance)));
|
2020-06-24 02:59:12 +00:00
|
|
|
|
|
|
|
return last_vsync_ + period;
|
2020-06-14 23:26:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-06-24 02:59:12 +00:00
|
|
|
class VarianceCollector {
|
|
|
|
public:
|
|
|
|
VarianceCollector(Time::Nanos default_value) {
|
|
|
|
sum_ = default_value * 128;
|
|
|
|
for(int c = 0; c < 128; ++c) {
|
|
|
|
history_[c] = default_value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void post(Time::Nanos value) {
|
|
|
|
sum_ -= history_[write_pointer_];
|
|
|
|
sum_ += value;
|
|
|
|
history_[write_pointer_] = value;
|
|
|
|
write_pointer_ = (write_pointer_ + 1) & 127;
|
|
|
|
}
|
|
|
|
|
|
|
|
Time::Nanos mean() {
|
|
|
|
return sum_ / 128;
|
|
|
|
}
|
|
|
|
|
|
|
|
Time::Nanos variance() {
|
|
|
|
// I haven't yet come up with a better solution that calculating this
|
|
|
|
// in whole every time, given the way that the mean mutates.
|
|
|
|
Time::Nanos variance = 0;
|
|
|
|
for(int c = 0; c < 128; ++c) {
|
|
|
|
const auto difference = (history_[c] * 128) - sum_;
|
|
|
|
variance += difference * difference;
|
|
|
|
}
|
|
|
|
return variance / (128 * 128 * 128);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Time::Nanos sum_;
|
|
|
|
Time::Nanos history_[128];
|
|
|
|
size_t write_pointer_ = 0;
|
|
|
|
};
|
|
|
|
|
2020-06-14 23:26:56 +00:00
|
|
|
Nanos redraw_begin_time_ = 0;
|
|
|
|
Nanos last_vsync_ = 0;
|
|
|
|
|
2020-06-24 02:59:12 +00:00
|
|
|
VarianceCollector vsync_period_{1'000'000'000 / 60}; // 60Hz: seems like a good first guess.
|
|
|
|
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
|
2020-06-26 03:59:44 +00:00
|
|
|
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
|
2020-06-14 23:26:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* VSyncPredictor_hpp */
|