1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-09 17:29:36 +00:00

Trusts Qt to supply a refresh rate, and handles retina <-> non-retina window transitions.

This commit is contained in:
Thomas Harte 2020-06-30 23:03:39 -04:00
parent b9e117cdcf
commit a096a09c72
3 changed files with 58 additions and 17 deletions

View File

@ -10,6 +10,7 @@
#define VSyncPredictor_hpp
#include "TimeTypes.hpp"
#include <cassert>
#include <cmath>
#include <cstdio>
@ -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.
};

View File

@ -2,6 +2,7 @@
#include <QDebug>
#include <QOpenGLContext>
#include <QScreen>
#include <QTimer>
#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) {

View File

@ -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();
};