diff --git a/ClockReceiver/VSyncPredictor.hpp b/ClockReceiver/VSyncPredictor.hpp index f446357bc..f133762c1 100644 --- a/ClockReceiver/VSyncPredictor.hpp +++ b/ClockReceiver/VSyncPredictor.hpp @@ -10,6 +10,7 @@ #define VSyncPredictor_hpp #include "TimeTypes.hpp" +#include namespace Time { @@ -25,19 +26,13 @@ class VSyncPredictor { } void end_redraw() { - const auto redraw_length = nanos_now() - redraw_begin_time_; - redraw_period_ = - ((redraw_period_ * 9) + - ((redraw_length) * 1)) / 10; + redraw_period_.post(nanos_now() - redraw_begin_time_); } void announce_vsync() { const auto vsync_time = nanos_now(); if(last_vsync_) { - // Use an IIR to try to converge on frame times. - vsync_period_ = - ((vsync_period_ * 9) + - ((vsync_time - last_vsync_) * 1)) / 10; + vsync_period_.post(vsync_time - last_vsync_); } last_vsync_ = vsync_time; } @@ -47,19 +42,58 @@ class VSyncPredictor { } Nanos suggested_draw_time() { - // TODO: this is a very simple version of how this calculation - // should be made. It's tracking the average amount of time these - // things take, therefore will often be wrong. Deviations need to - // be accounted for. - return last_vsync_ + vsync_period_ - redraw_period_; + const auto mean = (vsync_period_.mean() - redraw_period_.mean()) / 1; + const auto variance = (vsync_period_.variance() + redraw_period_.variance()) / 1; + + // Permit three standard deviations from the mean, to cover 99.9% of cases. + const auto period = mean + Nanos(3.0f * sqrt(float(variance))); + + return last_vsync_ + period; } private: + 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; + }; + Nanos redraw_begin_time_ = 0; Nanos last_vsync_ = 0; - Nanos vsync_period_ = 1'000'000'000 / 60; // Seems like a good first guess. - Nanos redraw_period_ = 0; + 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. }; } diff --git a/OSBindings/Qt/scantargetwidget.cpp b/OSBindings/Qt/scantargetwidget.cpp index 0a2db83b9..5d5ebc1b1 100644 --- a/OSBindings/Qt/scantargetwidget.cpp +++ b/OSBindings/Qt/scantargetwidget.cpp @@ -49,6 +49,7 @@ void ScanTargetWidget::paintGL() { vsyncPredictor.begin_redraw(); scanTarget->update(width(), height()); scanTarget->draw(width(), height()); + glFinish(); // Make sure all costs are properly accounted for in the vsync predictor. vsyncPredictor.end_redraw(); } }