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