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:
parent
405e9e7c68
commit
79833deeaf
63
ClockReceiver/VSyncPredictor.hpp
Normal file
63
ClockReceiver/VSyncPredictor.hpp
Normal 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 */
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
QT += core gui multimedia
|
||||
QT += core gui multimedia quickwidgets
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user