1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtQuick module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41
42 #include <QtCore/QMutex>
43 #include <QtCore/QWaitCondition>
44 #include <QtCore/QAnimationDriver>
45 #include <QtCore/QQueue>
46 #include <QtCore/QTime>
47
48 #include <QtGui/QGuiApplication>
49 #include <QtGui/QScreen>
50 #include <QtGui/QOffscreenSurface>
51
52 #include <qpa/qwindowsysteminterface.h>
53
54 #include <QtQuick/QQuickWindow>
55 #include <private/qquickwindow_p.h>
56
57 #include <QtQuick/private/qsgrenderer_p.h>
58
59 #include "qsgthreadedrenderloop_p.h"
60 #include "qsgrhisupport_p.h"
61 #include <private/qquickanimatorcontroller_p.h>
62
63 #include <private/qquickprofiler_p.h>
64 #include <private/qqmldebugserviceinterfaces_p.h>
65 #include <private/qqmldebugconnector_p.h>
66
67 #if QT_CONFIG(quick_shadereffect)
68 #include <private/qquickopenglshadereffectnode_p.h>
69 #endif
70 #include <private/qsgrhishadereffectnode_p.h>
71 #include <private/qsgdefaultrendercontext_p.h>
72
73 #include <qtquick_tracepoints_p.h>
74
75 /*
76 Overall design:
77
78 There are two classes here. QSGThreadedRenderLoop and
79 QSGRenderThread. All communication between the two is based on
80 event passing and we have a number of custom events.
81
82 In this implementation, the render thread is never blocked and the
83 GUI thread will initiate a polishAndSync which will block and wait
84 for the render thread to pick it up and release the block only
85 after the render thread is done syncing. The reason for this
86 is:
87
88 1. Clear blocking paradigm. We only have one real "block" point
89 (polishAndSync()) and all blocking is initiated by GUI and picked
90 up by Render at specific times based on events. This makes the
91 execution deterministic.
92
93 2. Render does not have to interact with GUI. This is done so that
94 the render thread can run its own animation system which stays
95 alive even when the GUI thread is blocked doing i/o, object
96 instantiation, QPainter-painting or any other non-trivial task.
97
98 ---
99
100 There is one thread per window and one opengl context per thread.
101
102 ---
103
104 The render thread has affinity to the GUI thread until a window
105 is shown. From that moment and until the window is destroyed, it
106 will have affinity to the render thread. (moved back at the end
107 of run for cleanup).
108
109 ---
110
111 The render loop is active while any window is exposed. All visible
112 windows are tracked, but only exposed windows are actually added to
113 the render thread and rendered. That means that if all windows are
114 obscured, we might end up cleaning up the SG and GL context (if all
115 windows have disabled persistency). Especially for multiprocess,
116 low-end systems, this should be quite important.
117
118 */
119
120 QT_BEGIN_NAMESPACE
121
122 #define QSG_RT_PAD " (RT) %s"
123
qsgrl_animation_interval()124 static inline int qsgrl_animation_interval() {
125 qreal refreshRate = QGuiApplication::primaryScreen()->refreshRate();
126 // To work around that some platforms wrongfully return 0 or something
127 // bogus for refreshrate
128 if (refreshRate < 1)
129 return 16;
130 return int(1000 / refreshRate);
131 }
132
133
134 static QElapsedTimer threadTimer;
135 static qint64 syncTime;
136 static qint64 renderTime;
137 static qint64 sinceLastTime;
138
139 extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
140
141 // RL: Render Loop
142 // RT: Render Thread
143
144 // Passed from the RL to the RT when a window is removed obscured and
145 // should be removed from the render loop.
146 const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
147
148 // Passed from the RL to RT when GUI has been locked, waiting for sync
149 // (updatePaintNode())
150 const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
151
152 // Passed by the RL to the RT to free up maybe release SG and GL contexts
153 // if no windows are rendering.
154 const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
155
156 // Passed by the RL to the RT when a QQuickWindow::grabWindow() is
157 // called.
158 const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
159
160 // Passed by the window when there is a render job to run
161 const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
162
163 // When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
164 // the event filter installed on the QQuickWindow.
165 const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
166
windowFor(const QList<T> & list,QQuickWindow * window)167 template <typename T> T *windowFor(const QList<T> &list, QQuickWindow *window)
168 {
169 for (int i=0; i<list.size(); ++i) {
170 const T &t = list.at(i);
171 if (t.window == window)
172 return const_cast<T *>(&t);
173 }
174 return nullptr;
175 }
176
177
178 class WMWindowEvent : public QEvent
179 {
180 public:
WMWindowEvent(QQuickWindow * c,QEvent::Type type)181 WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
182 QQuickWindow *window;
183 };
184
185 class WMTryReleaseEvent : public WMWindowEvent
186 {
187 public:
WMTryReleaseEvent(QQuickWindow * win,bool destroy,bool needsFallbackSurface)188 WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
189 : WMWindowEvent(win, WM_TryRelease)
190 , inDestructor(destroy)
191 , needsFallback(needsFallbackSurface)
192 {}
193
194 bool inDestructor;
195 bool needsFallback;
196 };
197
198 class WMSyncEvent : public WMWindowEvent
199 {
200 public:
WMSyncEvent(QQuickWindow * c,bool inExpose,bool force)201 WMSyncEvent(QQuickWindow *c, bool inExpose, bool force)
202 : WMWindowEvent(c, WM_RequestSync)
203 , size(c->size())
204 , dpr(float(c->effectiveDevicePixelRatio()))
205 , syncInExpose(inExpose)
206 , forceRenderPass(force)
207 {}
208 QSize size;
209 float dpr;
210 bool syncInExpose;
211 bool forceRenderPass;
212 };
213
214
215 class WMGrabEvent : public WMWindowEvent
216 {
217 public:
WMGrabEvent(QQuickWindow * c,QImage * result)218 WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
219 QImage *image;
220 };
221
222 class WMJobEvent : public WMWindowEvent
223 {
224 public:
WMJobEvent(QQuickWindow * c,QRunnable * postedJob)225 WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
226 : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
~WMJobEvent()227 ~WMJobEvent() { delete job; }
228 QRunnable *job;
229 };
230
231 class WMReleaseSwapchainEvent : public WMWindowEvent
232 {
233 public:
WMReleaseSwapchainEvent(QQuickWindow * c)234 WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
235 };
236
237 class QSGRenderThreadEventQueue : public QQueue<QEvent *>
238 {
239 public:
QSGRenderThreadEventQueue()240 QSGRenderThreadEventQueue()
241 : waiting(false)
242 {
243 }
244
addEvent(QEvent * e)245 void addEvent(QEvent *e) {
246 mutex.lock();
247 enqueue(e);
248 if (waiting)
249 condition.wakeOne();
250 mutex.unlock();
251 }
252
takeEvent(bool wait)253 QEvent *takeEvent(bool wait) {
254 mutex.lock();
255 if (size() == 0 && wait) {
256 waiting = true;
257 condition.wait(&mutex);
258 waiting = false;
259 }
260 QEvent *e = dequeue();
261 mutex.unlock();
262 return e;
263 }
264
hasMoreEvents()265 bool hasMoreEvents() {
266 mutex.lock();
267 bool has = !isEmpty();
268 mutex.unlock();
269 return has;
270 }
271
272 private:
273 QMutex mutex;
274 QWaitCondition condition;
275 bool waiting;
276 };
277
278
279 class QSGRenderThread : public QThread
280 {
281 Q_OBJECT
282 public:
QSGRenderThread(QSGThreadedRenderLoop * w,QSGRenderContext * renderContext)283 QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext)
284 : wm(w)
285 , gl(nullptr)
286 , enableRhi(false)
287 , rhi(nullptr)
288 , offscreenSurface(nullptr)
289 , animatorDriver(nullptr)
290 , pendingUpdate(0)
291 , sleeping(false)
292 , syncResultedInChanges(false)
293 , active(false)
294 , window(nullptr)
295 , stopEventProcessing(false)
296 {
297 sgrc = static_cast<QSGDefaultRenderContext *>(renderContext);
298 #if defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)
299 // The SDP 6.6.0 x86 MESA driver requires a larger stack than the default.
300 setStackSize(1024 * 1024);
301 #endif
302 vsyncDelta = qsgrl_animation_interval();
303 }
304
~QSGRenderThread()305 ~QSGRenderThread()
306 {
307 delete sgrc;
308 delete offscreenSurface;
309 }
310
311 void invalidateGraphics(QQuickWindow *window, bool inDestructor, QOffscreenSurface *backupSurface);
312
313 bool event(QEvent *) override;
314 void run() override;
315
316 void syncAndRender(QImage *grabImage = nullptr);
317 void sync(bool inExpose, bool inGrab);
318
requestRepaint()319 void requestRepaint()
320 {
321 if (sleeping)
322 stopEventProcessing = true;
323 if (window)
324 pendingUpdate |= RepaintRequest;
325 }
326
327 void processEventsAndWaitForMore();
328 void processEvents();
329 void postEvent(QEvent *e);
330
331 public slots:
sceneGraphChanged()332 void sceneGraphChanged() {
333 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sceneGraphChanged");
334 syncResultedInChanges = true;
335 }
336
337 public:
338 enum UpdateRequest {
339 SyncRequest = 0x01,
340 RepaintRequest = 0x02,
341 ExposeRequest = 0x04 | RepaintRequest | SyncRequest
342 };
343
344 void ensureRhi();
345 void handleDeviceLoss();
346
347 QSGThreadedRenderLoop *wm;
348 QOpenGLContext *gl;
349 bool enableRhi;
350 QRhi *rhi;
351 QSGDefaultRenderContext *sgrc;
352 QOffscreenSurface *offscreenSurface;
353
354 QAnimationDriver *animatorDriver;
355
356 uint pendingUpdate;
357 bool sleeping;
358 bool syncResultedInChanges;
359
360 volatile bool active;
361
362 float vsyncDelta;
363
364 QMutex mutex;
365 QWaitCondition waitCondition;
366
367 QElapsedTimer m_timer;
368
369 QQuickWindow *window; // Will be 0 when window is not exposed
370 QSize windowSize;
371 float dpr = 1;
372 int rhiSampleCount = 1;
373 bool rhiDeviceLost = false;
374 bool rhiDoomed = false;
375 bool guiNotifiedAboutRhiFailure = false;
376
377 // Local event queue stuff...
378 bool stopEventProcessing;
379 QSGRenderThreadEventQueue eventQueue;
380 };
381
event(QEvent * e)382 bool QSGRenderThread::event(QEvent *e)
383 {
384 switch ((int) e->type()) {
385
386 case WM_Obscure: {
387 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Obscure");
388
389 Q_ASSERT(!window || window == static_cast<WMWindowEvent *>(e)->window);
390
391 mutex.lock();
392 if (window) {
393 QQuickWindowPrivate::get(window)->fireAboutToStop();
394 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window removed");
395 window = nullptr;
396 }
397 waitCondition.wakeOne();
398 mutex.unlock();
399
400 return true; }
401
402 case WM_RequestSync: {
403 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestSync");
404 WMSyncEvent *se = static_cast<WMSyncEvent *>(e);
405 if (sleeping)
406 stopEventProcessing = true;
407 window = se->window;
408 windowSize = se->size;
409 dpr = se->dpr;
410
411 pendingUpdate |= SyncRequest;
412 if (se->syncInExpose) {
413 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- triggered from expose");
414 pendingUpdate |= ExposeRequest;
415 }
416 if (se->forceRenderPass) {
417 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- repaint regardless");
418 pendingUpdate |= RepaintRequest;
419 }
420 return true; }
421
422 case WM_TryRelease: {
423 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_TryRelease");
424 mutex.lock();
425 wm->m_lockedForSync = true;
426 WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e);
427 if (!window || wme->inDestructor) {
428 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- setting exit flag and invalidating OpenGL");
429 invalidateGraphics(wme->window, wme->inDestructor, wme->needsFallback ? offscreenSurface : nullptr);
430 active = gl || rhi;
431 Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateGraphics()", "Thread's active state is not set to false when shutting down");
432 if (sleeping)
433 stopEventProcessing = true;
434 } else {
435 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- not releasing because window is still active");
436 if (window) {
437 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
438 if (d->renderer) {
439 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting renderer to release cached resources");
440 d->renderer->releaseCachedResources();
441 }
442 }
443 }
444 waitCondition.wakeOne();
445 wm->m_lockedForSync = false;
446 mutex.unlock();
447 return true;
448 }
449
450 case WM_Grab: {
451 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Grab");
452 WMGrabEvent *ce = static_cast<WMGrabEvent *>(e);
453 Q_ASSERT(ce->window);
454 Q_ASSERT(ce->window == window || !window);
455 mutex.lock();
456 if (ce->window) {
457 const bool alpha = ce->window->format().alphaBufferSize() > 0 && ce->window->color().alpha() != 255;
458 const QSize readbackSize = windowSize * ce->window->effectiveDevicePixelRatio();
459 if (rhi) {
460 rhi->makeThreadLocalNativeContextCurrent();
461 syncAndRender(ce->image);
462 } else {
463 gl->makeCurrent(ce->window);
464
465 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync scene graph");
466 QQuickWindowPrivate *d = QQuickWindowPrivate::get(ce->window);
467 d->syncSceneGraph();
468 sgrc->endSync();
469
470 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering scene graph");
471 QQuickWindowPrivate::get(ce->window)->renderSceneGraph(ce->window->size());
472
473 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- grabbing result");
474 *ce->image = qt_gl_read_framebuffer(readbackSize, alpha, alpha);
475 }
476 ce->image->setDevicePixelRatio(ce->window->effectiveDevicePixelRatio());
477 }
478 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- waking gui to handle result");
479 waitCondition.wakeOne();
480 mutex.unlock();
481 return true;
482 }
483
484 case WM_PostJob: {
485 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_PostJob");
486 WMJobEvent *ce = static_cast<WMJobEvent *>(e);
487 Q_ASSERT(ce->window == window);
488 if (window) {
489 if (rhi)
490 rhi->makeThreadLocalNativeContextCurrent();
491 else
492 gl->makeCurrent(window);
493 ce->job->run();
494 delete ce->job;
495 ce->job = nullptr;
496 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- job done");
497 }
498 return true;
499 }
500
501 case WM_ReleaseSwapchain: {
502 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_ReleaseSwapchain");
503 WMReleaseSwapchainEvent *ce = static_cast<WMReleaseSwapchainEvent *>(e);
504 // forget about 'window' here that may be null when already unexposed
505 Q_ASSERT(ce->window);
506 mutex.lock();
507 if (ce->window) {
508 wm->releaseSwapchain(ce->window);
509 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- swapchain released");
510 }
511 waitCondition.wakeOne();
512 mutex.unlock();
513 return true;
514 }
515
516 default:
517 break;
518 }
519 return QThread::event(e);
520 }
521
invalidateGraphics(QQuickWindow * window,bool inDestructor,QOffscreenSurface * fallback)522 void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor, QOffscreenSurface *fallback)
523 {
524 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateGraphics()");
525
526 if (!gl && !rhi)
527 return;
528
529 if (!window) {
530 qCWarning(QSG_LOG_RENDERLOOP, "QSGThreadedRenderLoop:QSGRenderThread: no window to make current...");
531 return;
532 }
533
534
535 bool wipeSG = inDestructor || !window->isPersistentSceneGraph();
536 bool wipeGL = inDestructor || (wipeSG && !window->isPersistentOpenGLContext());
537
538 bool current = true;
539 if (gl)
540 current = gl->makeCurrent(fallback ? static_cast<QSurface *>(fallback) : static_cast<QSurface *>(window));
541 else if (rhi)
542 rhi->makeThreadLocalNativeContextCurrent();
543
544 if (Q_UNLIKELY(!current)) {
545 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- cleanup without an OpenGL context");
546 }
547
548 QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window);
549
550 #if QT_CONFIG(quick_shadereffect)
551 QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
552 #if QT_CONFIG(opengl)
553 if (current)
554 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
555 #endif
556 #endif
557
558 // The canvas nodes must be cleaned up regardless if we are in the destructor..
559 if (wipeSG) {
560 dd->cleanupNodesOnShutdown();
561 } else {
562 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup");
563 if (current && gl)
564 gl->doneCurrent();
565 return;
566 }
567
568 sgrc->invalidate();
569 QCoreApplication::processEvents();
570 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
571 if (inDestructor)
572 dd->animationController.reset();
573 if (current && gl)
574 gl->doneCurrent();
575 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidating scene graph");
576
577 if (wipeGL) {
578 if (dd->swapchain) {
579 if (window->handle()) {
580 // We get here when exiting via QCoreApplication::quit() instead of
581 // through QWindow::close().
582 wm->releaseSwapchain(window);
583 } else {
584 qWarning("QSGThreadedRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
585 window, dd->swapchain);
586 }
587 }
588 delete gl;
589 gl = nullptr;
590 delete rhi;
591 rhi = nullptr;
592 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidated OpenGL");
593 } else {
594 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup");
595 }
596 }
597
598 /*
599 Enters the mutex lock to make sure GUI is blocking and performs
600 sync, then wakes GUI.
601 */
sync(bool inExpose,bool inGrab)602 void QSGRenderThread::sync(bool inExpose, bool inGrab)
603 {
604 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()");
605 if (!inGrab)
606 mutex.lock();
607
608 Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
609
610 bool current = true;
611 if (gl) {
612 if (windowSize.width() > 0 && windowSize.height() > 0)
613 current = gl->makeCurrent(window);
614 else
615 current = false;
616 // Check for context loss.
617 if (!current && !gl->isValid()) {
618 QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown();
619 sgrc->invalidate();
620 current = gl->create() && gl->makeCurrent(window);
621 if (current) {
622 QSGDefaultRenderContext::InitParams rcParams;
623 rcParams.sampleCount = qMax(1, gl->format().samples());
624 rcParams.openGLContext = gl;
625 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
626 rcParams.maybeSurface = window;
627 sgrc->initialize(&rcParams);
628 }
629 }
630 } else if (rhi) {
631 // With the rhi making the (OpenGL) context current serves only one
632 // purpose: to enable external OpenGL rendering connected to one of
633 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
634 // etc.) to function like it did on the direct OpenGL path. For our
635 // own rendering this call would not be necessary.
636 rhi->makeThreadLocalNativeContextCurrent();
637 } else {
638 current = false;
639 }
640 if (current) {
641 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
642 bool hadRenderer = d->renderer != nullptr;
643 // If the scene graph was touched since the last sync() make sure it sends the
644 // changed signal.
645 if (d->renderer)
646 d->renderer->clearChangedFlag();
647 d->syncSceneGraph();
648 sgrc->endSync();
649 if (!hadRenderer && d->renderer) {
650 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- renderer was created");
651 syncResultedInChanges = true;
652 connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneGraphChanged()), Qt::DirectConnection);
653 }
654
655 // Process deferred deletes now, directly after the sync as
656 // deleteLater on the GUI must now also have resulted in SG changes
657 // and the delete is a safe operation.
658 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
659 } else {
660 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window has bad size, sync aborted");
661 }
662
663 // Two special cases: For grabs we do not care about blocking the gui
664 // (main) thread. When this is from an expose, we will keep locked until
665 // the frame is rendered (submitted), so in that case waking happens later
666 // in syncAndRender(). Otherwise, wake now and let the main thread go on
667 // while we render.
668 if (!inExpose && !inGrab) {
669 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui");
670 waitCondition.wakeOne();
671 mutex.unlock();
672 }
673 }
674
handleDeviceLoss()675 void QSGRenderThread::handleDeviceLoss()
676 {
677 if (!rhi || !rhi->isDeviceLost())
678 return;
679
680 qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
681 QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown();
682 sgrc->invalidate();
683 wm->releaseSwapchain(window);
684 rhiDeviceLost = true;
685 delete rhi;
686 rhi = nullptr;
687 }
688
syncAndRender(QImage * grabImage)689 void QSGRenderThread::syncAndRender(QImage *grabImage)
690 {
691 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
692 if (profileFrames) {
693 sinceLastTime = threadTimer.nsecsElapsed();
694 threadTimer.start();
695 }
696 Q_TRACE_SCOPE(QSG_syncAndRender);
697 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
698 Q_TRACE(QSG_sync_entry);
699
700 QElapsedTimer waitTimer;
701 waitTimer.start();
702
703 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "syncAndRender()");
704
705 syncResultedInChanges = false;
706 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
707
708 const bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage;
709 const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage;
710 const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
711 const bool grabRequested = grabImage != nullptr;
712 if (!grabRequested)
713 pendingUpdate = 0;
714
715 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
716 // Begin the frame before syncing -> sync is where we may invoke
717 // updatePaintNode() on the items and they may want to do resource updates.
718 // Also relevant for applications that connect to the before/afterSynchronizing
719 // signals and want to do graphics stuff already there.
720 if (cd->swapchain && windowSize.width() > 0 && windowSize.height() > 0) {
721 // always prefer what the surface tells us, not the QWindow
722 const QSize effectiveOutputSize = cd->swapchain->surfacePixelSize();
723 // An update request could still be delivered right before we get an
724 // unexpose. With Vulkan on Windows for example attempting to render
725 // leads to failures at this stage since the surface size is already 0.
726 if (effectiveOutputSize.isEmpty())
727 return;
728
729 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
730 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
731 if (cd->swapchainJustBecameRenderable)
732 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
733
734 cd->hasActiveSwapchain = cd->swapchain->buildOrResize();
735 if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
736 handleDeviceLoss();
737 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
738 return;
739 }
740
741 cd->swapchainJustBecameRenderable = false;
742 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
743
744 if (!cd->hasActiveSwapchain)
745 qWarning("Failed to build or resize swapchain");
746 else
747 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << cd->swapchain->currentPixelSize();
748 }
749
750 Q_ASSERT(rhi == cd->rhi);
751 // ### the flag should only be set when the app requests it, but there's no way to do that right now
752 QRhi::BeginFrameFlags frameFlags = QRhi::ExternalContentsInPass;
753 QRhi::FrameOpResult frameResult = rhi->beginFrame(cd->swapchain, frameFlags);
754 if (frameResult != QRhi::FrameOpSuccess) {
755 if (frameResult == QRhi::FrameOpDeviceLost)
756 handleDeviceLoss();
757 else if (frameResult == QRhi::FrameOpError)
758 qWarning("Failed to start frame");
759 // try again later
760 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
761 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
762 // Before returning we need to ensure the same wake up logic that
763 // would have happened if beginFrame() had suceeded.
764 if (syncRequested && !grabRequested) {
765 // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
766 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui");
767 mutex.lock();
768 // Go ahead with waking because we will return right after this.
769 waitCondition.wakeOne();
770 mutex.unlock();
771 }
772 return;
773 }
774 }
775
776 if (syncRequested) {
777 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync");
778 sync(exposeRequested, grabRequested);
779 }
780 #ifndef QSG_NO_RENDER_TIMING
781 if (profileFrames)
782 syncTime = threadTimer.nsecsElapsed();
783 #endif
784 Q_TRACE(QSG_sync_exit);
785 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
786 QQuickProfiler::SceneGraphRenderLoopSync);
787
788 if (!syncResultedInChanges
789 && !repaintRequested
790 && !(pendingUpdate & RepaintRequest) // may have been set in sync()
791 && sgrc->isValid()
792 && !grabRequested
793 && (gl || (rhi && !rhi->isRecordingFrame())))
794 {
795 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- no changes, render aborted");
796 int waitTime = vsyncDelta - (int) waitTimer.elapsed();
797 if (waitTime > 0)
798 msleep(waitTime);
799 return;
800 }
801
802 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering started");
803
804 Q_TRACE(QSG_render_entry);
805
806 // RepaintRequest may have been set in pendingUpdate in an
807 // updatePaintNode() invoked from sync(). We are about to do a repaint
808 // right now, so reset the flag. (bits other than RepaintRequest cannot
809 // be set in pendingUpdate at this point)
810 if (!grabRequested)
811 pendingUpdate = 0;
812
813 if (animatorDriver->isRunning() && !grabRequested) {
814 d->animationController->lock();
815 animatorDriver->advance();
816 d->animationController->unlock();
817 }
818
819 bool current = true;
820 if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0 && (gl || rhi)) {
821 if (gl)
822 current = gl->makeCurrent(window);
823 else
824 rhi->makeThreadLocalNativeContextCurrent();
825 } else {
826 current = false;
827 }
828 // Check for context loss (GL, RHI case handled after the beginFrame() above)
829 if (gl) {
830 if (!current && !gl->isValid()) {
831 // Cannot do anything here because gui is not locked. Request a new
832 // sync+render round on the gui thread and let the sync handle it.
833 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
834 }
835 }
836 if (current) {
837 d->renderSceneGraph(windowSize, rhi ? cd->swapchain->currentPixelSize() : QSize());
838
839 if (profileFrames)
840 renderTime = threadTimer.nsecsElapsed();
841 Q_TRACE(QSG_render_exit);
842 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
843 QQuickProfiler::SceneGraphRenderLoopRender);
844 Q_TRACE(QSG_swap_entry);
845
846 // With the rhi grabs can only be done by adding a readback and then
847 // blocking in a real frame. The legacy GL path never gets here with
848 // grabs as it rather invokes sync/render directly without going
849 // through syncAndRender().
850 if (grabRequested) {
851 Q_ASSERT(rhi && !gl && cd->swapchain);
852 *grabImage = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain);
853 }
854
855 if (cd->swapchain) {
856 QRhi::EndFrameFlags flags;
857 if (grabRequested)
858 flags |= QRhi::SkipPresent;
859 QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain, flags);
860 if (frameResult != QRhi::FrameOpSuccess) {
861 if (frameResult == QRhi::FrameOpDeviceLost)
862 handleDeviceLoss();
863 else if (frameResult == QRhi::FrameOpError)
864 qWarning("Failed to end frame");
865 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
866 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
867 }
868 } else {
869 if (!cd->customRenderStage || !cd->customRenderStage->swap())
870 gl->swapBuffers(window);
871 }
872
873 if (!grabRequested)
874 d->fireFrameSwapped();
875 } else {
876 Q_TRACE(QSG_render_exit);
877 Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
878 QQuickProfiler::SceneGraphRenderLoopSync, 1);
879 Q_TRACE(QSG_swap_entry);
880 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window not ready, skipping render");
881 }
882
883 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering done");
884
885 // Though it would be more correct to put this block directly after
886 // fireFrameSwapped in the if (current) branch above, we don't do
887 // that to avoid blocking the GUI thread in the case where it
888 // has started rendering with a bad window, causing makeCurrent to
889 // fail or if the window has a bad size.
890 if (exposeRequested) {
891 // With expose sync() did not wake gui, do it now.
892 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- wake Gui after expose");
893 waitCondition.wakeOne();
894 mutex.unlock();
895 }
896
897 qCDebug(QSG_LOG_TIME_RENDERLOOP,
898 "Frame rendered with 'threaded' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
899 int(threadTimer.elapsed()),
900 int((syncTime/1000000)),
901 int((renderTime - syncTime) / 1000000),
902 int(threadTimer.elapsed() - renderTime / 1000000));
903
904 Q_TRACE(QSG_swap_exit);
905 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
906 QQuickProfiler::SceneGraphRenderLoopSwap);
907
908 QSGRhiProfileConnection::instance()->send(rhi);
909 }
910
911
912
postEvent(QEvent * e)913 void QSGRenderThread::postEvent(QEvent *e)
914 {
915 eventQueue.addEvent(e);
916 }
917
918
919
processEvents()920 void QSGRenderThread::processEvents()
921 {
922 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEvents()");
923 while (eventQueue.hasMoreEvents()) {
924 QEvent *e = eventQueue.takeEvent(false);
925 event(e);
926 delete e;
927 }
928 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEvents()");
929 }
930
processEventsAndWaitForMore()931 void QSGRenderThread::processEventsAndWaitForMore()
932 {
933 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEventsAndWaitForMore()");
934 stopEventProcessing = false;
935 while (!stopEventProcessing) {
936 QEvent *e = eventQueue.takeEvent(true);
937 event(e);
938 delete e;
939 }
940 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEventsAndWaitForMore()");
941 }
942
ensureRhi()943 void QSGRenderThread::ensureRhi()
944 {
945 if (!rhi) {
946 if (rhiDoomed) // no repeated attempts if the initial attempt failed
947 return;
948 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
949 rhi = rhiSupport->createRhi(window, offscreenSurface);
950 if (rhi) {
951 rhiDeviceLost = false;
952 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
953 if (rhiSupport->isProfilingRequested())
954 QSGRhiProfileConnection::instance()->initialize(rhi); // ### this breaks down with multiple windows
955 } else {
956 if (!rhiDeviceLost) {
957 rhiDoomed = true;
958 qWarning("Failed to create QRhi on the render thread; scenegraph is not functional");
959 }
960 // otherwise no error, will retry on a subsequent rendering attempt
961 return;
962 }
963 }
964 if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) {
965 rhi->makeThreadLocalNativeContextCurrent();
966 QSGDefaultRenderContext::InitParams rcParams;
967 rcParams.rhi = rhi;
968 rcParams.sampleCount = rhiSampleCount;
969 rcParams.openGLContext = nullptr;
970 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
971 rcParams.maybeSurface = window;
972 sgrc->initialize(&rcParams);
973 }
974 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
975 if (rhi && !cd->swapchain) {
976 cd->rhi = rhi;
977 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
978 // QQ is always premul alpha. Decide based on alphaBufferSize in
979 // requestedFormat(). (the platform plugin can override format() but
980 // what matters here is what the application wanted, hence using the
981 // requested one)
982 const bool alpha = window->requestedFormat().alphaBufferSize() > 0;
983 if (alpha)
984 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
985 cd->swapchain = rhi->newSwapChain();
986 cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
987 QSize(),
988 rhiSampleCount,
989 QRhiRenderBuffer::UsedWithSwapChainOnly);
990 cd->swapchain->setWindow(window);
991 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
992 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
993 rhiSampleCount, alpha ? "yes" : "no");
994 cd->swapchain->setSampleCount(rhiSampleCount);
995 cd->swapchain->setFlags(flags);
996 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
997 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
998 }
999 }
1000
run()1001 void QSGRenderThread::run()
1002 {
1003 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()");
1004 animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(nullptr);
1005 animatorDriver->install();
1006 if (QQmlDebugConnector::service<QQmlProfilerService>())
1007 QQuickProfiler::registerAnimationCallback();
1008
1009 while (active) {
1010 #ifdef Q_OS_DARWIN
1011 QMacAutoReleasePool frameReleasePool;
1012 #endif
1013
1014 if (window) {
1015 if (enableRhi) {
1016
1017 ensureRhi();
1018
1019 // We absolutely have to syncAndRender() here, even when QRhi
1020 // failed to initialize otherwise the gui thread will be left
1021 // in a blocked state. It is up to syncAndRender() to
1022 // gracefully skip all graphics stuff when rhi is null.
1023
1024 syncAndRender();
1025
1026 // Now we can do something about rhi init failures. (reinit
1027 // failure after device reset does not count)
1028 if (rhiDoomed && !guiNotifiedAboutRhiFailure) {
1029 guiNotifiedAboutRhiFailure = true;
1030 QEvent *e = new QEvent(QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure));
1031 QCoreApplication::postEvent(window, e);
1032 }
1033
1034 } else {
1035 if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(window)) {
1036 QSGDefaultRenderContext::InitParams rcParams;
1037 rcParams.sampleCount = qMax(1, gl->format().samples());
1038 rcParams.openGLContext = gl;
1039 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
1040 rcParams.maybeSurface = window;
1041 sgrc->initialize(&rcParams);
1042 }
1043 syncAndRender();
1044 }
1045 }
1046
1047 processEvents();
1048 QCoreApplication::processEvents();
1049
1050 if (active && (pendingUpdate == 0 || !window)) {
1051 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "done drawing, sleep...");
1052 sleeping = true;
1053 processEventsAndWaitForMore();
1054 sleeping = false;
1055 }
1056 }
1057
1058 Q_ASSERT_X(!gl && !rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread...");
1059
1060 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed");
1061
1062 delete animatorDriver;
1063 animatorDriver = nullptr;
1064
1065 sgrc->moveToThread(wm->thread());
1066 moveToThread(wm->thread());
1067 }
1068
QSGThreadedRenderLoop()1069 QSGThreadedRenderLoop::QSGThreadedRenderLoop()
1070 : sg(QSGContext::createDefaultContext())
1071 , m_animation_timer(0)
1072 {
1073 #if defined(QSG_RENDER_LOOP_DEBUG)
1074 qsgrl_timer.start();
1075 #endif
1076
1077 m_animation_driver = sg->createAnimationDriver(this);
1078
1079 connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted()));
1080 connect(m_animation_driver, SIGNAL(stopped()), this, SLOT(animationStopped()));
1081
1082 m_animation_driver->install();
1083 }
1084
~QSGThreadedRenderLoop()1085 QSGThreadedRenderLoop::~QSGThreadedRenderLoop()
1086 {
1087 qDeleteAll(pendingRenderContexts);
1088 delete sg;
1089 }
1090
createRenderContext(QSGContext * sg) const1091 QSGRenderContext *QSGThreadedRenderLoop::createRenderContext(QSGContext *sg) const
1092 {
1093 auto context = sg->createRenderContext();
1094 pendingRenderContexts.insert(context);
1095 return context;
1096 }
1097
maybePostPolishRequest(Window * w)1098 void QSGThreadedRenderLoop::maybePostPolishRequest(Window *w)
1099 {
1100 w->window->requestUpdate();
1101 }
1102
animationDriver() const1103 QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const
1104 {
1105 return m_animation_driver;
1106 }
1107
sceneGraphContext() const1108 QSGContext *QSGThreadedRenderLoop::sceneGraphContext() const
1109 {
1110 return sg;
1111 }
1112
anyoneShowing() const1113 bool QSGThreadedRenderLoop::anyoneShowing() const
1114 {
1115 for (int i=0; i<m_windows.size(); ++i) {
1116 QQuickWindow *c = m_windows.at(i).window;
1117 if (c->isVisible() && c->isExposed())
1118 return true;
1119 }
1120 return false;
1121 }
1122
interleaveIncubation() const1123 bool QSGThreadedRenderLoop::interleaveIncubation() const
1124 {
1125 return m_animation_driver->isRunning() && anyoneShowing();
1126 }
1127
animationStarted()1128 void QSGThreadedRenderLoop::animationStarted()
1129 {
1130 qCDebug(QSG_LOG_RENDERLOOP, "- animationStarted()");
1131 startOrStopAnimationTimer();
1132
1133 for (int i=0; i<m_windows.size(); ++i)
1134 maybePostPolishRequest(const_cast<Window *>(&m_windows.at(i)));
1135 }
1136
animationStopped()1137 void QSGThreadedRenderLoop::animationStopped()
1138 {
1139 qCDebug(QSG_LOG_RENDERLOOP, "- animationStopped()");
1140 startOrStopAnimationTimer();
1141 }
1142
1143
startOrStopAnimationTimer()1144 void QSGThreadedRenderLoop::startOrStopAnimationTimer()
1145 {
1146 int exposedWindows = 0;
1147 const Window *theOne = nullptr;
1148 for (int i=0; i<m_windows.size(); ++i) {
1149 const Window &w = m_windows.at(i);
1150 if (w.window->isVisible() && w.window->isExposed()) {
1151 ++exposedWindows;
1152 theOne = &w;
1153 }
1154 }
1155
1156 if (m_animation_timer != 0 && (exposedWindows == 1 || !m_animation_driver->isRunning())) {
1157 qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping animation timer");
1158 killTimer(m_animation_timer);
1159 m_animation_timer = 0;
1160 // If animations are running, make sure we keep on animating
1161 if (m_animation_driver->isRunning())
1162 maybePostPolishRequest(const_cast<Window *>(theOne));
1163 } else if (m_animation_timer == 0 && exposedWindows != 1 && m_animation_driver->isRunning()) {
1164 qCDebug(QSG_LOG_RENDERLOOP, "*** Starting animation timer");
1165 m_animation_timer = startTimer(qsgrl_animation_interval());
1166 }
1167 }
1168
1169 /*
1170 Removes this window from the list of tracked windowes in this
1171 window manager. hide() will trigger obscure, which in turn will
1172 stop rendering.
1173
1174 This function will be called during QWindow::close() which will
1175 also destroy the QPlatformWindow so it is important that this
1176 triggers handleObscurity() and that rendering for that window
1177 is fully done and over with by the time this function exits.
1178 */
1179
hide(QQuickWindow * window)1180 void QSGThreadedRenderLoop::hide(QQuickWindow *window)
1181 {
1182 qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
1183
1184 if (window->isExposed())
1185 handleObscurity(windowFor(m_windows, window));
1186
1187 releaseResources(window);
1188 }
1189
1190
1191 /*
1192 If the window is first hide it, then perform a complete cleanup
1193 with releaseResources which will take down the GL context and
1194 exit the rendering thread.
1195 */
windowDestroyed(QQuickWindow * window)1196 void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
1197 {
1198 qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
1199
1200 Window *w = windowFor(m_windows, window);
1201 if (!w)
1202 return;
1203
1204 handleObscurity(w);
1205 releaseResources(w, true);
1206
1207 QSGRenderThread *thread = w->thread;
1208 while (thread->isRunning())
1209 QThread::yieldCurrentThread();
1210 Q_ASSERT(thread->thread() == QThread::currentThread());
1211 delete thread;
1212
1213 for (int i=0; i<m_windows.size(); ++i) {
1214 if (m_windows.at(i).window == window) {
1215 m_windows.removeAt(i);
1216 break;
1217 }
1218 }
1219
1220 // Now that we altered the window list, we may need to stop the animation
1221 // timer even if we didn't via handleObscurity. This covers the case where
1222 // we destroy a visible & exposed QQuickWindow.
1223 startOrStopAnimationTimer();
1224
1225 qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
1226 }
1227
releaseSwapchain(QQuickWindow * window)1228 void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
1229 {
1230 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1231 delete wd->rpDescForSwapchain;
1232 wd->rpDescForSwapchain = nullptr;
1233 delete wd->swapchain;
1234 wd->swapchain = nullptr;
1235 delete wd->depthStencilForSwapchain;
1236 wd->depthStencilForSwapchain = nullptr;
1237 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
1238 }
1239
exposureChanged(QQuickWindow * window)1240 void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
1241 {
1242 qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
1243
1244 // This is tricker than used to be. We want to detect having an empty
1245 // surface size (which may be the case even when window->size() is
1246 // non-empty, on some platforms with some graphics APIs!) as well as the
1247 // case when the window just became "newly exposed" (e.g. after a
1248 // minimize-restore on Windows, or when switching between fully obscured -
1249 // not fully obscured on macOS)
1250 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1251 if (!window->isExposed())
1252 wd->hasRenderableSwapchain = false;
1253
1254 bool skipThisExpose = false;
1255 if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) {
1256 wd->hasRenderableSwapchain = false;
1257 skipThisExpose = true;
1258 }
1259
1260 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
1261 && !wd->swapchain->surfacePixelSize().isEmpty())
1262 {
1263 wd->hasRenderableSwapchain = true;
1264 wd->swapchainJustBecameRenderable = true;
1265 }
1266
1267 if (window->isExposed()) {
1268 if (!skipThisExpose)
1269 handleExposure(window);
1270 } else {
1271 Window *w = windowFor(m_windows, window);
1272 if (w)
1273 handleObscurity(w);
1274 }
1275 }
1276
1277 /*
1278 Will post an event to the render thread that this window should
1279 start to render.
1280 */
handleExposure(QQuickWindow * window)1281 void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
1282 {
1283 qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
1284
1285 Window *w = windowFor(m_windows, window);
1286 if (!w) {
1287 qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
1288 Window win;
1289 win.window = window;
1290 win.actualWindowFormat = window->format();
1291 auto renderContext = QQuickWindowPrivate::get(window)->context;
1292 // The thread assumes ownership, so we don't need to delete it later.
1293 pendingRenderContexts.remove(renderContext);
1294 win.thread = new QSGRenderThread(this, renderContext);
1295 win.updateDuringSync = false;
1296 win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
1297 m_windows << win;
1298 w = &m_windows.last();
1299 }
1300
1301 // set this early as we'll be rendering shortly anyway and this avoids
1302 // specialcasing exposure in polishAndSync.
1303 w->thread->window = window;
1304
1305 if (w->window->width() <= 0 || w->window->height() <= 0
1306 || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
1307 #ifndef QT_NO_DEBUG
1308 qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
1309 << w->window << " with invalid geometry: " << w->window->geometry()
1310 << " on " << w->window->screen();
1311 #endif
1312 }
1313
1314 // Because we are going to bind a GL context to it, make sure it
1315 // is created.
1316 if (!w->window->handle())
1317 w->window->create();
1318
1319 // Start render thread if it is not running
1320 if (!w->thread->isRunning()) {
1321 qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread");
1322
1323 w->thread->enableRhi = QSGRhiSupport::instance()->isRhiEnabled();
1324 if (w->thread->enableRhi) {
1325 if (!w->thread->rhi) {
1326 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
1327 w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
1328 window->installEventFilter(this);
1329 }
1330 } else {
1331 if (!w->thread->gl) {
1332 w->thread->gl = new QOpenGLContext();
1333 if (qt_gl_global_share_context())
1334 w->thread->gl->setShareContext(qt_gl_global_share_context());
1335 w->thread->gl->setFormat(w->window->requestedFormat());
1336 w->thread->gl->setScreen(w->window->screen());
1337 if (!w->thread->gl->create()) {
1338 delete w->thread->gl;
1339 w->thread->gl = nullptr;
1340 handleContextCreationFailure(w->window);
1341 return;
1342 }
1343
1344 QQuickWindowPrivate::get(w->window)->fireOpenGLContextCreated(w->thread->gl);
1345
1346 w->thread->gl->moveToThread(w->thread);
1347 if (!w->thread->gl->shareContext())
1348 w->thread->gl->shareGroup()->moveToThread(w->thread);
1349 qCDebug(QSG_LOG_RENDERLOOP, "- OpenGL context created");
1350
1351 w->thread->offscreenSurface = new QOffscreenSurface();
1352 w->thread->offscreenSurface->setFormat(w->actualWindowFormat);
1353 w->thread->offscreenSurface->create();
1354 }
1355 }
1356
1357 QQuickAnimatorController *controller
1358 = QQuickWindowPrivate::get(w->window)->animationController.get();
1359 if (controller->thread() != w->thread)
1360 controller->moveToThread(w->thread);
1361
1362 w->thread->active = true;
1363 if (w->thread->thread() == QThread::currentThread()) {
1364 w->thread->sgrc->moveToThread(w->thread);
1365 w->thread->moveToThread(w->thread);
1366 }
1367 w->thread->start();
1368 if (!w->thread->isRunning())
1369 qFatal("Render thread failed to start, aborting application.");
1370
1371 } else {
1372 qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running");
1373 }
1374
1375 polishAndSync(w, true);
1376 qCDebug(QSG_LOG_RENDERLOOP, "- done with handleExposure()");
1377
1378 startOrStopAnimationTimer();
1379 }
1380
1381 /*
1382 This function posts an event to the render thread to remove the window
1383 from the list of windowses to render.
1384
1385 It also starts up the non-vsync animation tick if no more windows
1386 are showing.
1387 */
handleObscurity(Window * w)1388 void QSGThreadedRenderLoop::handleObscurity(Window *w)
1389 {
1390 qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
1391 if (w->thread->isRunning()) {
1392 w->thread->mutex.lock();
1393 w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
1394 w->thread->waitCondition.wait(&w->thread->mutex);
1395 w->thread->mutex.unlock();
1396 }
1397 startOrStopAnimationTimer();
1398 }
1399
eventFilter(QObject * watched,QEvent * event)1400 bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
1401 {
1402 switch (event->type()) {
1403 case QEvent::PlatformSurface:
1404 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
1405 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1406 QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
1407 if (window) {
1408 Window *w = windowFor(m_windows, window);
1409 if (w) {
1410 w->thread->mutex.lock();
1411 w->thread->postEvent(new WMReleaseSwapchainEvent(window));
1412 w->thread->waitCondition.wait(&w->thread->mutex);
1413 w->thread->mutex.unlock();
1414 }
1415 window->removeEventFilter(this);
1416 }
1417 }
1418 break;
1419 default:
1420 break;
1421 }
1422 return QObject::eventFilter(watched, event);
1423 }
1424
handleUpdateRequest(QQuickWindow * window)1425 void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
1426 {
1427 qCDebug(QSG_LOG_RENDERLOOP, "- polish and sync update request");
1428 Window *w = windowFor(m_windows, window);
1429 if (w)
1430 polishAndSync(w);
1431 }
1432
maybeUpdate(QQuickWindow * window)1433 void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
1434 {
1435 Window *w = windowFor(m_windows, window);
1436 if (w)
1437 maybeUpdate(w);
1438 }
1439
1440 /*
1441 Called whenever the QML scene has changed. Will post an event to
1442 ourselves that a sync is needed.
1443 */
maybeUpdate(Window * w)1444 void QSGThreadedRenderLoop::maybeUpdate(Window *w)
1445 {
1446 if (!QCoreApplication::instance())
1447 return;
1448
1449 if (!w || !w->thread->isRunning())
1450 return;
1451
1452 QThread *current = QThread::currentThread();
1453 if (current == w->thread && w->thread->rhi && w->thread->rhi->isDeviceLost())
1454 return;
1455 if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
1456 qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1457 return;
1458 }
1459
1460 qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
1461
1462 // Call this function from the Gui thread later as startTimer cannot be
1463 // called from the render thread.
1464 if (current == w->thread) {
1465 qCDebug(QSG_LOG_RENDERLOOP, "- on render thread");
1466 w->updateDuringSync = true;
1467 return;
1468 }
1469
1470 maybePostPolishRequest(w);
1471 }
1472
1473 /*
1474 Called when the QQuickWindow should be explicitly repainted. This function
1475 can also be called on the render thread when the GUI thread is blocked to
1476 keep render thread animations alive.
1477 */
update(QQuickWindow * window)1478 void QSGThreadedRenderLoop::update(QQuickWindow *window)
1479 {
1480 Window *w = windowFor(m_windows, window);
1481 if (!w)
1482 return;
1483
1484 if (w->thread == QThread::currentThread()) {
1485 qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
1486 w->thread->requestRepaint();
1487 return;
1488 }
1489
1490 qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
1491 // We set forceRenderPass because we want to make sure the QQuickWindow
1492 // actually does a full render pass after the next sync.
1493 w->forceRenderPass = true;
1494 maybeUpdate(w);
1495 }
1496
1497
releaseResources(QQuickWindow * window)1498 void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
1499 {
1500 Window *w = windowFor(m_windows, window);
1501 if (w)
1502 releaseResources(w, false);
1503 }
1504
1505 /*
1506 * Release resources will post an event to the render thread to
1507 * free up the SG and GL resources and exists the render thread.
1508 */
releaseResources(Window * w,bool inDestructor)1509 void QSGThreadedRenderLoop::releaseResources(Window *w, bool inDestructor)
1510 {
1511 qCDebug(QSG_LOG_RENDERLOOP) << "releaseResources()" << (inDestructor ? "in destructor" : "in api-call") << w->window;
1512
1513 w->thread->mutex.lock();
1514 if (w->thread->isRunning() && w->thread->active) {
1515 QQuickWindow *window = w->window;
1516
1517 // The platform window might have been destroyed before
1518 // hide/release/windowDestroyed is called, so we may need to have a
1519 // fallback surface to perform the cleanup of the scene graph and the
1520 // OpenGL resources. QOffscreenSurface must be created on the GUI
1521 // thread so that is done for us already.
1522
1523 qCDebug(QSG_LOG_RENDERLOOP, "- posting release request to render thread");
1524 w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, window->handle() == nullptr));
1525 w->thread->waitCondition.wait(&w->thread->mutex);
1526
1527 // Avoid a shutdown race condition.
1528 // If SG is invalidated and 'active' becomes false, the thread's run()
1529 // method will exit. handleExposure() relies on QThread::isRunning() (because it
1530 // potentially needs to start the thread again) and our mutex cannot be used to
1531 // track the thread stopping, so we wait a few nanoseconds extra so the thread
1532 // can exit properly.
1533 if (!w->thread->active) {
1534 qCDebug(QSG_LOG_RENDERLOOP) << " - waiting for render thread to exit" << w->window;
1535 w->thread->wait();
1536 qCDebug(QSG_LOG_RENDERLOOP) << " - render thread finished" << w->window;
1537 }
1538 }
1539 w->thread->mutex.unlock();
1540 }
1541
1542
1543 /* Calls polish on all items, then requests synchronization with the render thread
1544 * and blocks until that is complete. Returns false if it aborted; otherwise true.
1545 */
polishAndSync(Window * w,bool inExpose)1546 void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
1547 {
1548 qCDebug(QSG_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
1549
1550 QQuickWindow *window = w->window;
1551 if (!w->thread || !w->thread->window) {
1552 qCDebug(QSG_LOG_RENDERLOOP, "- not exposed, abort");
1553 return;
1554 }
1555
1556 // Flush pending touch events.
1557 QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
1558 // The delivery of the event might have caused the window to stop rendering
1559 w = windowFor(m_windows, window);
1560 if (!w || !w->thread || !w->thread->window) {
1561 qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
1562 return;
1563 }
1564
1565 Q_TRACE_SCOPE(QSG_polishAndSync);
1566 QElapsedTimer timer;
1567 qint64 polishTime = 0;
1568 qint64 waitTime = 0;
1569 qint64 syncTime = 0;
1570 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
1571 if (profileFrames)
1572 timer.start();
1573 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1574 Q_TRACE(QSG_polishItems_entry);
1575
1576 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1577 d->polishItems();
1578
1579 if (profileFrames)
1580 polishTime = timer.nsecsElapsed();
1581 Q_TRACE(QSG_polishItems_exit);
1582 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1583 QQuickProfiler::SceneGraphPolishAndSyncPolish);
1584 Q_TRACE(QSG_wait_entry);
1585
1586 w->updateDuringSync = false;
1587
1588 emit window->afterAnimating();
1589
1590 qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
1591 w->thread->mutex.lock();
1592 m_lockedForSync = true;
1593 w->thread->postEvent(new WMSyncEvent(window, inExpose, w->forceRenderPass));
1594 w->forceRenderPass = false;
1595
1596 qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
1597 if (profileFrames)
1598 waitTime = timer.nsecsElapsed();
1599 Q_TRACE(QSG_wait_exit);
1600 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1601 QQuickProfiler::SceneGraphPolishAndSyncWait);
1602 Q_TRACE(QSG_sync_entry);
1603
1604 w->thread->waitCondition.wait(&w->thread->mutex);
1605 m_lockedForSync = false;
1606 w->thread->mutex.unlock();
1607 qCDebug(QSG_LOG_RENDERLOOP, "- unlock after sync");
1608
1609 if (profileFrames)
1610 syncTime = timer.nsecsElapsed();
1611 Q_TRACE(QSG_sync_exit);
1612 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1613 QQuickProfiler::SceneGraphPolishAndSyncSync);
1614 Q_TRACE(QSG_animations_entry);
1615
1616 if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
1617 qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
1618 m_animation_driver->advance();
1619 qCDebug(QSG_LOG_RENDERLOOP, "- animations done..");
1620 // We need to trigger another sync to keep animations running...
1621 maybePostPolishRequest(w);
1622 emit timeToIncubate();
1623 } else if (w->updateDuringSync) {
1624 maybePostPolishRequest(w);
1625 }
1626
1627 qCDebug(QSG_LOG_TIME_RENDERLOOP()).nospace()
1628 << "Frame prepared with 'threaded' renderloop"
1629 << ", polish=" << (polishTime / 1000000)
1630 << ", lock=" << (waitTime - polishTime) / 1000000
1631 << ", blockedForSync=" << (syncTime - waitTime) / 1000000
1632 << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
1633 << " - (on Gui thread) " << window;
1634
1635 Q_TRACE(QSG_animations_exit);
1636 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1637 QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1638 }
1639
event(QEvent * e)1640 bool QSGThreadedRenderLoop::event(QEvent *e)
1641 {
1642 switch ((int) e->type()) {
1643
1644 case QEvent::Timer: {
1645 QTimerEvent *te = static_cast<QTimerEvent *>(e);
1646 if (te->timerId() == m_animation_timer) {
1647 qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-visual timer");
1648 m_animation_driver->advance();
1649 emit timeToIncubate();
1650 return true;
1651 }
1652 }
1653
1654 default:
1655 break;
1656 }
1657
1658 return QObject::event(e);
1659 }
1660
1661
1662
1663 /*
1664 Locks down GUI and performs a grab the scene graph, then returns the result.
1665
1666 Since the QML scene could have changed since the last time it was rendered,
1667 we need to polish and sync the scene graph. This might seem superfluous, but
1668 - QML changes could have triggered deleteLater() which could have removed
1669 textures or other objects from the scene graph, causing render to crash.
1670 - Autotests rely on grab(), setProperty(), grab(), compare behavior.
1671 */
1672
grab(QQuickWindow * window)1673 QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
1674 {
1675 qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
1676
1677 Window *w = windowFor(m_windows, window);
1678 Q_ASSERT(w);
1679
1680 if (!w->thread->isRunning())
1681 return QImage();
1682
1683 if (!window->handle())
1684 window->create();
1685
1686 qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
1687 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1688 d->polishItems();
1689
1690 QImage result;
1691 w->thread->mutex.lock();
1692 m_lockedForSync = true;
1693 qCDebug(QSG_LOG_RENDERLOOP, "- posting grab event");
1694 w->thread->postEvent(new WMGrabEvent(window, &result));
1695 w->thread->waitCondition.wait(&w->thread->mutex);
1696 m_lockedForSync = false;
1697 w->thread->mutex.unlock();
1698
1699 qCDebug(QSG_LOG_RENDERLOOP, "- grab complete");
1700
1701 return result;
1702 }
1703
1704 /*
1705 * Posts a new job event to the render thread.
1706 * Returns true if posting succeeded.
1707 */
postJob(QQuickWindow * window,QRunnable * job)1708 void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
1709 {
1710 Window *w = windowFor(m_windows, window);
1711 if (w && w->thread && w->thread->window)
1712 w->thread->postEvent(new WMJobEvent(window, job));
1713 else
1714 delete job;
1715 }
1716
1717 #include "qsgthreadedrenderloop.moc"
1718 #include "moc_qsgthreadedrenderloop_p.cpp"
1719
1720 QT_END_NAMESPACE
1721