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