diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2016-06-30 14:27:42 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2016-07-01 11:37:01 +0000 |
commit | c3561cdb36e538132ea07491ab6418294117b8f2 (patch) | |
tree | ee9dee6eb951d66eeaecd8c97b5c908ad727c7b3 /src/plugins | |
parent | da839643397dda062cf7371e01fc1c990b7cf7c3 (diff) |
D3D12: Advance anims in sync with vsync in the RL
Make the default render loop behave like the 'windows' one does with
OpenGL: advance animations manually after each render step. This provides
much better results than the previous approach (i.e. 'basic') where the
animations became quite jerky with heavier workloads.
Change-Id: Ic933e136479d2a04036af15212669027ef2408c3
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp | 212 | ||||
-rw-r--r-- | src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h | 17 |
2 files changed, 185 insertions, 44 deletions
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp index d845b65c28..551133e7bb 100644 --- a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp +++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop.cpp @@ -44,7 +44,10 @@ #include "qsgd3d12shadereffectnode_p.h" #include <private/qquickwindow_p.h> #include <private/qquickprofiler_p.h> +#include <private/qquickanimatorcontroller_p.h> #include <QElapsedTimer> +#include <QGuiApplication> +#include <QScreen> QT_BEGIN_NAMESPACE @@ -57,12 +60,29 @@ QT_BEGIN_NAMESPACE DECLARE_DEBUG_VAR(loop) DECLARE_DEBUG_VAR(time) + +// This render loop operates on the gui (main) thread. +// Conceptually it matches the OpenGL 'windows' render loop. + +static inline int qsgrl_animation_interval() +{ + const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0; + return refreshRate < 1 ? 16 : int(1000 / refreshRate); +} + QSGD3D12RenderLoop::QSGD3D12RenderLoop() { if (Q_UNLIKELY(debug_loop())) qDebug("new d3d12 render loop"); sg = new QSGD3D12Context; + + m_anims = sg->createAnimationDriver(this); + connect(m_anims, &QAnimationDriver::started, this, &QSGD3D12RenderLoop::onAnimationStarted); + connect(m_anims, &QAnimationDriver::stopped, this, &QSGD3D12RenderLoop::onAnimationStopped); + m_anims->install(); + + m_vsyncDelta = qsgrl_animation_interval(); } QSGD3D12RenderLoop::~QSGD3D12RenderLoop() @@ -130,6 +150,8 @@ void QSGD3D12RenderLoop::windowDestroyed(QQuickWindow *window) delete rc; delete engine; + + delete wd->animationController; } void QSGD3D12RenderLoop::exposeWindow(QQuickWindow *window) @@ -165,12 +187,31 @@ void QSGD3D12RenderLoop::exposureChanged(QQuickWindow *window) if (window->isExposed()) { if (!m_windows.contains(window)) exposeWindow(window); + + // Stop non-visual animation timer as we now have a window rendering. + if (m_animationTimer && somethingVisible()) { + killTimer(m_animationTimer); + m_animationTimer = 0; + } + // If we have a pending timer and we get an expose, we need to stop it. + // Otherwise we get two frames and two animation ticks in the same time interval. + if (m_updateTimer) { + killTimer(m_updateTimer); + m_updateTimer = 0; + } + WindowData &data(m_windows[window]); data.exposed = true; data.updatePending = true; - renderWindow(window); + + render(); + } else if (m_windows.contains(window)) { obscureWindow(window); + + // Potentially start the non-visual animation timer if nobody is rendering. + if (m_anims->isRunning() && !somethingVisible() && !m_animationTimer) + m_animationTimer = startTimer(m_vsyncDelta); } } @@ -193,32 +234,41 @@ QImage QSGD3D12RenderLoop::grab(QQuickWindow *window) return grabbed; } -void QSGD3D12RenderLoop::update(QQuickWindow *window) +bool QSGD3D12RenderLoop::somethingVisible() const { - if (!m_windows.contains(window)) - return; + for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it) { + if (it.key()->isVisible() && it.key()->isExposed()) + return true; + } + return false; +} - m_windows[window].updatePending = true; - window->requestUpdate(); +void QSGD3D12RenderLoop::maybePostUpdateTimer() +{ + if (!m_updateTimer) { + if (Q_UNLIKELY(debug_loop())) + qDebug("starting update timer"); + m_updateTimer = startTimer(m_vsyncDelta / 3); + } } -void QSGD3D12RenderLoop::maybeUpdate(QQuickWindow *window) +void QSGD3D12RenderLoop::update(QQuickWindow *window) { - update(window); + maybeUpdate(window); } -// called in response to window->requestUpdate() -void QSGD3D12RenderLoop::handleUpdateRequest(QQuickWindow *window) +void QSGD3D12RenderLoop::maybeUpdate(QQuickWindow *window) { - if (Q_UNLIKELY(debug_loop())) - qDebug() << "handleUpdateRequest" << window; + if (!m_windows.contains(window) || !somethingVisible()) + return; - renderWindow(window); + m_windows[window].updatePending = true; + maybePostUpdateTimer(); } QAnimationDriver *QSGD3D12RenderLoop::animationDriver() const { - return nullptr; + return m_anims; } QSGContext *QSGD3D12RenderLoop::sceneGraphContext() const @@ -250,6 +300,92 @@ QSurface::SurfaceType QSGD3D12RenderLoop::windowSurfaceType() const return QSurface::OpenGLSurface; } +bool QSGD3D12RenderLoop::interleaveIncubation() const +{ + return m_anims->isRunning() && somethingVisible(); +} + +void QSGD3D12RenderLoop::onAnimationStarted() +{ + if (!somethingVisible()) { + if (!m_animationTimer) { + if (Q_UNLIKELY(debug_loop())) + qDebug("starting non-visual animation timer"); + m_animationTimer = startTimer(m_vsyncDelta); + } + } else { + maybePostUpdateTimer(); + } +} + +void QSGD3D12RenderLoop::onAnimationStopped() +{ + if (m_animationTimer) { + if (Q_UNLIKELY(debug_loop())) + qDebug("stopping non-visual animation timer"); + killTimer(m_animationTimer); + m_animationTimer = 0; + } +} + +bool QSGD3D12RenderLoop::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::Timer: + { + QTimerEvent *te = static_cast<QTimerEvent *>(event); + if (te->timerId() == m_animationTimer) { + if (Q_UNLIKELY(debug_loop())) + qDebug("animation tick while no windows exposed"); + m_anims->advance(); + } else if (te->timerId() == m_updateTimer) { + if (Q_UNLIKELY(debug_loop())) + qDebug("update timeout - rendering"); + killTimer(m_updateTimer); + m_updateTimer = 0; + render(); + } + return true; + } + default: + break; + } + + return QObject::event(event); +} + +void QSGD3D12RenderLoop::render() +{ + bool rendered = false; + for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) { + if (it->updatePending) { + it->updatePending = false; + renderWindow(it.key()); + rendered = true; + } + } + + if (!rendered) { + if (Q_UNLIKELY(debug_loop())) + qDebug("render - no changes, sleep"); + QThread::msleep(m_vsyncDelta); + } + + if (m_anims->isRunning()) { + if (Q_UNLIKELY(debug_loop())) + qDebug("render - advancing animations"); + + m_anims->advance(); + + // It is not given that animations triggered another maybeUpdate() + // and thus another render pass, so to keep things running, + // make sure there is another frame pending. + maybePostUpdateTimer(); + + emit timeToIncubate(); + } +} + void QSGD3D12RenderLoop::renderWindow(QQuickWindow *window) { if (Q_UNLIKELY(debug_loop())) @@ -266,14 +402,8 @@ void QSGD3D12RenderLoop::renderWindow(QQuickWindow *window) return; } - const bool needsSwap = data.updatePending; - data.updatePending = false; - - if (!data.grabOnly) { + if (!data.grabOnly) wd->flushFrameSynchronousEvents(); - if (!m_windows.contains(window)) - return; - } QElapsedTimer renderTimer; qint64 renderTime = 0, syncTime = 0, polishTime = 0; @@ -336,28 +466,28 @@ void QSGD3D12RenderLoop::renderWindow(QQuickWindow *window) renderTime = renderTimer.nsecsElapsed(); Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame); - if (data.grabOnly) { + if (!data.grabOnly) { + // The engine is able to have multiple frames in flight. This in effect is + // similar to BufferQueueingOpenGL. Provide an env var to force the + // traditional blocking swap behavior, just in case. + static bool blockOnEachFrame = qEnvironmentVariableIntValue("QT_D3D_BLOCKING_PRESENT") != 0; + + if (window->isVisible()) { + data.engine->present(); + if (blockOnEachFrame) + data.engine->waitGPU(); + // The concept of "frame swaps" is quite misleading by default, when + // blockOnEachFrame is not used, but emit it for compatibility. + wd->fireFrameSwapped(); + } else { + if (blockOnEachFrame) + data.engine->waitGPU(); + } + } else { m_grabContent = data.engine->executeAndWaitReadbackRenderTarget(); data.grabOnly = false; } - // The engine is able to have multiple frames in flight. This in effect is - // similar to BufferQueueingOpenGL. Provide an env var to force the - // traditional blocking swap behavior, just in case. - static bool blockOnEachFrame = qEnvironmentVariableIntValue("QT_D3D_BLOCKING_PRESENT") != 0; - - if (needsSwap && window->isVisible()) { - data.engine->present(); - if (blockOnEachFrame) - data.engine->waitGPU(); - // The concept of "frame swaps" is quite misleading by default, when - // blockOnEachFrame is not used, but emit it for compatibility. - wd->fireFrameSwapped(); - } else { - if (blockOnEachFrame) - data.engine->waitGPU(); - } - qint64 swapTime = 0; if (profileFrames) swapTime = renderTimer.nsecsElapsed(); @@ -375,10 +505,6 @@ void QSGD3D12RenderLoop::renderWindow(QQuickWindow *window) lastFrameTime = QTime::currentTime(); } - // Might have been set during syncSceneGraph() - if (data.updatePending) - maybeUpdate(window); - // Simulate device loss if requested. static int devLossTest = qEnvironmentVariableIntValue("QT_D3D_TEST_DEVICE_LOSS"); if (devLossTest > 0) { diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h index 2f2ebbef33..c0333ffad0 100644 --- a/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h +++ b/src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h @@ -61,6 +61,8 @@ class QSGD3D12RenderContext; class QSGD3D12RenderLoop : public QSGRenderLoop { + Q_OBJECT + public: QSGD3D12RenderLoop(); ~QSGD3D12RenderLoop(); @@ -77,7 +79,6 @@ public: void update(QQuickWindow *window) override; void maybeUpdate(QQuickWindow *window) override; - void handleUpdateRequest(QQuickWindow *window) override; QAnimationDriver *animationDriver() const override; @@ -88,14 +89,28 @@ public: void postJob(QQuickWindow *window, QRunnable *job) override; QSurface::SurfaceType windowSurfaceType() const override; + bool interleaveIncubation() const override; int flags() const override; + bool event(QEvent *event) override; + +public Q_SLOTS: + void onAnimationStarted(); + void onAnimationStopped(); + private: void exposeWindow(QQuickWindow *window); void obscureWindow(QQuickWindow *window); void renderWindow(QQuickWindow *window); + void render(); + void maybePostUpdateTimer(); + bool somethingVisible() const; QSGD3D12Context *sg; + QAnimationDriver *m_anims; + int m_vsyncDelta; + int m_updateTimer = 0; + int m_animationTimer = 0; struct WindowData { QSGD3D12RenderContext *rc = nullptr; |