1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-24 12:30:17 +00:00

With some attempt at vsync prediction, seeks to smooth audio/video output.

There's plenty more work to do here, but hopefully it takes the issue immediately off the table.
This commit is contained in:
Thomas Harte 2020-06-14 19:26:56 -04:00
parent 405e9e7c68
commit 79833deeaf
6 changed files with 90 additions and 8 deletions

View File

@ -0,0 +1,63 @@
//
// 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"
namespace Time {
/*!
For platforms that provide no avenue into vsync tracking other than block-until-sync,
this class tracks: (i) how long frame draw takes; and (ii) the apparent frame period; in order
to suggest when you should next start drawing.
*/
class VSyncPredictor {
public:
void begin_redraw() {
redraw_begin_time_ = nanos_now();
}
void end_redraw() {
const auto redraw_length = nanos_now() - redraw_begin_time_;
redraw_period_ =
((redraw_period_ * 9) +
((redraw_length) * 1)) / 10;
}
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;
}
last_vsync_ = vsync_time;
}
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_;
}
private:
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;
};
}
#endif /* VSyncPredictor_hpp */

View File

@ -1317,6 +1317,7 @@
4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSROMFetcher.mm; sourceTree = "<group>"; };
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = "<group>"; };
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; };
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VSyncPredictor.hpp; sourceTree = "<group>"; };
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; };
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BCDTests.mm; sourceTree = "<group>"; };
@ -3855,8 +3856,9 @@
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */,
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
4B80214322EE7C3E00068002 /* JustInTime.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;

View File

@ -1,4 +1,4 @@
QT += core gui multimedia
QT += core gui multimedia quickwidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

View File

@ -47,7 +47,6 @@ void MainWindow::createActions() {
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
fileMenu->addAction(openAct);
}
void MainWindow::open() {
@ -143,7 +142,7 @@ void MainWindow::launchMachine() {
// Install audio output if required.
const auto audio_producer = machine->audio_producer();
if(audio_producer) {
static constexpr size_t samplesPerBuffer = 256;
static constexpr size_t samplesPerBuffer = 2048; // TODO: select this dynamically; it's very high.
const auto speaker = audio_producer->get_speaker();
if(speaker) {
const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();

View File

@ -13,15 +13,17 @@ void ScanTargetWidget::initializeGL() {
glClearColor(0.5, 0.5, 1.0, 1.0);
// Follow each swapped frame with an additional update.
connect(this, SIGNAL(frameSwapped()), this, SLOT(update()));
connect(this, &QOpenGLWidget::frameSwapped, this, &ScanTargetWidget::vsync);
// qDebug() << "share context: " << bool(context()->shareGroup());
}
void ScanTargetWidget::paintGL() {
glClear(GL_COLOR_BUFFER_BIT);
if(scanTarget) {
vsync_predictor_.begin_redraw();
scanTarget->update(width(), height());
scanTarget->draw(width(), height());
vsync_predictor_.end_redraw();
// static int64_t start = 0;
// static int frames = 0;
@ -34,15 +36,26 @@ void ScanTargetWidget::paintGL() {
}
}
void ScanTargetWidget::vsync() {
vsync_predictor_.announce_vsync();
const auto time_now = Time::nanos_now();
const auto delay_time = ((vsync_predictor_.suggested_draw_time() - time_now) / 1'000'000) - 5; // TODO: the extra 5 is a random guess.
if(delay_time > 0) {
QTimer::singleShot(delay_time, this, SLOT(repaint()));
} else {
repaint();
}
}
void ScanTargetWidget::resizeGL(int w, int h) {
glViewport(0,0,w,h);
glViewport(0,0,w,h);
}
Outputs::Display::OpenGL::ScanTarget *ScanTargetWidget::getScanTarget() {
makeCurrent();
if(!scanTarget) {
scanTarget = std::make_unique<Outputs::Display::OpenGL::ScanTarget>(defaultFramebufferObject());
QTimer::singleShot(500, this, SLOT(update())); // TODO: obviously this is nonsense.
}
return scanTarget.get();
}

View File

@ -4,6 +4,7 @@
#include <QOpenGLWidget>
#include "../../Outputs/OpenGL/ScanTarget.hpp"
#include "../../ClockReceiver/VSyncPredictor.hpp"
class ScanTargetWidget : public QOpenGLWidget
{
@ -20,8 +21,12 @@ class ScanTargetWidget : public QOpenGLWidget
private:
// This should be created only once there's an OpenGL context. So it
// can't be done at creation time.4
// can't be done at creation time.
std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> scanTarget;
Time::VSyncPredictor vsync_predictor_;
private slots:
void vsync();
};
#endif // SCANTARGETWIDGET_H