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
120QT_BEGIN_NAMESPACE
121
122#define QSG_RT_PAD " (RT) %s"
123
124static 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
134static QElapsedTimer threadTimer;
135static qint64 syncTime;
136static qint64 renderTime;
137static qint64 sinceLastTime;
138
139extern 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.
146const 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())
150const 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.
154const 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.
158const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
159
160// Passed by the window when there is a render job to run
161const 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.
165const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
166
167template <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
178class WMWindowEvent : public QEvent
179{
180public:
181 WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
182 QQuickWindow *window;
183};
184
185class WMTryReleaseEvent : public WMWindowEvent
186{
187public:
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
198class WMSyncEvent : public WMWindowEvent
199{
200public:
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
215class WMGrabEvent : public WMWindowEvent
216{
217public:
218 WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
219 QImage *image;
220};
221
222class WMJobEvent : public WMWindowEvent
223{
224public:
225 WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
226 : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
227 ~WMJobEvent() { delete job; }
228 QRunnable *job;
229};
230
231class WMReleaseSwapchainEvent : public WMWindowEvent
232{
233public:
234 WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
235};
236
237class QSGRenderThreadEventQueue : public QQueue<QEvent *>
238{
239public:
240 QSGRenderThreadEventQueue()
241 : waiting(false)
242 {
243 }
244
245 void addEvent(QEvent *e) {
246 mutex.lock();
247 enqueue(t: e);
248 if (waiting)
249 condition.wakeOne();
250 mutex.unlock();
251 }
252
253 QEvent *takeEvent(bool wait) {
254 mutex.lock();
255 if (size() == 0 && wait) {
256 waiting = true;
257 condition.wait(lockedMutex: &mutex);
258 waiting = false;
259 }
260 QEvent *e = dequeue();
261 mutex.unlock();
262 return e;
263 }
264
265 bool hasMoreEvents() {
266 mutex.lock();
267 bool has = !isEmpty();
268 mutex.unlock();
269 return has;
270 }
271
272private:
273 QMutex mutex;
274 QWaitCondition condition;
275 bool waiting;
276};
277
278
279class QSGRenderThread : public QThread
280{
281 Q_OBJECT
282public:
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
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
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
331public slots:
332 void sceneGraphChanged() {
333 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sceneGraphChanged");
334 syncResultedInChanges = true;
335 }
336
337public:
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
382bool 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(c: 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(window: wme->window, inDestructor: wme->inDestructor, backupSurface: 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(c: 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(grabImage: ce->image);
462 } else {
463 gl->makeCurrent(surface: ce->window);
464
465 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync scene graph");
466 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: ce->window);
467 d->syncSceneGraph();
468 sgrc->endSync();
469
470 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering scene graph");
471 QQuickWindowPrivate::get(c: ce->window)->renderSceneGraph(size: ce->window->size());
472
473 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- grabbing result");
474 *ce->image = qt_gl_read_framebuffer(size: readbackSize, alpha_format: alpha, include_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(surface: 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(window: 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(event: e);
520}
521
522void 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(surface: 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(c: window);
549
550 // The canvas nodes must be cleaned up regardless if we are in the destructor..
551 if (wipeSG) {
552 dd->cleanupNodesOnShutdown();
553#if QT_CONFIG(quick_shadereffect)
554 QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
555#if QT_CONFIG(opengl)
556 if (current)
557 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
558#endif
559#endif
560 } else {
561 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup");
562 if (current && gl)
563 gl->doneCurrent();
564 return;
565 }
566
567 sgrc->invalidate();
568 QCoreApplication::processEvents();
569 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
570 if (inDestructor)
571 dd->animationController.reset();
572 if (current && gl)
573 gl->doneCurrent();
574 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidating scene graph");
575
576 if (wipeGL) {
577 if (dd->swapchain) {
578 if (window->handle()) {
579 // We get here when exiting via QCoreApplication::quit() instead of
580 // through QWindow::close().
581 wm->releaseSwapchain(window);
582 } else {
583 qWarning(msg: "QSGThreadedRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
584 window, dd->swapchain);
585 }
586 }
587 delete gl;
588 gl = nullptr;
589 delete rhi;
590 rhi = nullptr;
591 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidated OpenGL");
592 } else {
593 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup");
594 }
595}
596
597/*
598 Enters the mutex lock to make sure GUI is blocking and performs
599 sync, then wakes GUI.
600 */
601void QSGRenderThread::sync(bool inExpose, bool inGrab)
602{
603 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()");
604 if (!inGrab)
605 mutex.lock();
606
607 Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
608
609 bool current = true;
610 if (gl) {
611 if (windowSize.width() > 0 && windowSize.height() > 0)
612 current = gl->makeCurrent(surface: window);
613 else
614 current = false;
615 // Check for context loss.
616 if (!current && !gl->isValid()) {
617 QQuickWindowPrivate::get(c: window)->cleanupNodesOnShutdown();
618 sgrc->invalidate();
619 current = gl->create() && gl->makeCurrent(surface: window);
620 if (current) {
621 QSGDefaultRenderContext::InitParams rcParams;
622 rcParams.sampleCount = qMax(a: 1, b: gl->format().samples());
623 rcParams.openGLContext = gl;
624 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
625 rcParams.maybeSurface = window;
626 sgrc->initialize(params: &rcParams);
627 }
628 }
629 } else if (rhi) {
630 // With the rhi making the (OpenGL) context current serves only one
631 // purpose: to enable external OpenGL rendering connected to one of
632 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
633 // etc.) to function like it did on the direct OpenGL path. For our
634 // own rendering this call would not be necessary.
635 rhi->makeThreadLocalNativeContextCurrent();
636 } else {
637 current = false;
638 }
639 if (current) {
640 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
641 bool hadRenderer = d->renderer != nullptr;
642 // If the scene graph was touched since the last sync() make sure it sends the
643 // changed signal.
644 if (d->renderer)
645 d->renderer->clearChangedFlag();
646 d->syncSceneGraph();
647 sgrc->endSync();
648 if (!hadRenderer && d->renderer) {
649 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- renderer was created");
650 syncResultedInChanges = true;
651 connect(sender: d->renderer, SIGNAL(sceneGraphChanged()), receiver: this, SLOT(sceneGraphChanged()), Qt::DirectConnection);
652 }
653
654 // Process deferred deletes now, directly after the sync as
655 // deleteLater on the GUI must now also have resulted in SG changes
656 // and the delete is a safe operation.
657 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
658 } else {
659 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window has bad size, sync aborted");
660 }
661
662 // Two special cases: For grabs we do not care about blocking the gui
663 // (main) thread. When this is from an expose, we will keep locked until
664 // the frame is rendered (submitted), so in that case waking happens later
665 // in syncAndRender(). Otherwise, wake now and let the main thread go on
666 // while we render.
667 if (!inExpose && !inGrab) {
668 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui");
669 waitCondition.wakeOne();
670 mutex.unlock();
671 }
672}
673
674void QSGRenderThread::handleDeviceLoss()
675{
676 if (!rhi || !rhi->isDeviceLost())
677 return;
678
679 qWarning(msg: "Graphics device lost, cleaning up scenegraph and releasing RHI");
680 QQuickWindowPrivate::get(c: window)->cleanupNodesOnShutdown();
681 sgrc->invalidate();
682 wm->releaseSwapchain(window);
683 rhiDeviceLost = true;
684 delete rhi;
685 rhi = nullptr;
686}
687
688void QSGRenderThread::syncAndRender(QImage *grabImage)
689{
690 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
691 if (profileFrames) {
692 sinceLastTime = threadTimer.nsecsElapsed();
693 threadTimer.start();
694 }
695 Q_TRACE_SCOPE(QSG_syncAndRender);
696 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
697 Q_TRACE(QSG_sync_entry);
698
699 QElapsedTimer waitTimer;
700 waitTimer.start();
701
702 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "syncAndRender()");
703
704 syncResultedInChanges = false;
705 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
706
707 const bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage;
708 const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage;
709 const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
710 const bool grabRequested = grabImage != nullptr;
711 if (!grabRequested)
712 pendingUpdate = 0;
713
714 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
715 // Begin the frame before syncing -> sync is where we may invoke
716 // updatePaintNode() on the items and they may want to do resource updates.
717 // Also relevant for applications that connect to the before/afterSynchronizing
718 // signals and want to do graphics stuff already there.
719 if (cd->swapchain && windowSize.width() > 0 && windowSize.height() > 0) {
720 // always prefer what the surface tells us, not the QWindow
721 const QSize effectiveOutputSize = cd->swapchain->surfacePixelSize();
722 // An update request could still be delivered right before we get an
723 // unexpose. With Vulkan on Windows for example attempting to render
724 // leads to failures at this stage since the surface size is already 0.
725 if (effectiveOutputSize.isEmpty())
726 return;
727
728 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
729 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
730 if (cd->swapchainJustBecameRenderable)
731 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
732
733 cd->hasActiveSwapchain = cd->swapchain->buildOrResize();
734 if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
735 handleDeviceLoss();
736 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
737 return;
738 }
739
740 cd->swapchainJustBecameRenderable = false;
741 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
742
743 if (!cd->hasActiveSwapchain)
744 qWarning(msg: "Failed to build or resize swapchain");
745 else
746 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << cd->swapchain->currentPixelSize();
747 }
748
749 Q_ASSERT(rhi == cd->rhi);
750 // ### the flag should only be set when the app requests it, but there's no way to do that right now
751 QRhi::BeginFrameFlags frameFlags = QRhi::ExternalContentsInPass;
752 QRhi::FrameOpResult frameResult = rhi->beginFrame(swapChain: cd->swapchain, flags: frameFlags);
753 if (frameResult != QRhi::FrameOpSuccess) {
754 if (frameResult == QRhi::FrameOpDeviceLost)
755 handleDeviceLoss();
756 else if (frameResult == QRhi::FrameOpError)
757 qWarning(msg: "Failed to start frame");
758 // try again later
759 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
760 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
761 // Before returning we need to ensure the same wake up logic that
762 // would have happened if beginFrame() had suceeded.
763 if (syncRequested && !grabRequested) {
764 // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
765 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui");
766 mutex.lock();
767 // Go ahead with waking because we will return right after this.
768 waitCondition.wakeOne();
769 mutex.unlock();
770 }
771 return;
772 }
773 }
774
775 if (syncRequested) {
776 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync");
777 sync(inExpose: exposeRequested, inGrab: grabRequested);
778 }
779#ifndef QSG_NO_RENDER_TIMING
780 if (profileFrames)
781 syncTime = threadTimer.nsecsElapsed();
782#endif
783 Q_TRACE(QSG_sync_exit);
784 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
785 QQuickProfiler::SceneGraphRenderLoopSync);
786
787 if (!syncResultedInChanges
788 && !repaintRequested
789 && !(pendingUpdate & RepaintRequest) // may have been set in sync()
790 && sgrc->isValid()
791 && !grabRequested
792 && (gl || (rhi && !rhi->isRecordingFrame())))
793 {
794 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- no changes, render aborted");
795 int waitTime = vsyncDelta - (int) waitTimer.elapsed();
796 if (waitTime > 0)
797 msleep(waitTime);
798 return;
799 }
800
801 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering started");
802
803 Q_TRACE(QSG_render_entry);
804
805 // RepaintRequest may have been set in pendingUpdate in an
806 // updatePaintNode() invoked from sync(). We are about to do a repaint
807 // right now, so reset the flag. (bits other than RepaintRequest cannot
808 // be set in pendingUpdate at this point)
809 if (!grabRequested)
810 pendingUpdate = 0;
811
812 if (animatorDriver->isRunning() && !grabRequested) {
813 d->animationController->lock();
814 animatorDriver->advance();
815 d->animationController->unlock();
816 }
817
818 bool current = true;
819 if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0 && (gl || rhi)) {
820 if (gl)
821 current = gl->makeCurrent(surface: window);
822 else
823 rhi->makeThreadLocalNativeContextCurrent();
824 } else {
825 current = false;
826 }
827 // Check for context loss (GL, RHI case handled after the beginFrame() above)
828 if (gl) {
829 if (!current && !gl->isValid()) {
830 // Cannot do anything here because gui is not locked. Request a new
831 // sync+render round on the gui thread and let the sync handle it.
832 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
833 }
834 }
835 if (current) {
836 d->renderSceneGraph(size: windowSize, surfaceSize: rhi ? cd->swapchain->currentPixelSize() : QSize());
837
838 if (profileFrames)
839 renderTime = threadTimer.nsecsElapsed();
840 Q_TRACE(QSG_render_exit);
841 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
842 QQuickProfiler::SceneGraphRenderLoopRender);
843 Q_TRACE(QSG_swap_entry);
844
845 // With the rhi grabs can only be done by adding a readback and then
846 // blocking in a real frame. The legacy GL path never gets here with
847 // grabs as it rather invokes sync/render directly without going
848 // through syncAndRender().
849 if (grabRequested) {
850 Q_ASSERT(rhi && !gl && cd->swapchain);
851 *grabImage = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, swapchain: cd->swapchain);
852 }
853
854 if (cd->swapchain) {
855 QRhi::EndFrameFlags flags;
856 if (grabRequested)
857 flags |= QRhi::SkipPresent;
858 QRhi::FrameOpResult frameResult = rhi->endFrame(swapChain: cd->swapchain, flags);
859 if (frameResult != QRhi::FrameOpSuccess) {
860 if (frameResult == QRhi::FrameOpDeviceLost)
861 handleDeviceLoss();
862 else if (frameResult == QRhi::FrameOpError)
863 qWarning(msg: "Failed to end frame");
864 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
865 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
866 }
867 } else {
868 if (!cd->customRenderStage || !cd->customRenderStage->swap())
869 gl->swapBuffers(surface: window);
870 }
871
872 if (!grabRequested)
873 d->fireFrameSwapped();
874 } else {
875 Q_TRACE(QSG_render_exit);
876 Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
877 QQuickProfiler::SceneGraphRenderLoopSync, 1);
878 Q_TRACE(QSG_swap_entry);
879 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window not ready, skipping render");
880 }
881
882 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering done");
883
884 // Though it would be more correct to put this block directly after
885 // fireFrameSwapped in the if (current) branch above, we don't do
886 // that to avoid blocking the GUI thread in the case where it
887 // has started rendering with a bad window, causing makeCurrent to
888 // fail or if the window has a bad size.
889 if (exposeRequested) {
890 // With expose sync() did not wake gui, do it now.
891 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- wake Gui after expose");
892 waitCondition.wakeOne();
893 mutex.unlock();
894 }
895
896 qCDebug(QSG_LOG_TIME_RENDERLOOP,
897 "Frame rendered with 'threaded' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
898 int(threadTimer.elapsed()),
899 int((syncTime/1000000)),
900 int((renderTime - syncTime) / 1000000),
901 int(threadTimer.elapsed() - renderTime / 1000000));
902
903 Q_TRACE(QSG_swap_exit);
904 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
905 QQuickProfiler::SceneGraphRenderLoopSwap);
906
907 QSGRhiProfileConnection::instance()->send(rhi);
908}
909
910
911
912void QSGRenderThread::postEvent(QEvent *e)
913{
914 eventQueue.addEvent(e);
915}
916
917
918
919void QSGRenderThread::processEvents()
920{
921 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEvents()");
922 while (eventQueue.hasMoreEvents()) {
923 QEvent *e = eventQueue.takeEvent(wait: false);
924 event(e);
925 delete e;
926 }
927 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEvents()");
928}
929
930void QSGRenderThread::processEventsAndWaitForMore()
931{
932 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEventsAndWaitForMore()");
933 stopEventProcessing = false;
934 while (!stopEventProcessing) {
935 QEvent *e = eventQueue.takeEvent(wait: true);
936 event(e);
937 delete e;
938 }
939 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEventsAndWaitForMore()");
940}
941
942void QSGRenderThread::ensureRhi()
943{
944 if (!rhi) {
945 if (rhiDoomed) // no repeated attempts if the initial attempt failed
946 return;
947 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
948 rhi = rhiSupport->createRhi(window, offscreenSurface);
949 if (rhi) {
950 rhiDeviceLost = false;
951 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
952 if (rhiSupport->isProfilingRequested())
953 QSGRhiProfileConnection::instance()->initialize(rhi); // ### this breaks down with multiple windows
954 } else {
955 if (!rhiDeviceLost) {
956 rhiDoomed = true;
957 qWarning(msg: "Failed to create QRhi on the render thread; scenegraph is not functional");
958 }
959 // otherwise no error, will retry on a subsequent rendering attempt
960 return;
961 }
962 }
963 if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) {
964 rhi->makeThreadLocalNativeContextCurrent();
965 QSGDefaultRenderContext::InitParams rcParams;
966 rcParams.rhi = rhi;
967 rcParams.sampleCount = rhiSampleCount;
968 rcParams.openGLContext = nullptr;
969 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
970 rcParams.maybeSurface = window;
971 sgrc->initialize(params: &rcParams);
972 }
973 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
974 if (rhi && !cd->swapchain) {
975 cd->rhi = rhi;
976 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
977 // QQ is always premul alpha. Decide based on alphaBufferSize in
978 // requestedFormat(). (the platform plugin can override format() but
979 // what matters here is what the application wanted, hence using the
980 // requested one)
981 const bool alpha = window->requestedFormat().alphaBufferSize() > 0;
982 if (alpha)
983 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
984 cd->swapchain = rhi->newSwapChain();
985 cd->depthStencilForSwapchain = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil,
986 pixelSize: QSize(),
987 sampleCount: rhiSampleCount,
988 flags: QRhiRenderBuffer::UsedWithSwapChainOnly);
989 cd->swapchain->setWindow(window);
990 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
991 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
992 rhiSampleCount, alpha ? "yes" : "no");
993 cd->swapchain->setSampleCount(rhiSampleCount);
994 cd->swapchain->setFlags(flags);
995 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
996 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
997 }
998}
999
1000void QSGRenderThread::run()
1001{
1002 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()");
1003 animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(parent: nullptr);
1004 animatorDriver->install();
1005 if (QQmlDebugConnector::service<QQmlProfilerService>())
1006 QQuickProfiler::registerAnimationCallback();
1007
1008 while (active) {
1009#ifdef Q_OS_DARWIN
1010 QMacAutoReleasePool frameReleasePool;
1011#endif
1012
1013 if (window) {
1014 if (enableRhi) {
1015
1016 ensureRhi();
1017
1018 // We absolutely have to syncAndRender() here, even when QRhi
1019 // failed to initialize otherwise the gui thread will be left
1020 // in a blocked state. It is up to syncAndRender() to
1021 // gracefully skip all graphics stuff when rhi is null.
1022
1023 syncAndRender();
1024
1025 // Now we can do something about rhi init failures. (reinit
1026 // failure after device reset does not count)
1027 if (rhiDoomed && !guiNotifiedAboutRhiFailure) {
1028 guiNotifiedAboutRhiFailure = true;
1029 QEvent *e = new QEvent(QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure));
1030 QCoreApplication::postEvent(receiver: window, event: e);
1031 }
1032
1033 } else {
1034 if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(surface: window)) {
1035 QSGDefaultRenderContext::InitParams rcParams;
1036 rcParams.sampleCount = qMax(a: 1, b: gl->format().samples());
1037 rcParams.openGLContext = gl;
1038 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
1039 rcParams.maybeSurface = window;
1040 sgrc->initialize(params: &rcParams);
1041 }
1042 syncAndRender();
1043 }
1044 }
1045
1046 processEvents();
1047 QCoreApplication::processEvents();
1048
1049 if (active && (pendingUpdate == 0 || !window)) {
1050 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "done drawing, sleep...");
1051 sleeping = true;
1052 processEventsAndWaitForMore();
1053 sleeping = false;
1054 }
1055 }
1056
1057 Q_ASSERT_X(!gl && !rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread...");
1058
1059 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed");
1060
1061 delete animatorDriver;
1062 animatorDriver = nullptr;
1063
1064 sgrc->moveToThread(thread: wm->thread());
1065 moveToThread(thread: wm->thread());
1066}
1067
1068QSGThreadedRenderLoop::QSGThreadedRenderLoop()
1069 : sg(QSGContext::createDefaultContext())
1070 , m_animation_timer(0)
1071{
1072#if defined(QSG_RENDER_LOOP_DEBUG)
1073 qsgrl_timer.start();
1074#endif
1075
1076 m_animation_driver = sg->createAnimationDriver(parent: this);
1077
1078 connect(sender: m_animation_driver, SIGNAL(started()), receiver: this, SLOT(animationStarted()));
1079 connect(sender: m_animation_driver, SIGNAL(stopped()), receiver: this, SLOT(animationStopped()));
1080
1081 m_animation_driver->install();
1082}
1083
1084QSGThreadedRenderLoop::~QSGThreadedRenderLoop()
1085{
1086 qDeleteAll(c: pendingRenderContexts);
1087 delete sg;
1088}
1089
1090QSGRenderContext *QSGThreadedRenderLoop::createRenderContext(QSGContext *sg) const
1091{
1092 auto context = sg->createRenderContext();
1093 pendingRenderContexts.insert(value: context);
1094 return context;
1095}
1096
1097void QSGThreadedRenderLoop::maybePostPolishRequest(Window *w)
1098{
1099 w->window->requestUpdate();
1100}
1101
1102QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const
1103{
1104 return m_animation_driver;
1105}
1106
1107QSGContext *QSGThreadedRenderLoop::sceneGraphContext() const
1108{
1109 return sg;
1110}
1111
1112bool QSGThreadedRenderLoop::anyoneShowing() const
1113{
1114 for (int i=0; i<m_windows.size(); ++i) {
1115 QQuickWindow *c = m_windows.at(i).window;
1116 if (c->isVisible() && c->isExposed())
1117 return true;
1118 }
1119 return false;
1120}
1121
1122bool QSGThreadedRenderLoop::interleaveIncubation() const
1123{
1124 return m_animation_driver->isRunning() && anyoneShowing();
1125}
1126
1127void QSGThreadedRenderLoop::animationStarted()
1128{
1129 qCDebug(QSG_LOG_RENDERLOOP, "- animationStarted()");
1130 startOrStopAnimationTimer();
1131
1132 for (int i=0; i<m_windows.size(); ++i)
1133 maybePostPolishRequest(w: const_cast<Window *>(&m_windows.at(i)));
1134}
1135
1136void QSGThreadedRenderLoop::animationStopped()
1137{
1138 qCDebug(QSG_LOG_RENDERLOOP, "- animationStopped()");
1139 startOrStopAnimationTimer();
1140}
1141
1142
1143void QSGThreadedRenderLoop::startOrStopAnimationTimer()
1144{
1145 int exposedWindows = 0;
1146 const Window *theOne = nullptr;
1147 for (int i=0; i<m_windows.size(); ++i) {
1148 const Window &w = m_windows.at(i);
1149 if (w.window->isVisible() && w.window->isExposed()) {
1150 ++exposedWindows;
1151 theOne = &w;
1152 }
1153 }
1154
1155 if (m_animation_timer != 0 && (exposedWindows == 1 || !m_animation_driver->isRunning())) {
1156 qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping animation timer");
1157 killTimer(id: m_animation_timer);
1158 m_animation_timer = 0;
1159 // If animations are running, make sure we keep on animating
1160 if (m_animation_driver->isRunning())
1161 maybePostPolishRequest(w: const_cast<Window *>(theOne));
1162 } else if (m_animation_timer == 0 && exposedWindows != 1 && m_animation_driver->isRunning()) {
1163 qCDebug(QSG_LOG_RENDERLOOP, "*** Starting animation timer");
1164 m_animation_timer = startTimer(interval: qsgrl_animation_interval());
1165 }
1166}
1167
1168/*
1169 Removes this window from the list of tracked windowes in this
1170 window manager. hide() will trigger obscure, which in turn will
1171 stop rendering.
1172
1173 This function will be called during QWindow::close() which will
1174 also destroy the QPlatformWindow so it is important that this
1175 triggers handleObscurity() and that rendering for that window
1176 is fully done and over with by the time this function exits.
1177 */
1178
1179void QSGThreadedRenderLoop::hide(QQuickWindow *window)
1180{
1181 qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
1182
1183 if (window->isExposed())
1184 handleObscurity(w: windowFor(list: m_windows, window));
1185
1186 releaseResources(window);
1187}
1188
1189
1190/*
1191 If the window is first hide it, then perform a complete cleanup
1192 with releaseResources which will take down the GL context and
1193 exit the rendering thread.
1194 */
1195void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
1196{
1197 qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
1198
1199 Window *w = windowFor(list: m_windows, window);
1200 if (!w)
1201 return;
1202
1203 handleObscurity(w);
1204 releaseResources(window: w, inDestructor: true);
1205
1206 QSGRenderThread *thread = w->thread;
1207 while (thread->isRunning())
1208 QThread::yieldCurrentThread();
1209 Q_ASSERT(thread->thread() == QThread::currentThread());
1210 delete thread;
1211
1212 for (int i=0; i<m_windows.size(); ++i) {
1213 if (m_windows.at(i).window == window) {
1214 m_windows.removeAt(i);
1215 break;
1216 }
1217 }
1218
1219 // Now that we altered the window list, we may need to stop the animation
1220 // timer even if we didn't via handleObscurity. This covers the case where
1221 // we destroy a visible & exposed QQuickWindow.
1222 startOrStopAnimationTimer();
1223
1224 qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
1225}
1226
1227void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
1228{
1229 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1230 delete wd->rpDescForSwapchain;
1231 wd->rpDescForSwapchain = nullptr;
1232 delete wd->swapchain;
1233 wd->swapchain = nullptr;
1234 delete wd->depthStencilForSwapchain;
1235 wd->depthStencilForSwapchain = nullptr;
1236 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
1237}
1238
1239void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
1240{
1241 qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
1242
1243 // This is tricker than used to be. We want to detect having an empty
1244 // surface size (which may be the case even when window->size() is
1245 // non-empty, on some platforms with some graphics APIs!) as well as the
1246 // case when the window just became "newly exposed" (e.g. after a
1247 // minimize-restore on Windows, or when switching between fully obscured -
1248 // not fully obscured on macOS)
1249 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1250 if (!window->isExposed())
1251 wd->hasRenderableSwapchain = false;
1252
1253 bool skipThisExpose = false;
1254 if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) {
1255 wd->hasRenderableSwapchain = false;
1256 skipThisExpose = true;
1257 }
1258
1259 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
1260 && !wd->swapchain->surfacePixelSize().isEmpty())
1261 {
1262 wd->hasRenderableSwapchain = true;
1263 wd->swapchainJustBecameRenderable = true;
1264 }
1265
1266 if (window->isExposed()) {
1267 if (!skipThisExpose)
1268 handleExposure(w: window);
1269 } else {
1270 Window *w = windowFor(list: m_windows, window);
1271 if (w)
1272 handleObscurity(w);
1273 }
1274}
1275
1276/*
1277 Will post an event to the render thread that this window should
1278 start to render.
1279 */
1280void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
1281{
1282 qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
1283
1284 Window *w = windowFor(list: m_windows, window);
1285 if (!w) {
1286 qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
1287 Window win;
1288 win.window = window;
1289 win.actualWindowFormat = window->format();
1290 auto renderContext = QQuickWindowPrivate::get(c: window)->context;
1291 // The thread assumes ownership, so we don't need to delete it later.
1292 pendingRenderContexts.remove(value: renderContext);
1293 win.thread = new QSGRenderThread(this, renderContext);
1294 win.updateDuringSync = false;
1295 win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
1296 m_windows << win;
1297 w = &m_windows.last();
1298 }
1299
1300 // set this early as we'll be rendering shortly anyway and this avoids
1301 // specialcasing exposure in polishAndSync.
1302 w->thread->window = window;
1303
1304 if (w->window->width() <= 0 || w->window->height() <= 0
1305 || (w->window->isTopLevel() && !w->window->geometry().intersects(r: w->window->screen()->availableGeometry()))) {
1306#ifndef QT_NO_DEBUG
1307 qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
1308 << w->window << " with invalid geometry: " << w->window->geometry()
1309 << " on " << w->window->screen();
1310#endif
1311 }
1312
1313 // Because we are going to bind a GL context to it, make sure it
1314 // is created.
1315 if (!w->window->handle())
1316 w->window->create();
1317
1318 // Start render thread if it is not running
1319 if (!w->thread->isRunning()) {
1320 qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread");
1321
1322 w->thread->enableRhi = QSGRhiSupport::instance()->isRhiEnabled();
1323 if (w->thread->enableRhi) {
1324 if (!w->thread->rhi) {
1325 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
1326 w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
1327 window->installEventFilter(filterObj: this);
1328 }
1329 } else {
1330 if (!w->thread->gl) {
1331 w->thread->gl = new QOpenGLContext();
1332 if (qt_gl_global_share_context())
1333 w->thread->gl->setShareContext(qt_gl_global_share_context());
1334 w->thread->gl->setFormat(w->window->requestedFormat());
1335 w->thread->gl->setScreen(w->window->screen());
1336 if (!w->thread->gl->create()) {
1337 delete w->thread->gl;
1338 w->thread->gl = nullptr;
1339 handleContextCreationFailure(window: w->window);
1340 return;
1341 }
1342
1343 QQuickWindowPrivate::get(c: w->window)->fireOpenGLContextCreated(context: w->thread->gl);
1344
1345 w->thread->gl->moveToThread(thread: w->thread);
1346 if (!w->thread->gl->shareContext())
1347 w->thread->gl->shareGroup()->moveToThread(thread: w->thread);
1348 qCDebug(QSG_LOG_RENDERLOOP, "- OpenGL context created");
1349
1350 w->thread->offscreenSurface = new QOffscreenSurface();
1351 w->thread->offscreenSurface->setFormat(w->actualWindowFormat);
1352 w->thread->offscreenSurface->create();
1353 }
1354 }
1355
1356 QQuickAnimatorController *controller
1357 = QQuickWindowPrivate::get(c: w->window)->animationController.get();
1358 if (controller->thread() != w->thread)
1359 controller->moveToThread(thread: w->thread);
1360
1361 w->thread->active = true;
1362 if (w->thread->thread() == QThread::currentThread()) {
1363 w->thread->sgrc->moveToThread(thread: w->thread);
1364 w->thread->moveToThread(thread: w->thread);
1365 }
1366 w->thread->start();
1367 if (!w->thread->isRunning())
1368 qFatal(msg: "Render thread failed to start, aborting application.");
1369
1370 } else {
1371 qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running");
1372 }
1373
1374 polishAndSync(w, inExpose: true);
1375 qCDebug(QSG_LOG_RENDERLOOP, "- done with handleExposure()");
1376
1377 startOrStopAnimationTimer();
1378}
1379
1380/*
1381 This function posts an event to the render thread to remove the window
1382 from the list of windowses to render.
1383
1384 It also starts up the non-vsync animation tick if no more windows
1385 are showing.
1386 */
1387void QSGThreadedRenderLoop::handleObscurity(Window *w)
1388{
1389 qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
1390 if (w->thread->isRunning()) {
1391 w->thread->mutex.lock();
1392 w->thread->postEvent(e: new WMWindowEvent(w->window, WM_Obscure));
1393 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1394 w->thread->mutex.unlock();
1395 }
1396 startOrStopAnimationTimer();
1397}
1398
1399bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
1400{
1401 switch (event->type()) {
1402 case QEvent::PlatformSurface:
1403 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
1404 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1405 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: watched);
1406 if (window) {
1407 Window *w = windowFor(list: m_windows, window);
1408 if (w) {
1409 w->thread->mutex.lock();
1410 w->thread->postEvent(e: new WMReleaseSwapchainEvent(window));
1411 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1412 w->thread->mutex.unlock();
1413 }
1414 window->removeEventFilter(obj: this);
1415 }
1416 }
1417 break;
1418 default:
1419 break;
1420 }
1421 return QObject::eventFilter(watched, event);
1422}
1423
1424void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
1425{
1426 qCDebug(QSG_LOG_RENDERLOOP, "- polish and sync update request");
1427 Window *w = windowFor(list: m_windows, window);
1428 if (w)
1429 polishAndSync(w);
1430}
1431
1432void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
1433{
1434 Window *w = windowFor(list: m_windows, window);
1435 if (w)
1436 maybeUpdate(window: w);
1437}
1438
1439/*
1440 Called whenever the QML scene has changed. Will post an event to
1441 ourselves that a sync is needed.
1442 */
1443void QSGThreadedRenderLoop::maybeUpdate(Window *w)
1444{
1445 if (!QCoreApplication::instance())
1446 return;
1447
1448 if (!w || !w->thread->isRunning())
1449 return;
1450
1451 QThread *current = QThread::currentThread();
1452 if (current == w->thread && w->thread->rhi && w->thread->rhi->isDeviceLost())
1453 return;
1454 if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
1455 qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1456 return;
1457 }
1458
1459 qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
1460
1461 // Call this function from the Gui thread later as startTimer cannot be
1462 // called from the render thread.
1463 if (current == w->thread) {
1464 qCDebug(QSG_LOG_RENDERLOOP, "- on render thread");
1465 w->updateDuringSync = true;
1466 return;
1467 }
1468
1469 maybePostPolishRequest(w);
1470}
1471
1472/*
1473 Called when the QQuickWindow should be explicitly repainted. This function
1474 can also be called on the render thread when the GUI thread is blocked to
1475 keep render thread animations alive.
1476 */
1477void QSGThreadedRenderLoop::update(QQuickWindow *window)
1478{
1479 Window *w = windowFor(list: m_windows, window);
1480 if (!w)
1481 return;
1482
1483 if (w->thread == QThread::currentThread()) {
1484 qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
1485 w->thread->requestRepaint();
1486 return;
1487 }
1488
1489 qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
1490 // We set forceRenderPass because we want to make sure the QQuickWindow
1491 // actually does a full render pass after the next sync.
1492 w->forceRenderPass = true;
1493 maybeUpdate(w);
1494}
1495
1496
1497void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
1498{
1499 Window *w = windowFor(list: m_windows, window);
1500 if (w)
1501 releaseResources(window: w, inDestructor: false);
1502}
1503
1504/*
1505 * Release resources will post an event to the render thread to
1506 * free up the SG and GL resources and exists the render thread.
1507 */
1508void QSGThreadedRenderLoop::releaseResources(Window *w, bool inDestructor)
1509{
1510 qCDebug(QSG_LOG_RENDERLOOP) << "releaseResources()" << (inDestructor ? "in destructor" : "in api-call") << w->window;
1511
1512 w->thread->mutex.lock();
1513 if (w->thread->isRunning() && w->thread->active) {
1514 QQuickWindow *window = w->window;
1515
1516 // The platform window might have been destroyed before
1517 // hide/release/windowDestroyed is called, so we may need to have a
1518 // fallback surface to perform the cleanup of the scene graph and the
1519 // OpenGL resources. QOffscreenSurface must be created on the GUI
1520 // thread so that is done for us already.
1521
1522 qCDebug(QSG_LOG_RENDERLOOP, "- posting release request to render thread");
1523 w->thread->postEvent(e: new WMTryReleaseEvent(window, inDestructor, window->handle() == nullptr));
1524 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1525
1526 // Avoid a shutdown race condition.
1527 // If SG is invalidated and 'active' becomes false, the thread's run()
1528 // method will exit. handleExposure() relies on QThread::isRunning() (because it
1529 // potentially needs to start the thread again) and our mutex cannot be used to
1530 // track the thread stopping, so we wait a few nanoseconds extra so the thread
1531 // can exit properly.
1532 if (!w->thread->active) {
1533 qCDebug(QSG_LOG_RENDERLOOP) << " - waiting for render thread to exit" << w->window;
1534 w->thread->wait();
1535 qCDebug(QSG_LOG_RENDERLOOP) << " - render thread finished" << w->window;
1536 }
1537 }
1538 w->thread->mutex.unlock();
1539}
1540
1541
1542/* Calls polish on all items, then requests synchronization with the render thread
1543 * and blocks until that is complete. Returns false if it aborted; otherwise true.
1544 */
1545void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
1546{
1547 qCDebug(QSG_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
1548
1549 QQuickWindow *window = w->window;
1550 if (!w->thread || !w->thread->window) {
1551 qCDebug(QSG_LOG_RENDERLOOP, "- not exposed, abort");
1552 return;
1553 }
1554
1555 // Flush pending touch events.
1556 QQuickWindowPrivate::get(c: window)->flushFrameSynchronousEvents();
1557 // The delivery of the event might have caused the window to stop rendering
1558 w = windowFor(list: m_windows, window);
1559 if (!w || !w->thread || !w->thread->window) {
1560 qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
1561 return;
1562 }
1563
1564 Q_TRACE_SCOPE(QSG_polishAndSync);
1565 QElapsedTimer timer;
1566 qint64 polishTime = 0;
1567 qint64 waitTime = 0;
1568 qint64 syncTime = 0;
1569 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
1570 if (profileFrames)
1571 timer.start();
1572 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1573 Q_TRACE(QSG_polishItems_entry);
1574
1575 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
1576 d->polishItems();
1577
1578 if (profileFrames)
1579 polishTime = timer.nsecsElapsed();
1580 Q_TRACE(QSG_polishItems_exit);
1581 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1582 QQuickProfiler::SceneGraphPolishAndSyncPolish);
1583 Q_TRACE(QSG_wait_entry);
1584
1585 w->updateDuringSync = false;
1586
1587 emit window->afterAnimating();
1588
1589 qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
1590 w->thread->mutex.lock();
1591 m_lockedForSync = true;
1592 w->thread->postEvent(e: new WMSyncEvent(window, inExpose, w->forceRenderPass));
1593 w->forceRenderPass = false;
1594
1595 qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
1596 if (profileFrames)
1597 waitTime = timer.nsecsElapsed();
1598 Q_TRACE(QSG_wait_exit);
1599 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1600 QQuickProfiler::SceneGraphPolishAndSyncWait);
1601 Q_TRACE(QSG_sync_entry);
1602
1603 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1604 m_lockedForSync = false;
1605 w->thread->mutex.unlock();
1606 qCDebug(QSG_LOG_RENDERLOOP, "- unlock after sync");
1607
1608 if (profileFrames)
1609 syncTime = timer.nsecsElapsed();
1610 Q_TRACE(QSG_sync_exit);
1611 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1612 QQuickProfiler::SceneGraphPolishAndSyncSync);
1613 Q_TRACE(QSG_animations_entry);
1614
1615 if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
1616 qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
1617 m_animation_driver->advance();
1618 qCDebug(QSG_LOG_RENDERLOOP, "- animations done..");
1619 // We need to trigger another sync to keep animations running...
1620 maybePostPolishRequest(w);
1621 emit timeToIncubate();
1622 } else if (w->updateDuringSync) {
1623 maybePostPolishRequest(w);
1624 }
1625
1626 qCDebug(QSG_LOG_TIME_RENDERLOOP()).nospace()
1627 << "Frame prepared with 'threaded' renderloop"
1628 << ", polish=" << (polishTime / 1000000)
1629 << ", lock=" << (waitTime - polishTime) / 1000000
1630 << ", blockedForSync=" << (syncTime - waitTime) / 1000000
1631 << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
1632 << " - (on Gui thread) " << window;
1633
1634 Q_TRACE(QSG_animations_exit);
1635 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1636 QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1637}
1638
1639bool QSGThreadedRenderLoop::event(QEvent *e)
1640{
1641 switch ((int) e->type()) {
1642
1643 case QEvent::Timer: {
1644 QTimerEvent *te = static_cast<QTimerEvent *>(e);
1645 if (te->timerId() == m_animation_timer) {
1646 qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-visual timer");
1647 m_animation_driver->advance();
1648 emit timeToIncubate();
1649 return true;
1650 }
1651 }
1652
1653 default:
1654 break;
1655 }
1656
1657 return QObject::event(event: e);
1658}
1659
1660
1661
1662/*
1663 Locks down GUI and performs a grab the scene graph, then returns the result.
1664
1665 Since the QML scene could have changed since the last time it was rendered,
1666 we need to polish and sync the scene graph. This might seem superfluous, but
1667 - QML changes could have triggered deleteLater() which could have removed
1668 textures or other objects from the scene graph, causing render to crash.
1669 - Autotests rely on grab(), setProperty(), grab(), compare behavior.
1670 */
1671
1672QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
1673{
1674 qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
1675
1676 Window *w = windowFor(list: m_windows, window);
1677 Q_ASSERT(w);
1678
1679 if (!w->thread->isRunning())
1680 return QImage();
1681
1682 if (!window->handle())
1683 window->create();
1684
1685 qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
1686 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
1687 d->polishItems();
1688
1689 QImage result;
1690 w->thread->mutex.lock();
1691 m_lockedForSync = true;
1692 qCDebug(QSG_LOG_RENDERLOOP, "- posting grab event");
1693 w->thread->postEvent(e: new WMGrabEvent(window, &result));
1694 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1695 m_lockedForSync = false;
1696 w->thread->mutex.unlock();
1697
1698 qCDebug(QSG_LOG_RENDERLOOP, "- grab complete");
1699
1700 return result;
1701}
1702
1703/*
1704 * Posts a new job event to the render thread.
1705 * Returns true if posting succeeded.
1706 */
1707void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
1708{
1709 Window *w = windowFor(list: m_windows, window);
1710 if (w && w->thread && w->thread->window)
1711 w->thread->postEvent(e: new WMJobEvent(window, job));
1712 else
1713 delete job;
1714}
1715
1716#include "qsgthreadedrenderloop.moc"
1717#include "moc_qsgthreadedrenderloop_p.cpp"
1718
1719QT_END_NAMESPACE
1720

source code of qtdeclarative/src/quick/scenegraph/qsgthreadedrenderloop.cpp