1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qsgd3d12threadedrenderloop_p.h"
41 #include "qsgd3d12engine_p.h"
42 #include "qsgd3d12context_p.h"
43 #include "qsgd3d12rendercontext_p.h"
44 #include "qsgd3d12shadereffectnode_p.h"
45 #include <private/qsgrenderer_p.h>
46 #include <private/qquickwindow_p.h>
47 #include <private/qquickanimatorcontroller_p.h>
48 #include <private/qquickprofiler_p.h>
49 #include <private/qqmldebugserviceinterfaces_p.h>
50 #include <private/qqmldebugconnector_p.h>
51 #include <QElapsedTimer>
52 #include <QQueue>
53 #include <QGuiApplication>
54 
55 #include <qtquick_tracepoints_p.h>
56 
57 QT_BEGIN_NAMESPACE
58 
59 // NOTE: Avoid categorized logging. It is slow.
60 
61 #define DECLARE_DEBUG_VAR(variable) \
62     static bool debug_ ## variable() \
63     { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
64 
65 DECLARE_DEBUG_VAR(loop)
66 DECLARE_DEBUG_VAR(time)
67 
68 
69 // NOTE: The threaded renderloop is not currently safe to use in practice as it
70 // is prone to deadlocks, in particular when multiple windows are active. This
71 // is because DXGI's limitation of relying on the gui message pump in certain
72 // cases. See
73 // https://msdn.microsoft.com/en-us/library/windows/desktop/ee417025(v=vs.85).aspx#multithreading_and_dxgi
74 //
75 // This means that if swap chain functions like create, release, and
76 // potentially even Present, are called outside the gui thread, then the
77 // application must ensure the gui thread does not ever block and wait for the
78 // render thread - since on the render thread a DXGI call may be in turn
79 // waiting for the gui thread to deliver a window message...
80 //
81 // Ensuring this is impossible with the current design where the gui thread
82 // must block at certain points, waiting for the render thread. Qt moves out
83 // rendering from the main thread, in order to make application's life easier,
84 // whereas the typical DXGI-compatible model would require moving work, but not
85 // windowing and presenting, out to additional threads.
86 
87 
88 /*
89    The D3D render loop mostly mirrors the threaded OpenGL render loop.
90 
91    There are two classes here. QSGD3D12ThreadedRenderLoop and
92    QSGD3D12RenderThread. All communication between the two is based on event
93    passing and we have a number of custom events.
94 
95    Render loop is per process, render thread is per window. The
96    QSGD3D12RenderContext and QSGD3D12Engine are per window as well. The former
97    is created (but not owned) by QQuickWindow. The D3D device is per process.
98 
99    In this implementation, the render thread is never blocked and the GUI
100    thread will initiate a polishAndSync which will block and wait for the
101    render thread to pick it up and release the block only after the render
102    thread is done syncing. The reason for this is:
103 
104    1. Clear blocking paradigm. We only have one real "block" point
105    (polishAndSync()) and all blocking is initiated by GUI and picked up by
106    Render at specific times based on events. This makes the execution
107    deterministic.
108 
109    2. Render does not have to interact with GUI. This is done so that the
110    render thread can run its own animation system which stays alive even when
111    the GUI thread is blocked doing I/O, object instantiation, QPainter-painting
112    or any other non-trivial task.
113 
114    The render thread has affinity to the GUI thread until a window is shown.
115    From that moment and until the window is destroyed, it will have affinity to
116    the render thread. (moved back at the end of run for cleanup).
117  */
118 
119 // Passed from the RL to the RT when a window is removed obscured and should be
120 // removed from the render loop.
121 const QEvent::Type WM_Obscure           = QEvent::Type(QEvent::User + 1);
122 
123 // Passed from the RL to RT when GUI has been locked, waiting for sync.
124 const QEvent::Type WM_RequestSync       = QEvent::Type(QEvent::User + 2);
125 
126 // Passed by the RL to the RT to maybe release resource if no windows are
127 // rendering.
128 const QEvent::Type WM_TryRelease        = QEvent::Type(QEvent::User + 4);
129 
130 // Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
131 const QEvent::Type WM_Grab              = QEvent::Type(QEvent::User + 5);
132 
133 // Passed by the window when there is a render job to run.
134 const QEvent::Type WM_PostJob           = QEvent::Type(QEvent::User + 6);
135 
136 class QSGD3D12WindowEvent : public QEvent
137 {
138 public:
QSGD3D12WindowEvent(QQuickWindow * c,QEvent::Type type)139     QSGD3D12WindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
140     QQuickWindow *window;
141 };
142 
143 class QSGD3D12TryReleaseEvent : public QSGD3D12WindowEvent
144 {
145 public:
QSGD3D12TryReleaseEvent(QQuickWindow * win,bool destroy)146     QSGD3D12TryReleaseEvent(QQuickWindow *win, bool destroy)
147         : QSGD3D12WindowEvent(win, WM_TryRelease), destroying(destroy) { }
148     bool destroying;
149 };
150 
151 class QSGD3D12SyncEvent : public QSGD3D12WindowEvent
152 {
153 public:
QSGD3D12SyncEvent(QQuickWindow * c,bool inExpose,bool force)154     QSGD3D12SyncEvent(QQuickWindow *c, bool inExpose, bool force)
155         : QSGD3D12WindowEvent(c, WM_RequestSync)
156         , size(c->size())
157         , dpr(c->effectiveDevicePixelRatio())
158         , syncInExpose(inExpose)
159         , forceRenderPass(force) { }
160     QSize size;
161     float dpr;
162     bool syncInExpose;
163     bool forceRenderPass;
164 };
165 
166 class QSGD3D12GrabEvent : public QSGD3D12WindowEvent
167 {
168 public:
QSGD3D12GrabEvent(QQuickWindow * c,QImage * result)169     QSGD3D12GrabEvent(QQuickWindow *c, QImage *result)
170         : QSGD3D12WindowEvent(c, WM_Grab), image(result) { }
171     QImage *image;
172 };
173 
174 class QSGD3D12JobEvent : public QSGD3D12WindowEvent
175 {
176 public:
QSGD3D12JobEvent(QQuickWindow * c,QRunnable * postedJob)177     QSGD3D12JobEvent(QQuickWindow *c, QRunnable *postedJob)
178         : QSGD3D12WindowEvent(c, WM_PostJob), job(postedJob) { }
~QSGD3D12JobEvent()179     ~QSGD3D12JobEvent() { delete job; }
180     QRunnable *job;
181 };
182 
183 class QSGD3D12EventQueue : public QQueue<QEvent *>
184 {
185 public:
addEvent(QEvent * e)186     void addEvent(QEvent *e) {
187         mutex.lock();
188         enqueue(e);
189         if (waiting)
190             condition.wakeOne();
191         mutex.unlock();
192     }
193 
takeEvent(bool wait)194     QEvent *takeEvent(bool wait) {
195         mutex.lock();
196         if (isEmpty() && wait) {
197             waiting = true;
198             condition.wait(&mutex);
199             waiting = false;
200         }
201         QEvent *e = dequeue();
202         mutex.unlock();
203         return e;
204     }
205 
hasMoreEvents()206     bool hasMoreEvents() {
207         mutex.lock();
208         bool has = !isEmpty();
209         mutex.unlock();
210         return has;
211     }
212 
213 private:
214     QMutex mutex;
215     QWaitCondition condition;
216     bool waiting = false;
217 };
218 
qsgrl_animation_interval()219 static inline int qsgrl_animation_interval()
220 {
221     const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
222     return refreshRate < 1 ? 16 : int(1000 / refreshRate);
223 }
224 
225 class QSGD3D12RenderThread : public QThread
226 {
227     Q_OBJECT
228 
229 public:
QSGD3D12RenderThread(QSGD3D12ThreadedRenderLoop * rl,QSGRenderContext * renderContext)230     QSGD3D12RenderThread(QSGD3D12ThreadedRenderLoop *rl, QSGRenderContext *renderContext)
231         : renderLoop(rl)
232     {
233         rc = static_cast<QSGD3D12RenderContext *>(renderContext);
234         vsyncDelta = qsgrl_animation_interval();
235     }
236 
~QSGD3D12RenderThread()237     ~QSGD3D12RenderThread()
238     {
239         delete rc;
240     }
241 
242     bool event(QEvent *e);
243     void run();
244 
245     void syncAndRender();
246     void sync(bool inExpose);
247 
requestRepaint()248     void requestRepaint()
249     {
250         if (sleeping)
251             stopEventProcessing = true;
252         if (exposedWindow)
253             pendingUpdate |= RepaintRequest;
254     }
255 
256     void processEventsAndWaitForMore();
257     void processEvents();
258     void postEvent(QEvent *e);
259 
260     enum UpdateRequest {
261         SyncRequest         = 0x01,
262         RepaintRequest      = 0x02,
263         ExposeRequest       = 0x04 | RepaintRequest | SyncRequest
264     };
265 
266     QSGD3D12Engine *engine = nullptr;
267     QSGD3D12ThreadedRenderLoop *renderLoop;
268     QSGD3D12RenderContext *rc;
269     QAnimationDriver *rtAnim = nullptr;
270     volatile bool active = false;
271     uint pendingUpdate = 0;
272     bool sleeping = false;
273     bool syncResultedInChanges = false;
274     float vsyncDelta;
275     QMutex mutex;
276     QWaitCondition waitCondition;
277     QQuickWindow *exposedWindow = nullptr;
278     bool stopEventProcessing = false;
279     QSGD3D12EventQueue eventQueue;
280     QElapsedTimer threadTimer;
281     qint64 syncTime;
282     qint64 renderTime;
283     qint64 sinceLastTime;
284 
285 public slots:
onSceneGraphChanged()286     void onSceneGraphChanged() {
287         syncResultedInChanges = true;
288     }
289 };
290 
event(QEvent * e)291 bool QSGD3D12RenderThread::event(QEvent *e)
292 {
293     switch (e->type()) {
294 
295     case WM_Obscure:
296         Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGD3D12WindowEvent *>(e)->window);
297         if (Q_UNLIKELY(debug_loop()))
298             qDebug() << "RT - WM_Obscure" << exposedWindow;
299         mutex.lock();
300         if (exposedWindow) {
301             QQuickWindowPrivate::get(exposedWindow)->fireAboutToStop();
302             if (Q_UNLIKELY(debug_loop()))
303                 qDebug("RT - WM_Obscure - window removed");
304             exposedWindow = nullptr;
305         }
306         waitCondition.wakeOne();
307         mutex.unlock();
308         return true;
309 
310     case WM_RequestSync: {
311         QSGD3D12SyncEvent *wme = static_cast<QSGD3D12SyncEvent *>(e);
312         if (sleeping)
313             stopEventProcessing = true;
314         // One thread+engine for each window. However, the native window may
315         // change in some (quite artificial) cases, e.g. due to a hide -
316         // destroy - show on the QWindow.
317         bool needsWindow = !engine->window();
318         if (engine->window() && engine->window() != wme->window->winId()) {
319             if (Q_UNLIKELY(debug_loop()))
320                 qDebug("RT - WM_RequestSync - native window handle changes for active engine");
321             engine->waitGPU();
322             QQuickWindowPrivate::get(wme->window)->cleanupNodesOnShutdown();
323             QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
324             rc->invalidate();
325             engine->releaseResources();
326             needsWindow = true;
327         }
328         if (needsWindow) {
329             // Must only ever get here when there is no window or releaseResources() has been called.
330             const int samples = wme->window->format().samples();
331             const bool alpha = wme->window->format().alphaBufferSize() > 0;
332             if (Q_UNLIKELY(debug_loop()))
333                 qDebug() << "RT - WM_RequestSync - initializing D3D12 engine" << wme->window
334                          << wme->size << wme->dpr << samples << alpha;
335             engine->attachToWindow(wme->window->winId(), wme->size, wme->dpr, samples, alpha);
336         }
337         exposedWindow = wme->window;
338         engine->setWindowSize(wme->size, wme->dpr);
339         if (Q_UNLIKELY(debug_loop()))
340             qDebug() << "RT - WM_RequestSync" << exposedWindow;
341         pendingUpdate |= SyncRequest;
342         if (wme->syncInExpose) {
343             if (Q_UNLIKELY(debug_loop()))
344                 qDebug("RT - WM_RequestSync - triggered from expose");
345             pendingUpdate |= ExposeRequest;
346         }
347         if (wme->forceRenderPass) {
348             if (Q_UNLIKELY(debug_loop()))
349                 qDebug("RT - WM_RequestSync - repaint regardless");
350             pendingUpdate |= RepaintRequest;
351         }
352         return true;
353     }
354 
355     case WM_TryRelease: {
356         if (Q_UNLIKELY(debug_loop()))
357             qDebug("RT - WM_TryRelease");
358         mutex.lock();
359         renderLoop->lockedForSync = true;
360         QSGD3D12TryReleaseEvent *wme = static_cast<QSGD3D12TryReleaseEvent *>(e);
361         // Only when no windows are exposed anymore or we are shutting down.
362         if (!exposedWindow || wme->destroying) {
363             if (Q_UNLIKELY(debug_loop()))
364                 qDebug("RT - WM_TryRelease - invalidating rc");
365             if (wme->window) {
366                 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
367                 if (wme->destroying) {
368                     // QSGNode destruction may release graphics resources in use so wait first.
369                     engine->waitGPU();
370                     // Bye bye nodes...
371                     wd->cleanupNodesOnShutdown();
372                     QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
373                 }
374                 rc->invalidate();
375                 QCoreApplication::processEvents();
376                 QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
377                 if (wme->destroying)
378                     wd->animationController.reset();
379             }
380             if (wme->destroying)
381                 active = false;
382             if (sleeping)
383                 stopEventProcessing = true;
384         } else {
385             if (Q_UNLIKELY(debug_loop()))
386                 qDebug("RT - WM_TryRelease - not releasing because window is still active");
387         }
388         waitCondition.wakeOne();
389         renderLoop->lockedForSync = false;
390         mutex.unlock();
391         return true;
392     }
393 
394     case WM_Grab: {
395         if (Q_UNLIKELY(debug_loop()))
396             qDebug("RT - WM_Grab");
397         QSGD3D12GrabEvent *wme = static_cast<QSGD3D12GrabEvent *>(e);
398         Q_ASSERT(wme->window);
399         Q_ASSERT(wme->window == exposedWindow || !exposedWindow);
400         mutex.lock();
401         if (wme->window) {
402             // Grabbing is generally done by rendering a frame and reading the
403             // color buffer contents back, without presenting, and then
404             // creating a QImage from the returned data. It is terribly
405             // inefficient since it involves a full blocking wait for the GPU.
406             // However, our hands are tied by the existing, synchronous APIs of
407             // QQuickWindow and such.
408             QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
409             rc->initialize(nullptr);
410             wd->syncSceneGraph();
411             rc->endSync();
412             wd->renderSceneGraph(wme->window->size());
413             *wme->image = engine->executeAndWaitReadbackRenderTarget();
414         }
415         if (Q_UNLIKELY(debug_loop()))
416             qDebug("RT - WM_Grab - waking gui to handle result");
417         waitCondition.wakeOne();
418         mutex.unlock();
419         return true;
420     }
421 
422     case WM_PostJob: {
423         if (Q_UNLIKELY(debug_loop()))
424             qDebug("RT - WM_PostJob");
425         QSGD3D12JobEvent *wme = static_cast<QSGD3D12JobEvent *>(e);
426         Q_ASSERT(wme->window == exposedWindow);
427         if (exposedWindow) {
428             wme->job->run();
429             delete wme->job;
430             wme->job = nullptr;
431             if (Q_UNLIKELY(debug_loop()))
432                 qDebug("RT - WM_PostJob - job done");
433         }
434         return true;
435     }
436 
437     default:
438         break;
439     }
440 
441     return QThread::event(e);
442 }
443 
postEvent(QEvent * e)444 void QSGD3D12RenderThread::postEvent(QEvent *e)
445 {
446     eventQueue.addEvent(e);
447 }
448 
processEvents()449 void QSGD3D12RenderThread::processEvents()
450 {
451     while (eventQueue.hasMoreEvents()) {
452         QEvent *e = eventQueue.takeEvent(false);
453         event(e);
454         delete e;
455     }
456 }
457 
processEventsAndWaitForMore()458 void QSGD3D12RenderThread::processEventsAndWaitForMore()
459 {
460     stopEventProcessing = false;
461     while (!stopEventProcessing) {
462         QEvent *e = eventQueue.takeEvent(true);
463         event(e);
464         delete e;
465     }
466 }
467 
run()468 void QSGD3D12RenderThread::run()
469 {
470     if (Q_UNLIKELY(debug_loop()))
471         qDebug("RT - run()");
472 
473     engine = new QSGD3D12Engine;
474     rc->setEngine(engine);
475 
476     rtAnim = rc->sceneGraphContext()->createAnimationDriver(nullptr);
477     rtAnim->install();
478 
479     if (QQmlDebugConnector::service<QQmlProfilerService>())
480         QQuickProfiler::registerAnimationCallback();
481 
482     while (active) {
483         if (exposedWindow)
484             syncAndRender();
485 
486         processEvents();
487         QCoreApplication::processEvents();
488 
489         if (pendingUpdate == 0 || !exposedWindow) {
490             if (Q_UNLIKELY(debug_loop()))
491                 qDebug("RT - done drawing, sleep");
492             sleeping = true;
493             processEventsAndWaitForMore();
494             sleeping = false;
495         }
496     }
497 
498     if (Q_UNLIKELY(debug_loop()))
499         qDebug("RT - run() exiting");
500 
501     delete rtAnim;
502     rtAnim = nullptr;
503 
504     rc->moveToThread(renderLoop->thread());
505     moveToThread(renderLoop->thread());
506 
507     rc->setEngine(nullptr);
508     delete engine;
509     engine = nullptr;
510 }
511 
sync(bool inExpose)512 void QSGD3D12RenderThread::sync(bool inExpose)
513 {
514     if (Q_UNLIKELY(debug_loop()))
515         qDebug("RT - sync");
516 
517     mutex.lock();
518     Q_ASSERT_X(renderLoop->lockedForSync, "QSGD3D12RenderThread::sync()", "sync triggered with gui not locked");
519 
520     // Recover from device loss.
521     if (!engine->hasResources()) {
522         if (Q_UNLIKELY(debug_loop()))
523             qDebug("RT - sync - device was lost, resetting scenegraph");
524         QQuickWindowPrivate::get(exposedWindow)->cleanupNodesOnShutdown();
525         QSGD3D12ShaderEffectNode::cleanupMaterialTypeCache();
526         rc->invalidate();
527     }
528 
529     if (engine->window()) {
530         QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
531         bool hadRenderer = wd->renderer != nullptr;
532         // If the scene graph was touched since the last sync() make sure it sends the
533         // changed signal.
534         if (wd->renderer)
535             wd->renderer->clearChangedFlag();
536 
537         rc->initialize(nullptr);
538         wd->syncSceneGraph();
539         rc->endSync();
540 
541         if (!hadRenderer && wd->renderer) {
542             if (Q_UNLIKELY(debug_loop()))
543                 qDebug("RT - created renderer");
544             syncResultedInChanges = true;
545             connect(wd->renderer, &QSGRenderer::sceneGraphChanged, this,
546                     &QSGD3D12RenderThread::onSceneGraphChanged, Qt::DirectConnection);
547         }
548 
549         // Process deferred deletes now, directly after the sync as deleteLater
550         // on the GUI must now also have resulted in SG changes and the delete
551         // is a safe operation.
552         QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
553     }
554 
555     if (!inExpose) {
556         if (Q_UNLIKELY(debug_loop()))
557             qDebug("RT - sync complete, waking gui");
558         waitCondition.wakeOne();
559         mutex.unlock();
560     }
561 }
562 
syncAndRender()563 void QSGD3D12RenderThread::syncAndRender()
564 {
565     Q_TRACE_SCOPE(QSG_syncAndRender);
566     if (Q_UNLIKELY(debug_time())) {
567         sinceLastTime = threadTimer.nsecsElapsed();
568         threadTimer.start();
569     }
570     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
571     Q_TRACE(QSG_sync_entry);
572 
573     QElapsedTimer waitTimer;
574     waitTimer.start();
575 
576     if (Q_UNLIKELY(debug_loop()))
577         qDebug("RT - syncAndRender()");
578 
579     syncResultedInChanges = false;
580     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
581 
582     const bool repaintRequested = (pendingUpdate & RepaintRequest) || wd->customRenderStage;
583     const bool syncRequested = pendingUpdate & SyncRequest;
584     const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
585     pendingUpdate = 0;
586 
587     if (syncRequested)
588         sync(exposeRequested);
589 
590 #ifndef QSG_NO_RENDER_TIMING
591     if (Q_UNLIKELY(debug_time()))
592         syncTime = threadTimer.nsecsElapsed();
593 #endif
594     Q_TRACE(QSG_sync_exit);
595     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
596                               QQuickProfiler::SceneGraphRenderLoopSync);
597 
598     if (!syncResultedInChanges && !repaintRequested) {
599         if (Q_UNLIKELY(debug_loop()))
600             qDebug("RT - no changes, render aborted");
601         int waitTime = vsyncDelta - (int) waitTimer.elapsed();
602         if (waitTime > 0)
603             msleep(waitTime);
604         return;
605     }
606     Q_TRACE(QSG_render_entry);
607 
608     if (Q_UNLIKELY(debug_loop()))
609         qDebug("RT - rendering started");
610 
611     if (rtAnim->isRunning()) {
612         wd->animationController->lock();
613         rtAnim->advance();
614         wd->animationController->unlock();
615     }
616 
617     bool canRender = wd->renderer != nullptr;
618     // Recover from device loss.
619     if (!engine->hasResources()) {
620         if (Q_UNLIKELY(debug_loop()))
621             qDebug("RT - syncAndRender - device was lost, posting FullUpdateRequest");
622         // Cannot do anything here because gui is not locked. Request a new
623         // sync+render round on the gui thread and let the sync handle it.
624         QCoreApplication::postEvent(exposedWindow, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
625         canRender = false;
626     }
627 
628     if (canRender) {
629         wd->renderSceneGraph(engine->windowSize());
630         if (Q_UNLIKELY(debug_time()))
631             renderTime = threadTimer.nsecsElapsed();
632         Q_TRACE(QSG_render_exit);
633         Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
634                                   QQuickProfiler::SceneGraphRenderLoopRender);
635         Q_TRACE(QSG_swap_entry);
636 
637         // The engine is able to have multiple frames in flight. This in effect is
638         // similar to BufferQueueingOpenGL. Provide an env var to force the
639         // traditional blocking swap behavior, just in case.
640         static bool blockOnEachFrame = qEnvironmentVariableIntValue("QT_D3D_BLOCKING_PRESENT") != 0;
641 
642         if (!wd->customRenderStage || !wd->customRenderStage->swap())
643             engine->present();
644 
645         if (blockOnEachFrame)
646             engine->waitGPU();
647 
648         // The concept of "frame swaps" is quite misleading by default, when
649         // blockOnEachFrame is not used, but emit it for compatibility.
650         wd->fireFrameSwapped();
651     } else {
652         Q_TRACE(QSG_render_exit);
653         Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
654                                 QQuickProfiler::SceneGraphRenderLoopSync, 1);
655         Q_TRACE(QSG_swap_entry);
656         if (Q_UNLIKELY(debug_loop()))
657             qDebug("RT - window not ready, skipping render");
658     }
659 
660     if (Q_UNLIKELY(debug_loop()))
661         qDebug("RT - rendering done");
662 
663     if (exposeRequested) {
664         if (Q_UNLIKELY(debug_loop()))
665             qDebug("RT - wake gui after initial expose");
666         waitCondition.wakeOne();
667         mutex.unlock();
668     }
669 
670     if (Q_UNLIKELY(debug_time()))
671         qDebug("Frame rendered with 'd3d12' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
672                int(threadTimer.elapsed()),
673                int((syncTime/1000000)),
674                int((renderTime - syncTime) / 1000000),
675                int(threadTimer.elapsed() - renderTime / 1000000));
676 
677     Q_TRACE(QSG_swap_exit);
678     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
679                            QQuickProfiler::SceneGraphRenderLoopSwap);
680 
681     static int devLossTest = qEnvironmentVariableIntValue("QT_D3D_TEST_DEVICE_LOSS");
682     if (devLossTest > 0) {
683         static QElapsedTimer kt;
684         static bool timerRunning = false;
685         if (!timerRunning) {
686             kt.start();
687             timerRunning = true;
688         } else if (kt.elapsed() > 5000) {
689             --devLossTest;
690             kt.restart();
691             engine->simulateDeviceLoss();
692         }
693     }
694 }
695 
windowFor(const QVector<T> & list,QQuickWindow * window)696 template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
697 {
698     for (const T &t : list) {
699         if (t.window == window)
700             return const_cast<T *>(&t);
701     }
702     return nullptr;
703 }
704 
QSGD3D12ThreadedRenderLoop()705 QSGD3D12ThreadedRenderLoop::QSGD3D12ThreadedRenderLoop()
706 {
707     if (Q_UNLIKELY(debug_loop()))
708         qDebug("d3d12 THREADED render loop ctor");
709 
710     sg = new QSGD3D12Context;
711 
712     anim = sg->createAnimationDriver(this);
713     connect(anim, &QAnimationDriver::started, this, &QSGD3D12ThreadedRenderLoop::onAnimationStarted);
714     connect(anim, &QAnimationDriver::stopped, this, &QSGD3D12ThreadedRenderLoop::onAnimationStopped);
715     anim->install();
716 }
717 
~QSGD3D12ThreadedRenderLoop()718 QSGD3D12ThreadedRenderLoop::~QSGD3D12ThreadedRenderLoop()
719 {
720     if (Q_UNLIKELY(debug_loop()))
721         qDebug("d3d12 THREADED render loop dtor");
722 
723     delete sg;
724 }
725 
show(QQuickWindow * window)726 void QSGD3D12ThreadedRenderLoop::show(QQuickWindow *window)
727 {
728     if (Q_UNLIKELY(debug_loop()))
729         qDebug() << "show" << window;
730 }
731 
hide(QQuickWindow * window)732 void QSGD3D12ThreadedRenderLoop::hide(QQuickWindow *window)
733 {
734     if (Q_UNLIKELY(debug_loop()))
735         qDebug() << "hide" << window;
736 
737     if (window->isExposed())
738         handleObscurity(windowFor(windows, window));
739 
740     releaseResources(window);
741 }
742 
resize(QQuickWindow * window)743 void QSGD3D12ThreadedRenderLoop::resize(QQuickWindow *window)
744 {
745     if (!window->isExposed() || window->size().isEmpty())
746         return;
747 
748     if (Q_UNLIKELY(debug_loop()))
749         qDebug() << "resize" << window << window->size();
750 }
751 
windowDestroyed(QQuickWindow * window)752 void QSGD3D12ThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
753 {
754     if (Q_UNLIKELY(debug_loop()))
755         qDebug() << "window destroyed" << window;
756 
757     WindowData *w = windowFor(windows, window);
758     if (!w)
759         return;
760 
761     handleObscurity(w);
762     handleResourceRelease(w, true);
763 
764     QSGD3D12RenderThread *thread = w->thread;
765     while (thread->isRunning())
766         QThread::yieldCurrentThread();
767 
768     Q_ASSERT(thread->thread() == QThread::currentThread());
769     delete thread;
770 
771     for (int i = 0; i < windows.size(); ++i) {
772         if (windows.at(i).window == window) {
773             windows.removeAt(i);
774             break;
775         }
776     }
777 
778     // Now that we altered the window list, we may need to stop the animation
779     // timer even if we didn't via handleObscurity. This covers the case where
780     // we destroy a visible & exposed QQuickWindow.
781     startOrStopAnimationTimer();
782 }
783 
exposureChanged(QQuickWindow * window)784 void QSGD3D12ThreadedRenderLoop::exposureChanged(QQuickWindow *window)
785 {
786     if (Q_UNLIKELY(debug_loop()))
787         qDebug() << "exposure changed" << window;
788 
789     if (window->isExposed()) {
790         handleExposure(window);
791     } else {
792         WindowData *w = windowFor(windows, window);
793         if (w)
794             handleObscurity(w);
795     }
796 }
797 
grab(QQuickWindow * window)798 QImage QSGD3D12ThreadedRenderLoop::grab(QQuickWindow *window)
799 {
800     if (Q_UNLIKELY(debug_loop()))
801         qDebug() << "grab" << window;
802 
803     WindowData *w = windowFor(windows, window);
804     // Have to support invisible (but created()'ed) windows as well.
805     // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
806     const bool tempExpose = !w;
807     if (tempExpose) {
808         handleExposure(window);
809         w = windowFor(windows, window);
810         Q_ASSERT(w);
811     }
812 
813     if (!w->thread->isRunning())
814         return QImage();
815 
816     if (!window->handle())
817         window->create();
818 
819     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
820     wd->polishItems();
821 
822     QImage result;
823     w->thread->mutex.lock();
824     lockedForSync = true;
825     w->thread->postEvent(new QSGD3D12GrabEvent(window, &result));
826     w->thread->waitCondition.wait(&w->thread->mutex);
827     lockedForSync = false;
828     w->thread->mutex.unlock();
829 
830     result.setDevicePixelRatio(window->effectiveDevicePixelRatio());
831 
832     if (tempExpose)
833         handleObscurity(w);
834 
835     return result;
836 }
837 
update(QQuickWindow * window)838 void QSGD3D12ThreadedRenderLoop::update(QQuickWindow *window)
839 {
840     WindowData *w = windowFor(windows, window);
841     if (!w)
842         return;
843 
844     if (w->thread == QThread::currentThread()) {
845         w->thread->requestRepaint();
846         return;
847     }
848 
849     // We set forceRenderPass because we want to make sure the QQuickWindow
850     // actually does a full render pass after the next sync.
851     w->forceRenderPass = true;
852     scheduleUpdate(w);
853 }
854 
maybeUpdate(QQuickWindow * window)855 void QSGD3D12ThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
856 {
857     WindowData *w = windowFor(windows, window);
858     if (w)
859         scheduleUpdate(w);
860 }
861 
862 // called in response to window->requestUpdate()
handleUpdateRequest(QQuickWindow * window)863 void QSGD3D12ThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
864 {
865     if (Q_UNLIKELY(debug_loop()))
866         qDebug() << "handleUpdateRequest" << window;
867 
868     WindowData *w = windowFor(windows, window);
869     if (w)
870         polishAndSync(w, false);
871 }
872 
animationDriver() const873 QAnimationDriver *QSGD3D12ThreadedRenderLoop::animationDriver() const
874 {
875     return anim;
876 }
877 
sceneGraphContext() const878 QSGContext *QSGD3D12ThreadedRenderLoop::sceneGraphContext() const
879 {
880     return sg;
881 }
882 
createRenderContext(QSGContext *) const883 QSGRenderContext *QSGD3D12ThreadedRenderLoop::createRenderContext(QSGContext *) const
884 {
885     return sg->createRenderContext();
886 }
887 
releaseResources(QQuickWindow * window)888 void QSGD3D12ThreadedRenderLoop::releaseResources(QQuickWindow *window)
889 {
890     if (Q_UNLIKELY(debug_loop()))
891         qDebug() << "releaseResources" << window;
892 
893     WindowData *w = windowFor(windows, window);
894     if (w)
895         handleResourceRelease(w, false);
896 }
897 
postJob(QQuickWindow * window,QRunnable * job)898 void QSGD3D12ThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
899 {
900     WindowData *w = windowFor(windows, window);
901     if (w && w->thread && w->thread->exposedWindow)
902         w->thread->postEvent(new QSGD3D12JobEvent(window, job));
903     else
904         delete job;
905 }
906 
windowSurfaceType() const907 QSurface::SurfaceType QSGD3D12ThreadedRenderLoop::windowSurfaceType() const
908 {
909     return QSurface::OpenGLSurface;
910 }
911 
interleaveIncubation() const912 bool QSGD3D12ThreadedRenderLoop::interleaveIncubation() const
913 {
914     bool somethingVisible = false;
915     for (const WindowData &w : windows) {
916         if (w.window->isVisible() && w.window->isExposed()) {
917             somethingVisible = true;
918             break;
919         }
920     }
921     return somethingVisible && anim->isRunning();
922 }
923 
flags() const924 int QSGD3D12ThreadedRenderLoop::flags() const
925 {
926     return SupportsGrabWithoutExpose;
927 }
928 
event(QEvent * e)929 bool QSGD3D12ThreadedRenderLoop::event(QEvent *e)
930 {
931     if (e->type() == QEvent::Timer) {
932         QTimerEvent *te = static_cast<QTimerEvent *>(e);
933         if (te->timerId() == animationTimer) {
934             anim->advance();
935             emit timeToIncubate();
936             return true;
937         }
938     }
939 
940     return QObject::event(e);
941 }
942 
onAnimationStarted()943 void QSGD3D12ThreadedRenderLoop::onAnimationStarted()
944 {
945     startOrStopAnimationTimer();
946 
947     for (const WindowData &w : qAsConst(windows))
948         w.window->requestUpdate();
949 }
950 
onAnimationStopped()951 void QSGD3D12ThreadedRenderLoop::onAnimationStopped()
952 {
953     startOrStopAnimationTimer();
954 }
955 
startOrStopAnimationTimer()956 void QSGD3D12ThreadedRenderLoop::startOrStopAnimationTimer()
957 {
958     int exposedWindowCount = 0;
959     const WindowData *exposed = nullptr;
960 
961     for (int i = 0; i < windows.size(); ++i) {
962         const WindowData &w(windows[i]);
963         if (w.window->isVisible() && w.window->isExposed()) {
964             ++exposedWindowCount;
965             exposed = &w;
966         }
967     }
968 
969     if (animationTimer && (exposedWindowCount == 1 || !anim->isRunning())) {
970         killTimer(animationTimer);
971         animationTimer = 0;
972         // If animations are running, make sure we keep on animating
973         if (anim->isRunning())
974             exposed->window->requestUpdate();
975     } else if (!animationTimer && exposedWindowCount != 1 && anim->isRunning()) {
976         animationTimer = startTimer(qsgrl_animation_interval());
977     }
978 }
979 
handleExposure(QQuickWindow * window)980 void QSGD3D12ThreadedRenderLoop::handleExposure(QQuickWindow *window)
981 {
982     if (Q_UNLIKELY(debug_loop()))
983         qDebug() << "handleExposure" << window;
984 
985     WindowData *w = windowFor(windows, window);
986     if (!w) {
987         if (Q_UNLIKELY(debug_loop()))
988             qDebug("adding window to list");
989         WindowData win;
990         win.window = window;
991         QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context; // will transfer ownership
992         win.thread = new QSGD3D12RenderThread(this, rc);
993         win.updateDuringSync = false;
994         win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
995         windows.append(win);
996         w = &windows.last();
997     }
998 
999     // set this early as we'll be rendering shortly anyway and this avoids
1000     // special casing exposure in polishAndSync.
1001     w->thread->exposedWindow = window;
1002 
1003     if (w->window->size().isEmpty()
1004         || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
1005 #ifndef QT_NO_DEBUG
1006         qWarning().noquote().nospace() << "QSGD3D12ThreadedRenderLoop: expose event received for window "
1007             << w->window << " with invalid geometry: " << w->window->geometry()
1008             << " on " << w->window->screen();
1009 #endif
1010     }
1011 
1012     if (!w->window->handle())
1013         w->window->create();
1014 
1015     // Start render thread if it is not running
1016     if (!w->thread->isRunning()) {
1017         if (Q_UNLIKELY(debug_loop()))
1018             qDebug("starting render thread");
1019         // Push a few things to the render thread.
1020         QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController.data();
1021         if (controller->thread() != w->thread)
1022             controller->moveToThread(w->thread);
1023         if (w->thread->thread() == QThread::currentThread()) {
1024             w->thread->rc->moveToThread(w->thread);
1025             w->thread->moveToThread(w->thread);
1026         }
1027 
1028         w->thread->active = true;
1029         w->thread->start();
1030 
1031         if (!w->thread->isRunning())
1032             qFatal("Render thread failed to start, aborting application.");
1033     }
1034 
1035     polishAndSync(w, true);
1036 
1037     startOrStopAnimationTimer();
1038 }
1039 
handleObscurity(WindowData * w)1040 void QSGD3D12ThreadedRenderLoop::handleObscurity(WindowData *w)
1041 {
1042     if (Q_UNLIKELY(debug_loop()))
1043         qDebug() << "handleObscurity" << w->window;
1044 
1045     if (w->thread->isRunning()) {
1046         w->thread->mutex.lock();
1047         w->thread->postEvent(new QSGD3D12WindowEvent(w->window, WM_Obscure));
1048         w->thread->waitCondition.wait(&w->thread->mutex);
1049         w->thread->mutex.unlock();
1050     }
1051 
1052     startOrStopAnimationTimer();
1053 }
1054 
scheduleUpdate(WindowData * w)1055 void QSGD3D12ThreadedRenderLoop::scheduleUpdate(WindowData *w)
1056 {
1057     if (!QCoreApplication::instance())
1058         return;
1059 
1060     if (!w || !w->thread->isRunning())
1061         return;
1062 
1063     QThread *current = QThread::currentThread();
1064     if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) {
1065         qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1066         return;
1067     }
1068 
1069     if (current == w->thread) {
1070         w->updateDuringSync = true;
1071         return;
1072     }
1073 
1074     w->window->requestUpdate();
1075 }
1076 
handleResourceRelease(WindowData * w,bool destroying)1077 void QSGD3D12ThreadedRenderLoop::handleResourceRelease(WindowData *w, bool destroying)
1078 {
1079     if (Q_UNLIKELY(debug_loop()))
1080         qDebug() << "handleResourceRelease" << (destroying ? "destroying" : "hide/releaseResources") << w->window;
1081 
1082     w->thread->mutex.lock();
1083     if (w->thread->isRunning() && w->thread->active) {
1084         QQuickWindow *window = w->window;
1085 
1086         // Note that window->handle() is typically null by this time because
1087         // the platform window is already destroyed. This should not be a
1088         // problem for the D3D cleanup.
1089 
1090         w->thread->postEvent(new QSGD3D12TryReleaseEvent(window, destroying));
1091         w->thread->waitCondition.wait(&w->thread->mutex);
1092 
1093         // Avoid a shutdown race condition.
1094         // If SG is invalidated and 'active' becomes false, the thread's run()
1095         // method will exit. handleExposure() relies on QThread::isRunning() (because it
1096         // potentially needs to start the thread again) and our mutex cannot be used to
1097         // track the thread stopping, so we wait a few nanoseconds extra so the thread
1098         // can exit properly.
1099         if (!w->thread->active)
1100             w->thread->wait();
1101     }
1102     w->thread->mutex.unlock();
1103 }
1104 
polishAndSync(WindowData * w,bool inExpose)1105 void QSGD3D12ThreadedRenderLoop::polishAndSync(WindowData *w, bool inExpose)
1106 {
1107     if (Q_UNLIKELY(debug_loop()))
1108         qDebug() << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
1109 
1110     QQuickWindow *window = w->window;
1111     if (!w->thread || !w->thread->exposedWindow) {
1112         if (Q_UNLIKELY(debug_loop()))
1113             qDebug("polishAndSync - not exposed, abort");
1114         return;
1115     }
1116 
1117     Q_TRACE_SCOPE(QSG_polishAndSync);
1118 
1119     // Flush pending touch events.
1120     QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
1121     // The delivery of the event might have caused the window to stop rendering
1122     w = windowFor(windows, window);
1123     if (!w || !w->thread || !w->thread->exposedWindow) {
1124         if (Q_UNLIKELY(debug_loop()))
1125             qDebug("polishAndSync - removed after touch event flushing, abort");
1126         return;
1127     }
1128 
1129     QElapsedTimer timer;
1130     qint64 polishTime = 0;
1131     qint64 waitTime = 0;
1132     qint64 syncTime = 0;
1133     if (Q_UNLIKELY(debug_time()))
1134         timer.start();
1135     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1136     Q_TRACE(QSG_polishItems_entry);
1137 
1138     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1139     wd->polishItems();
1140 
1141     if (Q_UNLIKELY(debug_time()))
1142         polishTime = timer.nsecsElapsed();
1143     Q_TRACE(QSG_polishItems_exit);
1144     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1145                               QQuickProfiler::SceneGraphPolishAndSyncPolish);
1146     Q_TRACE(QSG_wait_entry);
1147 
1148     w->updateDuringSync = false;
1149 
1150     emit window->afterAnimating();
1151 
1152     if (Q_UNLIKELY(debug_loop()))
1153         qDebug("polishAndSync - lock for sync");
1154     w->thread->mutex.lock();
1155     lockedForSync = true;
1156     w->thread->postEvent(new QSGD3D12SyncEvent(window, inExpose, w->forceRenderPass));
1157     w->forceRenderPass = false;
1158 
1159     if (Q_UNLIKELY(debug_loop()))
1160         qDebug("polishAndSync - wait for sync");
1161     if (Q_UNLIKELY(debug_time()))
1162         waitTime = timer.nsecsElapsed();
1163     Q_TRACE(QSG_wait_exit);
1164     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1165                               QQuickProfiler::SceneGraphPolishAndSyncWait);
1166     Q_TRACE(QSG_sync_entry);
1167 
1168     w->thread->waitCondition.wait(&w->thread->mutex);
1169     lockedForSync = false;
1170     w->thread->mutex.unlock();
1171     if (Q_UNLIKELY(debug_loop()))
1172         qDebug("polishAndSync - unlock after sync");
1173 
1174     if (Q_UNLIKELY(debug_time()))
1175         syncTime = timer.nsecsElapsed();
1176     Q_TRACE(QSG_sync_exit);
1177     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1178                               QQuickProfiler::SceneGraphPolishAndSyncSync);
1179     Q_TRACE(QSG_animations_entry);
1180 
1181     if (!animationTimer && anim->isRunning()) {
1182         if (Q_UNLIKELY(debug_loop()))
1183             qDebug("polishAndSync - advancing animations");
1184         anim->advance();
1185         // We need to trigger another sync to keep animations running...
1186         w->window->requestUpdate();
1187         emit timeToIncubate();
1188     } else if (w->updateDuringSync) {
1189         w->window->requestUpdate();
1190     }
1191 
1192     if (Q_UNLIKELY(debug_time()))
1193         qDebug().nospace()
1194                 << "Frame prepared with 'd3d12' renderloop"
1195                 << ", polish=" << (polishTime / 1000000)
1196                 << ", lock=" << (waitTime - polishTime) / 1000000
1197                 << ", blockedForSync=" << (syncTime - waitTime) / 1000000
1198                 << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
1199                 << " - (on gui thread) " << window;
1200 
1201     Q_TRACE(QSG_animations_exit);
1202     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1203                            QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1204 }
1205 
1206 #include "qsgd3d12threadedrenderloop.moc"
1207 
1208 QT_END_NAMESPACE
1209