aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp')
-rw-r--r--src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp1186
1 files changed, 0 insertions, 1186 deletions
diff --git a/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp b/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp
deleted file mode 100644
index 4302a9119b..0000000000
--- a/src/plugins/scenegraph/d3d12/qsgd3d12threadedrenderloop.cpp
+++ /dev/null
@@ -1,1186 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQuick module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qsgd3d12threadedrenderloop_p.h"
-#include "qsgd3d12engine_p.h"
-#include "qsgd3d12context_p.h"
-#include "qsgd3d12rendercontext_p.h"
-#include "qsgd3d12shadereffectnode_p.h"
-#include <private/qsgrenderer_p.h>
-#include <private/qquickwindow_p.h>
-#include <private/qquickanimatorcontroller_p.h>
-#include <private/qquickprofiler_p.h>
-#include <private/qqmldebugserviceinterfaces_p.h>
-#include <private/qqmldebugconnector_p.h>
-#include <QElapsedTimer>
-#include <QQueue>
-#include <QGuiApplication>
-
-QT_BEGIN_NAMESPACE
-
-// NOTE: Avoid categorized logging. It is slow.
-
-#define DECLARE_DEBUG_VAR(variable) \
- static bool debug_ ## variable() \
- { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
-
-DECLARE_DEBUG_VAR(loop)
-DECLARE_DEBUG_VAR(time)
-
-
-// NOTE: The threaded renderloop is not currently safe to use in practice as it
-// is prone to deadlocks, in particular when multiple windows are active. This
-// is because DXGI's limitation of relying on the gui message pump in certain
-// cases. See
-// https://msdn.microsoft.com/en-us/library/windows/desktop/ee417025(v=vs.85).aspx#multithreading_and_dxgi
-//
-// This means that if swap chain functions like create, release, and
-// potentially even Present, are called outside the gui thread, then the
-// application must ensure the gui thread does not ever block and wait for the
-// render thread - since on the render thread a DXGI call may be in turn
-// waiting for the gui thread to deliver a window message...
-//
-// Ensuring this is impossible with the current design where the gui thread
-// must block at certain points, waiting for the render thread. Qt moves out
-// rendering from the main thread, in order to make application's life easier,
-// whereas the typical DXGI-compatible model would require moving work, but not
-// windowing and presenting, out to additional threads.
-
-
-/*
- The D3D render loop mostly mirrors the threaded OpenGL render loop.
-
- There are two classes here. QSGD3D12ThreadedRenderLoop and
- QSGD3D12RenderThread. All communication between the two is based on event
- passing and we have a number of custom events.
-
- Render loop is per process, render thread is per window. The
- QSGD3D12RenderContext and QSGD3D12Engine are per window as well. The former
- is created (but not owned) by QQuickWindow. The D3D device is per process.
-
- In this implementation, the render thread is never blocked and the GUI
- thread will initiate a polishAndSync which will block and wait for the
- render thread to pick it up and release the block only after the render
- thread is done syncing. The reason for this is:
-
- 1. Clear blocking paradigm. We only have one real "block" point
- (polishAndSync()) and all blocking is initiated by GUI and picked up by
- Render at specific times based on events. This makes the execution
- deterministic.
-
- 2. Render does not have to interact with GUI. This is done so that the
- render thread can run its own animation system which stays alive even when
- the GUI thread is blocked doing I/O, object instantiation, QPainter-painting
- or any other non-trivial task.
-
- The render thread has affinity to the GUI thread until a window is shown.
- From that moment and until the window is destroyed, it will have affinity to
- the render thread. (moved back at the end of run for cleanup).
- */
-
-// Passed from the RL to the RT when a window is removed obscured and should be
-// removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync.
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-
-// Passed by the RL to the RT to maybe release resource if no windows are
-// rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run.
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
-class QSGD3D12WindowEvent : public QEvent
-{
-public:
- QSGD3D12WindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
- QQuickWindow *window;
-};
-
-class QSGD3D12TryReleaseEvent : public QSGD3D12WindowEvent
-{
-public:
- QSGD3D12TryReleaseEvent(QQuickWindow *win, bool destroy)
- : QSGD3D12WindowEvent(win, WM_TryRelease), destroying(destroy) { }
- bool destroying;
-};
-
-class QSGD3D12SyncEvent : public QSGD3D12WindowEvent
-{
-public:
- QSGD3D12SyncEvent(QQuickWindow *c, bool inExpose, bool force)
- : QSGD3D12WindowEvent(c, WM_RequestSync)
- , size(c->size())
- , dpr(c->effectiveDevicePixelRatio())
- , syncInExpose(inExpose)
- , forceRenderPass(force) { }
- QSize size;
- float dpr;
- bool syncInExpose;
- bool forceRenderPass;
-};
-
-class QSGD3D12GrabEvent : public QSGD3D12WindowEvent
-{
-public:
- QSGD3D12GrabEvent(QQuickWindow *c, QImage *result)
- : QSGD3D12WindowEvent(c, WM_Grab), image(result) { }
- QImage *image;
-};
-
-class QSGD3D12JobEvent : public QSGD3D12WindowEvent
-{
-public:
- QSGD3D12JobEvent(QQuickWindow *c, QRunnable *postedJob)
- : QSGD3D12WindowEvent(c, WM_PostJob), job(postedJob) { }
- ~QSGD3D12JobEvent() { delete job; }
- QRunnable *job;
-};
-
-class QSGD3D12EventQueue : public QQueue<QEvent *>
-{
-public:
- void addEvent(QEvent *e) {
- mutex.lock();
- enqueue(e);
- if (waiting)
- condition.wakeOne();
- mutex.unlock();
- }
-
- QEvent *takeEvent(bool wait) {
- mutex.lock();
- if (isEmpty() && wait) {
- waiting = true;
- condition.wait(&mutex);
- waiting = false;
- }
- QEvent *e = dequeue();
- mutex.unlock();
- return e;
- }
-
- bool hasMoreEvents() {
- mutex.lock();
- bool has = !isEmpty();
- mutex.unlock();
- return has;
- }
-
-private:
- QMutex mutex;
- QWaitCondition condition;
- bool waiting = false;
-};
-
-static inline int qsgrl_animation_interval()
-{
- const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
- return refreshRate < 1 ? 16 : int(1000 / refreshRate);
-}
-
-class QSGD3D12RenderThread : public QThread
-{
- Q_OBJECT
-
-public:
- QSGD3D12RenderThread(QSGD3D12ThreadedRenderLoop *rl, QSGRenderContext *renderContext)
- : renderLoop(rl)
- {
- rc = static_cast<QSGD3D12RenderContext *>(renderContext);
- vsyncDelta = qsgrl_animation_interval();
- }
-
- ~QSGD3D12RenderThread()
- {
- delete rc;
- }
-
- bool event(QEvent *e);
- void run();
-
- void syncAndRender();
- void sync(bool inExpose);
-
- void requestRepaint()
- {
- if (sleeping)
- stopEventProcessing = true;
- if (exposedWindow)
- pendingUpdate |= RepaintRequest;
- }
-
- void processEventsAndWaitForMore();
- void processEvents();
- void postEvent(QEvent *e);
-
- enum UpdateRequest {
- SyncRequest = 0x01,
- RepaintRequest = 0x02,
- ExposeRequest = 0x04 | RepaintRequest | SyncRequest
- };
-
- QSGD3D12Engine *engine = nullptr;
- QSGD3D12ThreadedRenderLoop *renderLoop;
- QSGD3D12RenderContext *rc;
- QAnimationDriver *rtAnim = nullptr;
- volatile bool active = false;
- uint pendingUpdate = 0;
- bool sleeping = false;
- bool syncResultedInChanges = false;
- float vsyncDelta;
- QMutex mutex;
- QWaitCondition waitCondition;
- QQuickWindow *exposedWindow = nullptr;
- bool stopEventProcessing = false;
- QSGD3D12EventQueue eventQueue;
- QElapsedTimer threadTimer;
- qint64 syncTime;
- qint64 renderTime;
- qint64 sinceLastTime;
-
-public slots:
- void onSceneGraphChanged() {
- syncResultedInChanges = true;
- }
-};
-
-bool QSGD3D12RenderThread::event(QEvent *e)
-{
- switch (e->type()) {
-
- case WM_Obscure:
- Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGD3D12WindowEvent *>(e)->window);
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "RT - WM_Obscure" << exposedWindow;
- mutex.lock();
- if (exposedWindow) {
- QQuickWindowPrivate::get(exposedWindow)->fireAboutToStop();
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_Obscure - window removed");
- exposedWindow = nullptr;
- }
- waitCondition.wakeOne();
- mutex.unlock();
- return true;
-
- case WM_RequestSync: {
- QSGD3D12SyncEvent *wme = static_cast<QSGD3D12SyncEvent *>(e);
- if (sleeping)
- stopEventProcessing = true;
- // One thread+engine for each window. However, the native window may
- // change in some (quite artificial) cases, e.g. due to a hide -
- // destroy - show on the QWindow.
- bool needsWindow = !engine->window();
- if (engine->window() && engine->window() != wme->window->winId()) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_RequestSync - native window handle changes for active engine");
- engine->waitGPU();
- QQuickWindowPrivate::get(wme->window)->cleanupNodesOnShutdown();
- QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
- rc->invalidate();
- engine->releaseResources();
- needsWindow = true;
- }
- if (needsWindow) {
- // Must only ever get here when there is no window or releaseResources() has been called.
- const int samples = wme->window->format().samples();
- const bool alpha = wme->window->format().alphaBufferSize() > 0;
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "RT - WM_RequestSync - initializing D3D12 engine" << wme->window
- << wme->size << wme->dpr << samples << alpha;
- engine->attachToWindow(wme->window->winId(), wme->size, wme->dpr, samples, alpha);
- }
- exposedWindow = wme->window;
- engine->setWindowSize(wme->size, wme->dpr);
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "RT - WM_RequestSync" << exposedWindow;
- pendingUpdate |= SyncRequest;
- if (wme->syncInExpose) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_RequestSync - triggered from expose");
- pendingUpdate |= ExposeRequest;
- }
- if (wme->forceRenderPass) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_RequestSync - repaint regardless");
- pendingUpdate |= RepaintRequest;
- }
- return true;
- }
-
- case WM_TryRelease: {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_TryRelease");
- mutex.lock();
- renderLoop->lockedForSync = true;
- QSGD3D12TryReleaseEvent *wme = static_cast<QSGD3D12TryReleaseEvent *>(e);
- // Only when no windows are exposed anymore or we are shutting down.
- if (!exposedWindow || wme->destroying) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_TryRelease - invalidating rc");
- if (wme->window) {
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
- if (wme->destroying) {
- // QSGNode destruction may release graphics resources in use so wait first.
- engine->waitGPU();
- // Bye bye nodes...
- wd->cleanupNodesOnShutdown();
- QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
- }
- rc->invalidate();
- QCoreApplication::processEvents();
- QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
- if (wme->destroying)
- wd->animationController.reset();
- }
- if (wme->destroying)
- active = false;
- if (sleeping)
- stopEventProcessing = true;
- } else {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_TryRelease - not releasing because window is still active");
- }
- waitCondition.wakeOne();
- renderLoop->lockedForSync = false;
- mutex.unlock();
- return true;
- }
-
- case WM_Grab: {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_Grab");
- QSGD3D12GrabEvent *wme = static_cast<QSGD3D12GrabEvent *>(e);
- Q_ASSERT(wme->window);
- Q_ASSERT(wme->window == exposedWindow || !exposedWindow);
- mutex.lock();
- if (wme->window) {
- // Grabbing is generally done by rendering a frame and reading the
- // color buffer contents back, without presenting, and then
- // creating a QImage from the returned data. It is terribly
- // inefficient since it involves a full blocking wait for the GPU.
- // However, our hands are tied by the existing, synchronous APIs of
- // QQuickWindow and such.
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
- rc->initialize(nullptr);
- wd->syncSceneGraph();
- rc->endSync();
- wd->renderSceneGraph(wme->window->size());
- *wme->image = engine->executeAndWaitReadbackRenderTarget();
- }
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_Grab - waking gui to handle result");
- waitCondition.wakeOne();
- mutex.unlock();
- return true;
- }
-
- case WM_PostJob: {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_PostJob");
- QSGD3D12JobEvent *wme = static_cast<QSGD3D12JobEvent *>(e);
- Q_ASSERT(wme->window == exposedWindow);
- if (exposedWindow) {
- wme->job->run();
- delete wme->job;
- wme->job = nullptr;
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - WM_PostJob - job done");
- }
- return true;
- }
-
- default:
- break;
- }
-
- return QThread::event(e);
-}
-
-void QSGD3D12RenderThread::postEvent(QEvent *e)
-{
- eventQueue.addEvent(e);
-}
-
-void QSGD3D12RenderThread::processEvents()
-{
- while (eventQueue.hasMoreEvents()) {
- QEvent *e = eventQueue.takeEvent(false);
- event(e);
- delete e;
- }
-}
-
-void QSGD3D12RenderThread::processEventsAndWaitForMore()
-{
- stopEventProcessing = false;
- while (!stopEventProcessing) {
- QEvent *e = eventQueue.takeEvent(true);
- event(e);
- delete e;
- }
-}
-
-void QSGD3D12RenderThread::run()
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - run()");
-
- engine = new QSGD3D12Engine;
- rc->setEngine(engine);
-
- rtAnim = rc->sceneGraphContext()->createAnimationDriver(nullptr);
- rtAnim->install();
-
- if (QQmlDebugConnector::service<QQmlProfilerService>())
- QQuickProfiler::registerAnimationCallback();
-
- while (active) {
- if (exposedWindow)
- syncAndRender();
-
- processEvents();
- QCoreApplication::processEvents();
-
- if (pendingUpdate == 0 || !exposedWindow) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - done drawing, sleep");
- sleeping = true;
- processEventsAndWaitForMore();
- sleeping = false;
- }
- }
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - run() exiting");
-
- delete rtAnim;
- rtAnim = nullptr;
-
- rc->moveToThread(renderLoop->thread());
- moveToThread(renderLoop->thread());
-
- rc->setEngine(nullptr);
- delete engine;
- engine = nullptr;
-}
-
-void QSGD3D12RenderThread::sync(bool inExpose)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - sync");
-
- mutex.lock();
- Q_ASSERT_X(renderLoop->lockedForSync, "QSGD3D12RenderThread::sync()", "sync triggered with gui not locked");
-
- // Recover from device loss.
- if (!engine->hasResources()) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - sync - device was lost, resetting scenegraph");
- QQuickWindowPrivate::get(exposedWindow)->cleanupNodesOnShutdown();
- QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
- rc->invalidate();
- }
-
- if (engine->window()) {
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
- bool hadRenderer = wd->renderer != nullptr;
- // If the scene graph was touched since the last sync() make sure it sends the
- // changed signal.
- if (wd->renderer)
- wd->renderer->clearChangedFlag();
-
- rc->initialize(nullptr);
- wd->syncSceneGraph();
- rc->endSync();
-
- if (!hadRenderer && wd->renderer) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - created renderer");
- syncResultedInChanges = true;
- connect(wd->renderer, &QSGRenderer::sceneGraphChanged, this,
- &QSGD3D12RenderThread::onSceneGraphChanged, Qt::DirectConnection);
- }
-
- // Process deferred deletes now, directly after the sync as deleteLater
- // on the GUI must now also have resulted in SG changes and the delete
- // is a safe operation.
- QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
- }
-
- if (!inExpose) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - sync complete, waking gui");
- waitCondition.wakeOne();
- mutex.unlock();
- }
-}
-
-void QSGD3D12RenderThread::syncAndRender()
-{
- if (Q_UNLIKELY(debug_time())) {
- sinceLastTime = threadTimer.nsecsElapsed();
- threadTimer.start();
- }
- Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
-
- QElapsedTimer waitTimer;
- waitTimer.start();
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - syncAndRender()");
-
- syncResultedInChanges = false;
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
-
- const bool repaintRequested = (pendingUpdate & RepaintRequest) || wd->customRenderStage;
- const bool syncRequested = pendingUpdate & SyncRequest;
- const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
- pendingUpdate = 0;
-
- if (syncRequested)
- sync(exposeRequested);
-
-#ifndef QSG_NO_RENDER_TIMING
- if (Q_UNLIKELY(debug_time()))
- syncTime = threadTimer.nsecsElapsed();
-#endif
- Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
- QQuickProfiler::SceneGraphRenderLoopSync);
-
- if (!syncResultedInChanges && !repaintRequested) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - no changes, render aborted");
- int waitTime = vsyncDelta - (int) waitTimer.elapsed();
- if (waitTime > 0)
- msleep(waitTime);
- return;
- }
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - rendering started");
-
- if (rtAnim->isRunning()) {
- wd->animationController->lock();
- rtAnim->advance();
- wd->animationController->unlock();
- }
-
- bool canRender = wd->renderer != nullptr;
- // Recover from device loss.
- if (!engine->hasResources()) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - syncAndRender - device was lost, posting FullUpdateRequest");
- // Cannot do anything here because gui is not locked. Request a new
- // sync+render round on the gui thread and let the sync handle it.
- QCoreApplication::postEvent(exposedWindow, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
- canRender = false;
- }
-
- if (canRender) {
- wd->renderSceneGraph(engine->windowSize());
- if (Q_UNLIKELY(debug_time()))
- renderTime = threadTimer.nsecsElapsed();
- Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
- QQuickProfiler::SceneGraphRenderLoopRender);
-
- // 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 (!wd->customRenderStage || !wd->customRenderStage->swap())
- engine->present();
-
- if (blockOnEachFrame)
- 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 {
- Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
- QQuickProfiler::SceneGraphRenderLoopSync, 1);
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - window not ready, skipping render");
- }
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - rendering done");
-
- if (exposeRequested) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("RT - wake gui after initial expose");
- waitCondition.wakeOne();
- mutex.unlock();
- }
-
- if (Q_UNLIKELY(debug_time()))
- qDebug("Frame rendered with 'd3d12' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
- int(threadTimer.elapsed()),
- int((syncTime/1000000)),
- int((renderTime - syncTime) / 1000000),
- int(threadTimer.elapsed() - renderTime / 1000000));
-
- Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
- QQuickProfiler::SceneGraphRenderLoopSwap);
-
- static int devLossTest = qEnvironmentVariableIntValue("QT_D3D_TEST_DEVICE_LOSS");
- if (devLossTest > 0) {
- static QElapsedTimer kt;
- static bool timerRunning = false;
- if (!timerRunning) {
- kt.start();
- timerRunning = true;
- } else if (kt.elapsed() > 5000) {
- --devLossTest;
- kt.restart();
- engine->simulateDeviceLoss();
- }
- }
-}
-
-template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
-{
- for (const T &t : list) {
- if (t.window == window)
- return const_cast<T *>(&t);
- }
- return nullptr;
-}
-
-QSGD3D12ThreadedRenderLoop::QSGD3D12ThreadedRenderLoop()
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug("d3d12 THREADED render loop ctor");
-
- sg = new QSGD3D12Context;
-
- anim = sg->createAnimationDriver(this);
- connect(anim, &QAnimationDriver::started, this, &QSGD3D12ThreadedRenderLoop::onAnimationStarted);
- connect(anim, &QAnimationDriver::stopped, this, &QSGD3D12ThreadedRenderLoop::onAnimationStopped);
- anim->install();
-}
-
-QSGD3D12ThreadedRenderLoop::~QSGD3D12ThreadedRenderLoop()
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug("d3d12 THREADED render loop dtor");
-
- delete sg;
-}
-
-void QSGD3D12ThreadedRenderLoop::show(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "show" << window;
-}
-
-void QSGD3D12ThreadedRenderLoop::hide(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "hide" << window;
-
- if (window->isExposed())
- handleObscurity(windowFor(windows, window));
-
- releaseResources(window);
-}
-
-void QSGD3D12ThreadedRenderLoop::resize(QQuickWindow *window)
-{
- if (!window->isExposed() || window->size().isEmpty())
- return;
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "resize" << window << window->size();
-}
-
-void QSGD3D12ThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "window destroyed" << window;
-
- WindowData *w = windowFor(windows, window);
- if (!w)
- return;
-
- handleObscurity(w);
- handleResourceRelease(w, true);
-
- QSGD3D12RenderThread *thread = w->thread;
- while (thread->isRunning())
- QThread::yieldCurrentThread();
-
- Q_ASSERT(thread->thread() == QThread::currentThread());
- delete thread;
-
- for (int i = 0; i < windows.size(); ++i) {
- if (windows.at(i).window == window) {
- windows.removeAt(i);
- break;
- }
- }
-
- // Now that we altered the window list, we may need to stop the animation
- // timer even if we didn't via handleObscurity. This covers the case where
- // we destroy a visible & exposed QQuickWindow.
- startOrStopAnimationTimer();
-}
-
-void QSGD3D12ThreadedRenderLoop::exposureChanged(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "exposure changed" << window;
-
- if (window->isExposed()) {
- handleExposure(window);
- } else {
- WindowData *w = windowFor(windows, window);
- if (w)
- handleObscurity(w);
- }
-}
-
-QImage QSGD3D12ThreadedRenderLoop::grab(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "grab" << window;
-
- WindowData *w = windowFor(windows, window);
- // Have to support invisible (but created()'ed) windows as well.
- // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
- const bool tempExpose = !w;
- if (tempExpose) {
- handleExposure(window);
- w = windowFor(windows, window);
- Q_ASSERT(w);
- }
-
- if (!w->thread->isRunning())
- return QImage();
-
- if (!window->handle())
- window->create();
-
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
- wd->polishItems();
-
- QImage result;
- w->thread->mutex.lock();
- lockedForSync = true;
- w->thread->postEvent(new QSGD3D12GrabEvent(window, &result));
- w->thread->waitCondition.wait(&w->thread->mutex);
- lockedForSync = false;
- w->thread->mutex.unlock();
-
- result.setDevicePixelRatio(window->effectiveDevicePixelRatio());
-
- if (tempExpose)
- handleObscurity(w);
-
- return result;
-}
-
-void QSGD3D12ThreadedRenderLoop::update(QQuickWindow *window)
-{
- WindowData *w = windowFor(windows, window);
- if (!w)
- return;
-
- if (w->thread == QThread::currentThread()) {
- w->thread->requestRepaint();
- return;
- }
-
- // We set forceRenderPass because we want to make sure the QQuickWindow
- // actually does a full render pass after the next sync.
- w->forceRenderPass = true;
- scheduleUpdate(w);
-}
-
-void QSGD3D12ThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
-{
- WindowData *w = windowFor(windows, window);
- if (w)
- scheduleUpdate(w);
-}
-
-// called in response to window->requestUpdate()
-void QSGD3D12ThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "handleUpdateRequest" << window;
-
- WindowData *w = windowFor(windows, window);
- if (w)
- polishAndSync(w, false);
-}
-
-QAnimationDriver *QSGD3D12ThreadedRenderLoop::animationDriver() const
-{
- return anim;
-}
-
-QSGContext *QSGD3D12ThreadedRenderLoop::sceneGraphContext() const
-{
- return sg;
-}
-
-QSGRenderContext *QSGD3D12ThreadedRenderLoop::createRenderContext(QSGContext *) const
-{
- return sg->createRenderContext();
-}
-
-void QSGD3D12ThreadedRenderLoop::releaseResources(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "releaseResources" << window;
-
- WindowData *w = windowFor(windows, window);
- if (w)
- handleResourceRelease(w, false);
-}
-
-void QSGD3D12ThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
-{
- WindowData *w = windowFor(windows, window);
- if (w && w->thread && w->thread->exposedWindow)
- w->thread->postEvent(new QSGD3D12JobEvent(window, job));
- else
- delete job;
-}
-
-QSurface::SurfaceType QSGD3D12ThreadedRenderLoop::windowSurfaceType() const
-{
- return QSurface::OpenGLSurface;
-}
-
-bool QSGD3D12ThreadedRenderLoop::interleaveIncubation() const
-{
- bool somethingVisible = false;
- for (const WindowData &w : windows) {
- if (w.window->isVisible() && w.window->isExposed()) {
- somethingVisible = true;
- break;
- }
- }
- return somethingVisible && anim->isRunning();
-}
-
-int QSGD3D12ThreadedRenderLoop::flags() const
-{
- return SupportsGrabWithoutExpose;
-}
-
-bool QSGD3D12ThreadedRenderLoop::event(QEvent *e)
-{
- if (e->type() == QEvent::Timer) {
- QTimerEvent *te = static_cast<QTimerEvent *>(e);
- if (te->timerId() == animationTimer) {
- anim->advance();
- emit timeToIncubate();
- return true;
- }
- }
-
- return QObject::event(e);
-}
-
-void QSGD3D12ThreadedRenderLoop::onAnimationStarted()
-{
- startOrStopAnimationTimer();
-
- for (const WindowData &w : qAsConst(windows))
- w.window->requestUpdate();
-}
-
-void QSGD3D12ThreadedRenderLoop::onAnimationStopped()
-{
- startOrStopAnimationTimer();
-}
-
-void QSGD3D12ThreadedRenderLoop::startOrStopAnimationTimer()
-{
- int exposedWindowCount = 0;
- const WindowData *exposed = nullptr;
-
- for (int i = 0; i < windows.size(); ++i) {
- const WindowData &w(windows[i]);
- if (w.window->isVisible() && w.window->isExposed()) {
- ++exposedWindowCount;
- exposed = &w;
- }
- }
-
- if (animationTimer && (exposedWindowCount == 1 || !anim->isRunning())) {
- killTimer(animationTimer);
- animationTimer = 0;
- // If animations are running, make sure we keep on animating
- if (anim->isRunning())
- exposed->window->requestUpdate();
- } else if (!animationTimer && exposedWindowCount != 1 && anim->isRunning()) {
- animationTimer = startTimer(qsgrl_animation_interval());
- }
-}
-
-void QSGD3D12ThreadedRenderLoop::handleExposure(QQuickWindow *window)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "handleExposure" << window;
-
- WindowData *w = windowFor(windows, window);
- if (!w) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("adding window to list");
- WindowData win;
- win.window = window;
- QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context; // will transfer ownership
- win.thread = new QSGD3D12RenderThread(this, rc);
- win.updateDuringSync = false;
- win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
- windows.append(win);
- w = &windows.last();
- }
-
- // set this early as we'll be rendering shortly anyway and this avoids
- // special casing exposure in polishAndSync.
- w->thread->exposedWindow = window;
-
- if (w->window->size().isEmpty()
- || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
-#ifndef QT_NO_DEBUG
- qWarning().noquote().nospace() << "QSGD3D12ThreadedRenderLoop: expose event received for window "
- << w->window << " with invalid geometry: " << w->window->geometry()
- << " on " << w->window->screen();
-#endif
- }
-
- if (!w->window->handle())
- w->window->create();
-
- // Start render thread if it is not running
- if (!w->thread->isRunning()) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("starting render thread");
- // Push a few things to the render thread.
- QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController.data();
- if (controller->thread() != w->thread)
- controller->moveToThread(w->thread);
- if (w->thread->thread() == QThread::currentThread()) {
- w->thread->rc->moveToThread(w->thread);
- w->thread->moveToThread(w->thread);
- }
-
- w->thread->active = true;
- w->thread->start();
-
- if (!w->thread->isRunning())
- qFatal("Render thread failed to start, aborting application.");
- }
-
- polishAndSync(w, true);
-
- startOrStopAnimationTimer();
-}
-
-void QSGD3D12ThreadedRenderLoop::handleObscurity(WindowData *w)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "handleObscurity" << w->window;
-
- if (w->thread->isRunning()) {
- w->thread->mutex.lock();
- w->thread->postEvent(new QSGD3D12WindowEvent(w->window, WM_Obscure));
- w->thread->waitCondition.wait(&w->thread->mutex);
- w->thread->mutex.unlock();
- }
-
- startOrStopAnimationTimer();
-}
-
-void QSGD3D12ThreadedRenderLoop::scheduleUpdate(WindowData *w)
-{
- if (!QCoreApplication::instance())
- return;
-
- if (!w || !w->thread->isRunning())
- return;
-
- QThread *current = QThread::currentThread();
- if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) {
- qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
- return;
- }
-
- if (current == w->thread) {
- w->updateDuringSync = true;
- return;
- }
-
- w->window->requestUpdate();
-}
-
-void QSGD3D12ThreadedRenderLoop::handleResourceRelease(WindowData *w, bool destroying)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "handleResourceRelease" << (destroying ? "destroying" : "hide/releaseResources") << w->window;
-
- w->thread->mutex.lock();
- if (w->thread->isRunning() && w->thread->active) {
- QQuickWindow *window = w->window;
-
- // Note that window->handle() is typically null by this time because
- // the platform window is already destroyed. This should not be a
- // problem for the D3D cleanup.
-
- w->thread->postEvent(new QSGD3D12TryReleaseEvent(window, destroying));
- w->thread->waitCondition.wait(&w->thread->mutex);
-
- // Avoid a shutdown race condition.
- // If SG is invalidated and 'active' becomes false, the thread's run()
- // method will exit. handleExposure() relies on QThread::isRunning() (because it
- // potentially needs to start the thread again) and our mutex cannot be used to
- // track the thread stopping, so we wait a few nanoseconds extra so the thread
- // can exit properly.
- if (!w->thread->active)
- w->thread->wait();
- }
- w->thread->mutex.unlock();
-}
-
-void QSGD3D12ThreadedRenderLoop::polishAndSync(WindowData *w, bool inExpose)
-{
- if (Q_UNLIKELY(debug_loop()))
- qDebug() << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
-
- QQuickWindow *window = w->window;
- if (!w->thread || !w->thread->exposedWindow) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - not exposed, abort");
- return;
- }
-
- // Flush pending touch events.
- QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
- // The delivery of the event might have caused the window to stop rendering
- w = windowFor(windows, window);
- if (!w || !w->thread || !w->thread->exposedWindow) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - removed after touch event flushing, abort");
- return;
- }
-
- QElapsedTimer timer;
- qint64 polishTime = 0;
- qint64 waitTime = 0;
- qint64 syncTime = 0;
- if (Q_UNLIKELY(debug_time()))
- timer.start();
- Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
-
- QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
- wd->polishItems();
-
- if (Q_UNLIKELY(debug_time()))
- polishTime = timer.nsecsElapsed();
- Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
- QQuickProfiler::SceneGraphPolishAndSyncPolish);
-
- w->updateDuringSync = false;
-
- emit window->afterAnimating();
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - lock for sync");
- w->thread->mutex.lock();
- lockedForSync = true;
- w->thread->postEvent(new QSGD3D12SyncEvent(window, inExpose, w->forceRenderPass));
- w->forceRenderPass = false;
-
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - wait for sync");
- if (Q_UNLIKELY(debug_time()))
- waitTime = timer.nsecsElapsed();
- Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
- QQuickProfiler::SceneGraphPolishAndSyncWait);
- w->thread->waitCondition.wait(&w->thread->mutex);
- lockedForSync = false;
- w->thread->mutex.unlock();
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - unlock after sync");
-
- if (Q_UNLIKELY(debug_time()))
- syncTime = timer.nsecsElapsed();
- Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
- QQuickProfiler::SceneGraphPolishAndSyncSync);
-
- if (!animationTimer && anim->isRunning()) {
- if (Q_UNLIKELY(debug_loop()))
- qDebug("polishAndSync - advancing animations");
- anim->advance();
- // We need to trigger another sync to keep animations running...
- w->window->requestUpdate();
- emit timeToIncubate();
- } else if (w->updateDuringSync) {
- w->window->requestUpdate();
- }
-
- if (Q_UNLIKELY(debug_time()))
- qDebug().nospace()
- << "Frame prepared with 'd3d12' renderloop"
- << ", polish=" << (polishTime / 1000000)
- << ", lock=" << (waitTime - polishTime) / 1000000
- << ", blockedForSync=" << (syncTime - waitTime) / 1000000
- << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
- << " - (on gui thread) " << window;
-
- Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
- QQuickProfiler::SceneGraphPolishAndSyncAnimations);
-}
-
-#include "qsgd3d12threadedrenderloop.moc"
-
-QT_END_NAMESPACE