Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rryan committed Jan 2, 2019
1 parent 73f9532 commit 7d0990b
Show file tree
Hide file tree
Showing 31 changed files with 209 additions and 192 deletions.
1 change: 1 addition & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,7 @@ def sources(self, build):
"src/waveform/renderers/glslwaveformrenderersignal.cpp",
"src/waveform/renderers/glvsynctestrenderer.cpp",

"src/waveform/widgets/baseqopenglwidget.cpp",
"src/waveform/widgets/waveformwidgetabstract.cpp",
"src/waveform/widgets/emptywaveformwidget.cpp",
"src/waveform/widgets/softwarewaveformwidget.cpp",
Expand Down
4 changes: 1 addition & 3 deletions src/skin/legacyskinparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1177,9 +1177,7 @@ QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) {

auto waveformWidgetFactory = WaveformWidgetFactory::instance();
connect(waveformWidgetFactory, SIGNAL(renderSpinnies()),
spinny, SLOT(render()));
connect(waveformWidgetFactory, SIGNAL(swapSpinnies()),
spinny, SLOT(swap()));
spinny, SLOT(slotShouldRenderOnNextTick()));
connect(spinny, SIGNAL(trackDropped(QString, QString)),
m_pPlayerManager, SLOT(slotLoadToPlayer(QString, QString)));

Expand Down
4 changes: 2 additions & 2 deletions src/waveform/renderers/waveformwidgetrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ bool WaveformWidgetRenderer::init() {
return true;
}

void WaveformWidgetRenderer::onPreRender(VSyncThread* vsyncThread) {
void WaveformWidgetRenderer::onPreRender(mixxx::Duration estimatedTimeUntilSwap) {
// For a valid track to render we need
m_trackSamples = m_pTrackSamplesControlObject->get();
if (m_trackSamples <= 0.0) {
Expand Down Expand Up @@ -140,7 +140,7 @@ void WaveformWidgetRenderer::onPreRender(VSyncThread* vsyncThread) {
}


double truePlayPos = m_visualPlayPosition->getAtNextVSync(vsyncThread);
double truePlayPos = m_visualPlayPosition->getAtNextSwap(estimatedTimeUntilSwap);
// m_playPos = -1 happens, when a new track is in buffer but m_visualPlayPosition was not updated

if (m_audioSamplePerPixel && truePlayPos != -1) {
Expand Down
4 changes: 2 additions & 2 deletions src/waveform/renderers/waveformwidgetrenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
#include "util/class.h"
#include "waveform/renderers/waveformrendererabstract.h"
#include "waveform/renderers/waveformsignalcolors.h"
#include "util/duration.h"
#include "util/performancetimer.h"

//#define WAVEFORMWIDGETRENDERER_DEBUG

class Track;
class ControlProxy;
class VisualPlayPosition;
class VSyncThread;

class WaveformWidgetRenderer {
public:
Expand All @@ -34,7 +34,7 @@ class WaveformWidgetRenderer {
virtual bool onInit() {return true;}

void setup(const QDomNode& node, const SkinContext& context);
void onPreRender(VSyncThread* vsyncThread);
void onPreRender(mixxx::Duration estimatedTimeUntilSwap);
void draw(QPainter* painter, QPaintEvent* event);

inline const char* getGroup() const { return m_group;}
Expand Down
37 changes: 26 additions & 11 deletions src/waveform/visualplayposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,39 +50,54 @@ void VisualPlayPosition::set(double playPos, double rate,
m_valid = true;
}

double VisualPlayPosition::getAtNextVSync(VSyncThread* vsyncThread) {
double VisualPlayPosition::getAtNextSwap(mixxx::Duration estimatedTimeUntilSwap) {
//static double testPos = 0;
//testPos += 0.000017759; //0.000016608; // 1.46257e-05;
//return testPos;

if (m_valid) {
VisualPlayPositionData data = m_data.getValue();
int refToVSync = vsyncThread->fromTimerToNextSyncMicros(data.m_referenceTime);
int offset = refToVSync - data.m_callbackEntrytoDac;
offset = math_min(offset, m_audioBufferMicros * kMaxOffsetBufferCnt);
int microsUntilSwap = estimatedTimeUntilSwap.toIntegerMicros();
int microsUntilDac = data.m_callbackEntrytoDac - data.m_referenceTime.elapsed().toIntegerMicros();

// playPos will be played in microsUntilDac seconds. If swap comes
// first, offset will be negative so that playpos goes backward.
int offsetMicros = microsUntilSwap - microsUntilDac;
offsetMicros = math_clamp(offsetMicros,
-m_audioBufferMicros * kMaxOffsetBufferCnt,
m_audioBufferMicros * kMaxOffsetBufferCnt);
double playPos = data.m_enginePlayPos; // load playPos for the first sample in Buffer
// add the offset for the position of the sample that will be transferred to the DAC
// When the next display frame is displayed
playPos += data.m_positionStep * offset * data.m_rate / m_audioBufferMicros;
//qDebug() << "playPos" << playPos << offset;
// TODO(rryan): m_audioBufferMicros is not accurate for this purpose. We
// should use the PortAudio reported actual latency instead of Mixxx's
// configured audio buffer size.
playPos += data.m_positionStep * offsetMicros * data.m_rate / m_audioBufferMicros;
return playPos;
}
return -1;
}

void VisualPlayPosition::getPlaySlipAt(int fromNowMicros, double* playPosition, double* slipPosition) {
void VisualPlayPosition::getPlaySlipAt(mixxx::Duration estimatedTimeUntilSwap,
double* playPosition, double* slipPosition) {
//static double testPos = 0;
//testPos += 0.000017759; //0.000016608; // 1.46257e-05;
//return testPos;

if (m_valid) {
VisualPlayPositionData data = m_data.getValue();
int elapsed = data.m_referenceTime.elapsed().toIntegerMicros();
int dacFromNow = elapsed - data.m_callbackEntrytoDac;
int offset = dacFromNow - fromNowMicros;
offset = math_min(offset, m_audioBufferMicros * kMaxOffsetBufferCnt);
int microsUntilSwap = estimatedTimeUntilSwap.toIntegerMicros();
int microsUntilDac = data.m_callbackEntrytoDac - data.m_referenceTime.elapsed().toIntegerMicros();
int offsetMicros = microsUntilSwap - microsUntilDac;
offsetMicros = math_clamp(offsetMicros,
-m_audioBufferMicros * kMaxOffsetBufferCnt,
m_audioBufferMicros * kMaxOffsetBufferCnt);
double playPos = data.m_enginePlayPos; // load playPos for the first sample in Buffer
playPos += data.m_positionStep * offset * data.m_rate / m_audioBufferMicros;
// TODO(rryan): m_audioBufferMicros is not accurate for this purpose. We
// should use the PortAudio reported actual latency instead of Mixxx's
// configured audio buffer size.
playPos += data.m_positionStep * offsetMicros * data.m_rate / m_audioBufferMicros;
*playPosition = playPos;
*slipPosition = data.m_pSlipPosition;
}
Expand Down
9 changes: 5 additions & 4 deletions src/waveform/visualplayposition.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include <QMap>
#include <QAtomicPointer>

#include "util/duration.h"
#include "util/performancetimer.h"
#include "control/controlvalue.h"

class ControlProxy;
class VSyncThread;

// This class is for synchronizing the sound device DAC time with the waveforms, displayed on the
// graphic device, using the CPU time
Expand All @@ -23,7 +23,7 @@ class VSyncThread;
// ^Start m_timeInfoTime |
// |
// GPU: ---------|----------------------------------- |--|-------------------------------
// ^Render Waveform sample X | ^VSync (New waveform is displayed
// ^Render Waveform sample X | ^Swap (New waveform is displayed
// by use usFromTimerToNextSync ^swap Buffer

class VisualPlayPositionData {
Expand All @@ -46,8 +46,9 @@ class VisualPlayPosition : public QObject {
// WARNING: Not thread safe. This function must be called only from the
// engine thread.
void set(double playPos, double rate, double positionStep, double pSlipPosition);
double getAtNextVSync(VSyncThread* vsyncThread);
void getPlaySlipAt(int usFromNow, double* playPosition, double* slipPosition);
double getAtNextSwap(mixxx::Duration estimatedTimeUntilSwap);
void getPlaySlipAt(mixxx::Duration estimatedTimeUntilSwap,
double* playPosition, double* slipPosition);
double getEnginePlayPos();

// WARNING: Not thread safe. This function must only be called from the main
Expand Down
52 changes: 10 additions & 42 deletions src/waveform/vsyncthread.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#include <QThread>
#include <QGLFormat>
#include <QTime>
#include <QtDebug>
#include <QTime>
#include <QOpenGLContext>

#include "vsyncthread.h"
#include "util/performancetimer.h"
#include "util/event.h"
#include "util/counter.h"
#include "util/math.h"
#include "util/time.h"
#include "waveform/guitick.h"

#if defined(__APPLE__)
Expand Down Expand Up @@ -62,15 +63,11 @@ void VSyncThread::run() {
m_semaVsyncSlot.acquire();
Event::end("VsyncThread vsync render");

Event::start("VsyncThread vsync swap");
emit(vsyncSwap()); // swaps the new waveform to front
m_semaVsyncSlot.acquire();
Event::end("VsyncThread vsync swap");

m_timer.restart();
m_waitToSwapMicros = 1000;
usleep(1000);
} else { // if (m_vSyncMode == ST_TIMER) {
m_timer.restart();

Event::start("VsyncThread vsync render");
emit(vsyncRender()); // renders the new waveform.
Expand All @@ -80,47 +77,18 @@ void VSyncThread::run() {
m_semaVsyncSlot.acquire();
Event::end("VsyncThread vsync render");

// qDebug() << "ST_TIMER " << lastMicros << restMicros;
int remainingForSwap = m_waitToSwapMicros - static_cast<int>(
m_timer.elapsed().toIntegerMicros());
// waiting for interval by sleep
if (remainingForSwap > 100) {
Event::start("VsyncThread usleep for VSync");
usleep(remainingForSwap);
Event::end("VsyncThread usleep for VSync");
}

Event::start("VsyncThread vsync swap");
// swaps the new waveform to front in case of gl-wf
emit(vsyncSwap());

// wait until swap occurred. It might be delayed due to driver vSync
// settings.
m_semaVsyncSlot.acquire();
Event::end("VsyncThread vsync swap");

// <- Assume we are VSynced here ->
int lastSwapTime = static_cast<int>(m_timer.restart().toIntegerMicros());
if (remainingForSwap < 0) {
// Our swapping call was already delayed
// The real swap might happens on the following VSync, depending on driver settings
m_droppedFrames++; // Count as Real Time Error
droppedFrames.increment();
int elapsed = m_timer.restart().toIntegerMicros();
int sleepTimeMicros = m_syncIntervalTimeMicros - elapsed;
//qDebug() << "VsyncThread sleepTimeMicros" << sleepTimeMicros;
if (sleepTimeMicros > 100) {
usleep(sleepTimeMicros);
} else if (sleepTimeMicros < 0) {
m_droppedFrames++;
}
// try to stay in right intervals
m_waitToSwapMicros = m_syncIntervalTimeMicros +
((m_waitToSwapMicros - lastSwapTime) % m_syncIntervalTimeMicros);
}
}
}


// static
void VSyncThread::swapGl(QOpenGLWidget* glw, int index) {
Q_UNUSED(index);
// TODO: glw->swapBuffers() used to be called here, but it is not offered by the QOpenGLWidget
}

int VSyncThread::elapsed() {
return static_cast<int>(m_timer.elapsed().toIntegerMicros());
}
Expand Down
5 changes: 2 additions & 3 deletions src/waveform/vsyncthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

#include "util/performancetimer.h"

// TODO(rryan): Rename to RenderThread or something, or possibly replace with a
// generic BlockingThreadTimer class.
class VSyncThread : public QThread {
Q_OBJECT
public:
Expand All @@ -40,8 +42,6 @@ class VSyncThread : public QThread {
ST_COUNT // Dummy Type at last, counting possible types
};

static void swapGl(QOpenGLWidget* glw, int index);

VSyncThread(QObject* pParent);
~VSyncThread();

Expand All @@ -59,7 +59,6 @@ class VSyncThread : public QThread {

signals:
void vsyncRender();
void vsyncSwap();

private:
bool m_bDoRendering;
Expand Down
53 changes: 3 additions & 50 deletions src/waveform/waveformwidgetfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "waveform/vsyncthread.h"
#include "util/cmdlineargs.h"
#include "util/performancetimer.h"
#include "util/time.h"
#include "util/timer.h"
#include "util/math.h"

Expand Down Expand Up @@ -463,30 +464,17 @@ void WaveformWidgetFactory::render() {
if (!m_skipRender) {
if (m_type) { // no regular updates for an empty waveform
// next rendered frame is displayed after next buffer swap and than after VSync
QVarLengthArray<bool, 10> shouldRenderWaveforms(m_waveformWidgetHolders.size());
for (int i = 0; i < m_waveformWidgetHolders.size(); i++) {
WaveformWidgetAbstract* pWaveformWidget = m_waveformWidgetHolders[i].m_waveformWidget;
// Don't bother doing the pre-render work if we aren't going to
// render this widget.
bool shouldRender = shouldRenderWaveform(pWaveformWidget);
shouldRenderWaveforms[i] = shouldRender;
if (!shouldRender) {
continue;
}
// Calculate play position for the new Frame in following run
pWaveformWidget->preRender(m_vsyncThread);
}
//qDebug() << "prerender" << m_vsyncThread->elapsed();

// It may happen that there is an artificially delayed due to
// anti tearing driver settings
// all render commands are delayed until the swap from the previous run is executed
for (int i = 0; i < m_waveformWidgetHolders.size(); i++) {
WaveformWidgetAbstract* pWaveformWidget = m_waveformWidgetHolders[i].m_waveformWidget;
if (!shouldRenderWaveforms[i]) {
if (!shouldRenderWaveform(pWaveformWidget)) {
continue;
}
pWaveformWidget->render();
pWaveformWidget->renderOnNextTick();
//qDebug() << "render" << i << m_vsyncThread->elapsed();
}
}
Expand Down Expand Up @@ -517,39 +505,6 @@ void WaveformWidgetFactory::render() {
m_vsyncThread->vsyncSlotFinished();
}

void WaveformWidgetFactory::swap() {
ScopedTimer t("WaveformWidgetFactory::swap() %1waveforms", m_waveformWidgetHolders.size());

// Do this in an extra slot to be sure to hit the desired interval
if (!m_skipRender) {
if (m_type) { // no regular updates for an empty waveform
// Show rendered buffer from last render() run
//qDebug() << "swap() start" << m_vsyncThread->elapsed();
for (int i = 0; i < m_waveformWidgetHolders.size(); i++) {
WaveformWidgetAbstract* pWaveformWidget = m_waveformWidgetHolders[i].m_waveformWidget;

// Don't swap invalid / invisible widgets or widgets with an
// unexposed window. Prevents continuous log spew of
// "QOpenGLContext::swapBuffers() called with non-exposed
// window, behavior is undefined" on Qt5. See Bug #1779487.
if (!shouldRenderWaveform(pWaveformWidget)) {
continue;
}
auto glw = dynamic_cast<QOpenGLWidget*>(pWaveformWidget->getWidget());
if (glw != nullptr) {
VSyncThread::swapGl(glw, i);
}
//qDebug() << "swap x" << m_vsyncThread->elapsed();
}
}
// WSpinnys are also double-buffered QOpenGLWidgets, like all the
// waveform renderers. Swap all the WSpinny widgets now.
emit(swapSpinnies());
}
//qDebug() << "swap end" << m_vsyncThread->elapsed();
m_vsyncThread->vsyncSlotFinished();
}

WaveformWidgetType::Type WaveformWidgetFactory::autoChooseWidgetType() const {
//default selection
if (m_openGLAvailable) {
Expand Down Expand Up @@ -757,8 +712,6 @@ void WaveformWidgetFactory::startVSync(GuiTick* pGuiTick) {

connect(m_vsyncThread, SIGNAL(vsyncRender()),
this, SLOT(render()));
connect(m_vsyncThread, SIGNAL(vsyncSwap()),
this, SLOT(swap()));
}

void WaveformWidgetFactory::getAvailableVSyncTypes(QList<QPair<int, QString > >* pList) {
Expand Down
2 changes: 0 additions & 2 deletions src/waveform/waveformwidgetfactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ class WaveformWidgetFactory : public QObject, public Singleton<WaveformWidgetFac
void waveformUpdateTick();
void waveformMeasured(float frameRate, int droppedFrames);
void renderSpinnies();
void swapSpinnies();

protected:
WaveformWidgetFactory();
Expand All @@ -134,7 +133,6 @@ class WaveformWidgetFactory : public QObject, public Singleton<WaveformWidgetFac

private slots:
void render();
void swap();

private:
void evaluateWidgets();
Expand Down
Loading

0 comments on commit 7d0990b

Please sign in to comment.