diff --git a/ClockReceiver/VSyncPredictor.hpp b/ClockReceiver/VSyncPredictor.hpp index d30f837bc..229a8d636 100644 --- a/ClockReceiver/VSyncPredictor.hpp +++ b/ClockReceiver/VSyncPredictor.hpp @@ -10,6 +10,7 @@ #define VSyncPredictor_hpp #include "TimeTypes.hpp" +#include #include #include @@ -44,11 +45,22 @@ class VSyncPredictor { frame rate. */ void announce_vsync() { - const auto vsync_time = nanos_now(); + const auto now = nanos_now(); + if(last_vsync_) { - vsync_period_.post(vsync_time - last_vsync_); + last_vsync_ += frame_duration_; + vsync_jitter_.post(last_vsync_ - now); + last_vsync_ = (last_vsync_ + now) >> 1; + } else { + last_vsync_ = now; } - last_vsync_ = vsync_time; + } + + /*! + Sets the frame rate for the target display. + */ + void set_frame_rate(float rate) { + frame_duration_ = Nanos(1'000'000'000.0f / rate); } /*! @@ -75,12 +87,14 @@ class VSyncPredictor { (if those figures are being supplied). */ Nanos suggested_draw_time() { - const auto mean = vsync_period_.mean() - redraw_period_.mean() - timer_jitter_.mean(); - const auto variance = vsync_period_.variance() + redraw_period_.variance() + timer_jitter_.variance(); + const auto mean = redraw_period_.mean() - timer_jitter_.mean() - vsync_jitter_.mean(); + const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance(); // Permit three standard deviations from the mean, to cover 99.9% of cases. const auto period = mean - Nanos(3.0f * sqrt(float(variance))); + assert(abs(period) < 10'000'000'000); + return last_vsync_ + period; } @@ -95,6 +109,7 @@ class VSyncPredictor { } void post(Time::Nanos value) { + assert(abs(value) < 10'000'000'000); // 10 seconds is a very liberal maximum. sum_ -= history_[write_pointer_]; sum_ += value; history_[write_pointer_] = value; @@ -110,10 +125,10 @@ class VSyncPredictor { // 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; + const auto difference = ((history_[c] * 128) - sum_) / 128; + variance += (difference * difference); } - return variance / (128 * 128 * 128); + return variance / 128; } private: @@ -124,8 +139,9 @@ class VSyncPredictor { Nanos redraw_begin_time_ = 0; Nanos last_vsync_ = 0; + Nanos frame_duration_ = 1'000'000'000 / 60; - VarianceCollector vsync_period_{1'000'000'000 / 60}; // 60Hz: seems like a good first guess. + VarianceCollector vsync_jitter_{0}; VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess. VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner. }; diff --git a/OSBindings/Qt/scantargetwidget.cpp b/OSBindings/Qt/scantargetwidget.cpp index 668cc58a7..b84b62f9e 100644 --- a/OSBindings/Qt/scantargetwidget.cpp +++ b/OSBindings/Qt/scantargetwidget.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "../../ClockReceiver/TimeTypes.hpp" @@ -18,10 +19,18 @@ void ScanTargetWidget::initializeGL() { void ScanTargetWidget::paintGL() { if(requested_redraw_time_) { - vsyncPredictor.add_timer_jitter(Time::nanos_now() - requested_redraw_time_); + const auto now = Time::nanos_now(); + vsyncPredictor.add_timer_jitter(now - requested_redraw_time_); requested_redraw_time_ = 0; } + const float newOutputScale = float(window()->screen()->devicePixelRatio()); + if(outputScale != newOutputScale) { + outputScale = newOutputScale; + resize(); + } + vsyncPredictor.set_frame_rate(float(window()->screen()->refreshRate())); + glClear(GL_COLOR_BUFFER_BIT); // Gmynastics ahoy: if a producer has been specified or previously connected then: @@ -52,11 +61,8 @@ void ScanTargetWidget::paintGL() { } vsyncPredictor.begin_redraw(); - const int devicePixelRatio = QPaintDevice::devicePixelRatio(); - const int outputWidth = width()*devicePixelRatio; - const int outputHeight = height()*devicePixelRatio; - scanTarget->update(outputWidth, outputHeight); - scanTarget->draw(outputWidth, outputHeight); + scanTarget->update(scaledWidth, scaledHeight); + scanTarget->draw(scaledWidth, scaledHeight); glFinish(); // Make sure all costs are properly accounted for in the vsync predictor. vsyncPredictor.end_redraw(); } @@ -79,8 +85,22 @@ void ScanTargetWidget::vsync() { } void ScanTargetWidget::resizeGL(int w, int h) { - const int devicePixelRatio = QPaintDevice::devicePixelRatio(); - glViewport(0, 0, w*devicePixelRatio, h*devicePixelRatio); + if(width != w || height != h) { + width = w; + height = h; + resize(); + } +} + +void ScanTargetWidget::resize() { + const int newScaledWidth = int(float(width) * outputScale); + const int newScaledHeight = int(float(height) * outputScale); + + if(newScaledWidth != scaledWidth || newScaledHeight != scaledHeight) { + scaledWidth = newScaledWidth; + scaledHeight = newScaledHeight; + glViewport(0, 0, scaledWidth, scaledHeight); + } } void ScanTargetWidget::setScanProducer(MachineTypes::ScanProducer *producer) { diff --git a/OSBindings/Qt/scantargetwidget.h b/OSBindings/Qt/scantargetwidget.h index 846370a77..d1df0d555 100644 --- a/OSBindings/Qt/scantargetwidget.h +++ b/OSBindings/Qt/scantargetwidget.h @@ -38,6 +38,11 @@ class ScanTargetWidget : public QOpenGLWidget void setDefaultClearColour(); + int width = 0, height = 0; + int scaledWidth = 0, scaledHeight = 0; + float outputScale = 1.0f; + void resize(); + private slots: void vsync(); };