aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2016-06-30 14:27:42 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2016-07-01 11:37:01 +0000
commitc3561cdb36e538132ea07491ab6418294117b8f2 (patch)
treeee9dee6eb951d66eeaecd8c97b5c908ad727c7b3 /src/plugins
parentda839643397dda062cf7371e01fc1c990b7cf7c3 (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.cpp212
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12renderloop_p.h17
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;