1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5
6#include <QtCore/QMutex>
7#include <QtCore/QWaitCondition>
8#include <QtCore/QAnimationDriver>
9#include <QtCore/QQueue>
10#include <QtCore/QTimer>
11
12#include <QtGui/QGuiApplication>
13#include <QtGui/QScreen>
14#include <QtGui/QOffscreenSurface>
15
16#include <qpa/qwindowsysteminterface.h>
17
18#include <QtQuick/QQuickWindow>
19#include <private/qquickwindow_p.h>
20#include <private/qquickitem_p.h>
21#include <QtGui/qpa/qplatformwindow_p.h>
22
23#include <QtQuick/private/qsgrenderer_p.h>
24
25#include "qsgthreadedrenderloop_p.h"
26#include "qsgrhisupport_p.h"
27#include <private/qquickanimatorcontroller_p.h>
28
29#include <private/qquickprofiler_p.h>
30#include <private/qqmldebugserviceinterfaces_p.h>
31#include <private/qqmldebugconnector_p.h>
32
33#include <private/qsgrhishadereffectnode_p.h>
34#include <private/qsgdefaultrendercontext_p.h>
35
36#include <qtquick_tracepoints_p.h>
37
38#ifdef Q_OS_DARWIN
39#include <QtCore/private/qcore_mac_p.h>
40#endif
41
42/*
43 Overall design:
44
45 There are two classes here. QSGThreadedRenderLoop and
46 QSGRenderThread. All communication between the two is based on
47 event passing and we have a number of custom events.
48
49 In this implementation, the render thread is never blocked and the
50 GUI thread will initiate a polishAndSync which will block and wait
51 for the render thread to pick it up and release the block only
52 after the render thread is done syncing. The reason for this
53 is:
54
55 1. Clear blocking paradigm. We only have one real "block" point
56 (polishAndSync()) and all blocking is initiated by GUI and picked
57 up by Render at specific times based on events. This makes the
58 execution deterministic.
59
60 2. Render does not have to interact with GUI. This is done so that
61 the render thread can run its own animation system which stays
62 alive even when the GUI thread is blocked doing i/o, object
63 instantiation, QPainter-painting or any other non-trivial task.
64
65 ---
66
67 There is one thread per window and one QRhi instance per thread.
68
69 ---
70
71 The render thread has affinity to the GUI thread until a window
72 is shown. From that moment and until the window is destroyed, it
73 will have affinity to the render thread. (moved back at the end
74 of run for cleanup).
75
76 ---
77
78 The render loop is active while any window is exposed. All visible
79 windows are tracked, but only exposed windows are actually added to
80 the render thread and rendered. That means that if all windows are
81 obscured, we might end up cleaning up the SG and GL context (if all
82 windows have disabled persistency). Especially for multiprocess,
83 low-end systems, this should be quite important.
84
85 */
86
87QT_BEGIN_NAMESPACE
88
89Q_TRACE_POINT(qtquick, QSG_polishAndSync_entry)
90Q_TRACE_POINT(qtquick, QSG_polishAndSync_exit)
91Q_TRACE_POINT(qtquick, QSG_wait_entry)
92Q_TRACE_POINT(qtquick, QSG_wait_exit)
93Q_TRACE_POINT(qtquick, QSG_syncAndRender_entry)
94Q_TRACE_POINT(qtquick, QSG_syncAndRender_exit)
95Q_TRACE_POINT(qtquick, QSG_animations_entry)
96Q_TRACE_POINT(qtquick, QSG_animations_exit)
97
98#define QSG_RT_PAD " (RT) %s"
99
100extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
101
102// RL: Render Loop
103// RT: Render Thread
104
105
106QSGThreadedRenderLoop::Window *QSGThreadedRenderLoop::windowFor(QQuickWindow *window)
107{
108 for (const auto &t : std::as_const(t&: m_windows)) {
109 if (t.window == window)
110 return const_cast<Window *>(&t);
111 }
112 return nullptr;
113}
114
115class WMWindowEvent : public QEvent
116{
117public:
118 WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
119 QQuickWindow *window;
120};
121
122class WMTryReleaseEvent : public WMWindowEvent
123{
124public:
125 WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
126 : WMWindowEvent(win, QEvent::Type(WM_TryRelease))
127 , inDestructor(destroy)
128 , needsFallback(needsFallbackSurface)
129 {}
130
131 bool inDestructor;
132 bool needsFallback;
133};
134
135class WMSyncEvent : public WMWindowEvent
136{
137public:
138 WMSyncEvent(QQuickWindow *c, bool inExpose, bool force, const QRhiSwapChainProxyData &scProxyData)
139 : WMWindowEvent(c, QEvent::Type(WM_RequestSync))
140 , size(c->size())
141 , dpr(float(c->effectiveDevicePixelRatio()))
142 , syncInExpose(inExpose)
143 , forceRenderPass(force)
144 , scProxyData(scProxyData)
145 {}
146 QSize size;
147 float dpr;
148 bool syncInExpose;
149 bool forceRenderPass;
150 QRhiSwapChainProxyData scProxyData;
151};
152
153
154class WMGrabEvent : public WMWindowEvent
155{
156public:
157 WMGrabEvent(QQuickWindow *c, QImage *result) :
158 WMWindowEvent(c, QEvent::Type(WM_Grab)), image(result) {}
159 QImage *image;
160};
161
162class WMJobEvent : public WMWindowEvent
163{
164public:
165 WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
166 : WMWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) {}
167 ~WMJobEvent() { delete job; }
168 QRunnable *job;
169};
170
171class WMReleaseSwapchainEvent : public WMWindowEvent
172{
173public:
174 WMReleaseSwapchainEvent(QQuickWindow *c) :
175 WMWindowEvent(c, QEvent::Type(WM_ReleaseSwapchain)) { }
176};
177
178class QSGRenderThreadEventQueue : public QQueue<QEvent *>
179{
180public:
181 QSGRenderThreadEventQueue()
182 : waiting(false)
183 {
184 }
185
186 void addEvent(QEvent *e) {
187 mutex.lock();
188 enqueue(t: e);
189 if (waiting)
190 condition.wakeOne();
191 mutex.unlock();
192 }
193
194 QEvent *takeEvent(bool wait) {
195 mutex.lock();
196 if (size() == 0 && wait) {
197 waiting = true;
198 condition.wait(lockedMutex: &mutex);
199 waiting = false;
200 }
201 QEvent *e = dequeue();
202 mutex.unlock();
203 return e;
204 }
205
206 bool hasMoreEvents() {
207 mutex.lock();
208 bool has = !isEmpty();
209 mutex.unlock();
210 return has;
211 }
212
213private:
214 QMutex mutex;
215 QWaitCondition condition;
216 bool waiting;
217};
218
219
220class QSGRenderThread : public QThread
221{
222 Q_OBJECT
223public:
224 QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext)
225 : wm(w)
226 , rhi(nullptr)
227 , ownRhi(true)
228 , offscreenSurface(nullptr)
229 , animatorDriver(nullptr)
230 , pendingUpdate(0)
231 , sleeping(false)
232 , syncResultedInChanges(false)
233 , active(false)
234 , window(nullptr)
235 , stopEventProcessing(false)
236 {
237 sgrc = static_cast<QSGDefaultRenderContext *>(renderContext);
238#if (defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)) || defined(Q_OS_INTEGRITY)
239 // The SDP 6.6.0 x86 MESA driver requires a larger stack than the default.
240 setStackSize(1024 * 1024);
241#endif
242 }
243
244 ~QSGRenderThread()
245 {
246 delete sgrc;
247 delete offscreenSurface;
248 }
249
250 void invalidateGraphics(QQuickWindow *window, bool inDestructor);
251
252 bool event(QEvent *) override;
253 void run() override;
254
255 void syncAndRender();
256 void sync(bool inExpose);
257
258 void requestRepaint()
259 {
260 if (sleeping)
261 stopEventProcessing = true;
262 if (window)
263 pendingUpdate |= RepaintRequest;
264 }
265
266 void processEventsAndWaitForMore();
267 void processEvents();
268 void postEvent(QEvent *e);
269
270public slots:
271 void sceneGraphChanged() {
272 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sceneGraphChanged");
273 syncResultedInChanges = true;
274 }
275
276public:
277 enum {
278 SyncRequest = 0x01,
279 RepaintRequest = 0x02,
280 ExposeRequest = 0x04 | RepaintRequest | SyncRequest
281 };
282
283 void ensureRhi();
284 void teardownGraphics();
285 void handleDeviceLoss();
286
287 QSGThreadedRenderLoop *wm;
288 QRhi *rhi;
289 bool ownRhi;
290 QSGDefaultRenderContext *sgrc;
291 QOffscreenSurface *offscreenSurface;
292
293 QAnimationDriver *animatorDriver;
294
295 uint pendingUpdate;
296 bool sleeping;
297 bool syncResultedInChanges;
298
299 volatile bool active;
300
301 QMutex mutex;
302 QWaitCondition waitCondition;
303
304 QElapsedTimer m_threadTimeBetweenRenders;
305
306 QQuickWindow *window; // Will be 0 when window is not exposed
307 QSize windowSize;
308 float dpr = 1;
309 QRhiSwapChainProxyData scProxyData;
310 int rhiSampleCount = 1;
311 bool rhiDeviceLost = false;
312 bool rhiDoomed = false;
313 bool guiNotifiedAboutRhiFailure = false;
314 bool swRastFallbackDueToSwapchainFailure = false;
315
316 // Local event queue stuff...
317 bool stopEventProcessing;
318 QSGRenderThreadEventQueue eventQueue;
319};
320
321bool QSGRenderThread::event(QEvent *e)
322{
323 switch ((int) e->type()) {
324
325 case WM_Obscure: {
326 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Obscure");
327
328 Q_ASSERT(!window || window == static_cast<WMWindowEvent *>(e)->window);
329
330 mutex.lock();
331 if (window) {
332 QQuickWindowPrivate::get(c: window)->fireAboutToStop();
333 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window removed");
334 window = nullptr;
335 }
336 waitCondition.wakeOne();
337 mutex.unlock();
338
339 return true; }
340
341 case WM_RequestSync: {
342 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestSync");
343 WMSyncEvent *se = static_cast<WMSyncEvent *>(e);
344 if (sleeping)
345 stopEventProcessing = true;
346 window = se->window;
347 windowSize = se->size;
348 dpr = se->dpr;
349 scProxyData = se->scProxyData;
350
351 pendingUpdate |= SyncRequest;
352 if (se->syncInExpose) {
353 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- triggered from expose");
354 pendingUpdate |= ExposeRequest;
355 }
356 if (se->forceRenderPass) {
357 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- repaint regardless");
358 pendingUpdate |= RepaintRequest;
359 }
360 return true; }
361
362 case WM_TryRelease: {
363 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_TryRelease");
364 mutex.lock();
365 wm->m_lockedForSync = true;
366 WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e);
367 if (!window || wme->inDestructor) {
368 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- setting exit flag and invalidating");
369 invalidateGraphics(window: wme->window, inDestructor: wme->inDestructor);
370 active = rhi != nullptr;
371 Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateGraphics()", "Thread's active state is not set to false when shutting down");
372 if (sleeping)
373 stopEventProcessing = true;
374 } else {
375 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- not releasing because window is still active");
376 if (window) {
377 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
378 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting external renderers such as Quick 3D to release cached resources");
379 emit d->context->releaseCachedResourcesRequested();
380 if (d->renderer) {
381 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting renderer to release cached resources");
382 d->renderer->releaseCachedResources();
383 }
384#if QT_CONFIG(quick_shadereffect)
385 QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(materialTypeCacheKey: window);
386#endif
387 }
388 }
389 waitCondition.wakeOne();
390 wm->m_lockedForSync = false;
391 mutex.unlock();
392 return true;
393 }
394
395 case WM_Grab: {
396 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Grab");
397 WMGrabEvent *ce = static_cast<WMGrabEvent *>(e);
398 Q_ASSERT(ce->window);
399 Q_ASSERT(ce->window == window || !window);
400 mutex.lock();
401 if (ce->window) {
402 if (rhi) {
403 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: ce->window);
404 // The assumption is that the swapchain is usable, because on
405 // expose the thread starts up and renders a frame so one cannot
406 // get here without having done at least one on-screen frame.
407 cd->rhi->beginFrame(swapChain: cd->swapchain);
408 cd->rhi->makeThreadLocalNativeContextCurrent(); // for custom GL rendering before/during/after sync
409 cd->syncSceneGraph();
410 sgrc->endSync();
411 cd->renderSceneGraph();
412 *ce->image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cb: cd->swapchain->currentFrameCommandBuffer());
413 cd->rhi->endFrame(swapChain: cd->swapchain, flags: QRhi::SkipPresent);
414 }
415 ce->image->setDevicePixelRatio(ce->window->effectiveDevicePixelRatio());
416 }
417 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- waking gui to handle result");
418 waitCondition.wakeOne();
419 mutex.unlock();
420 return true;
421 }
422
423 case WM_PostJob: {
424 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_PostJob");
425 WMJobEvent *ce = static_cast<WMJobEvent *>(e);
426 Q_ASSERT(ce->window == window);
427 if (window) {
428 if (rhi)
429 rhi->makeThreadLocalNativeContextCurrent();
430 ce->job->run();
431 delete ce->job;
432 ce->job = nullptr;
433 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- job done");
434 }
435 return true;
436 }
437
438 case WM_ReleaseSwapchain: {
439 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_ReleaseSwapchain");
440 WMReleaseSwapchainEvent *ce = static_cast<WMReleaseSwapchainEvent *>(e);
441 // forget about 'window' here that may be null when already unexposed
442 Q_ASSERT(ce->window);
443 mutex.lock();
444 if (ce->window) {
445 wm->releaseSwapchain(window: ce->window);
446 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- swapchain released");
447 }
448 waitCondition.wakeOne();
449 mutex.unlock();
450 return true;
451 }
452
453 default:
454 break;
455 }
456 return QThread::event(event: e);
457}
458
459void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor)
460{
461 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateGraphics()");
462
463 if (!rhi)
464 return;
465
466 if (!window) {
467 qCWarning(QSG_LOG_RENDERLOOP, "QSGThreadedRenderLoop:QSGRenderThread: no window to make current...");
468 return;
469 }
470
471 bool wipeSG = inDestructor || !window->isPersistentSceneGraph();
472 bool wipeGraphics = inDestructor || (wipeSG && !window->isPersistentGraphics());
473
474 rhi->makeThreadLocalNativeContextCurrent();
475
476 QQuickWindowPrivate *dd = QQuickWindowPrivate::get(c: window);
477
478 // The canvas nodes must be cleaned up regardless if we are in the destructor..
479 if (wipeSG) {
480 dd->cleanupNodesOnShutdown();
481#if QT_CONFIG(quick_shadereffect)
482 QSGRhiShaderEffectNode::resetMaterialTypeCache(materialTypeCacheKey: window);
483#endif
484 } else {
485 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup");
486 return;
487 }
488
489 sgrc->invalidate();
490 QCoreApplication::processEvents();
491 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
492 if (inDestructor)
493 dd->animationController.reset();
494
495 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidating scene graph");
496
497 if (wipeGraphics) {
498 if (dd->swapchain) {
499 if (window->handle()) {
500 // We get here when exiting via QCoreApplication::quit() instead of
501 // through QWindow::close().
502 wm->releaseSwapchain(window);
503 } else {
504 qWarning(msg: "QSGThreadedRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
505 window, dd->swapchain);
506 }
507 }
508 if (ownRhi)
509 QSGRhiSupport::instance()->destroyRhi(rhi, config: dd->graphicsConfig);
510 rhi = nullptr;
511 dd->rhi = nullptr;
512 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- QRhi destroyed");
513 } else {
514 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup");
515 }
516}
517
518/*
519 Enters the mutex lock to make sure GUI is blocking and performs
520 sync, then wakes GUI.
521 */
522void QSGRenderThread::sync(bool inExpose)
523{
524 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()");
525 mutex.lock();
526
527 Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
528
529 bool canSync = true;
530 if (rhi) {
531 if (windowSize.width() > 0 && windowSize.height() > 0) {
532 // With the rhi making the (OpenGL) context current serves only one
533 // purpose: to enable external OpenGL rendering connected to one of
534 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
535 // etc.) to function like it did on the direct OpenGL path. For our
536 // own rendering this call would not be necessary.
537 rhi->makeThreadLocalNativeContextCurrent();
538 } else {
539 // Zero size windows do not initialize a swapchain and
540 // rendercontext. So no sync or render can be done then.
541 canSync = false;
542 }
543 } else {
544 canSync = false;
545 }
546 if (canSync) {
547 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
548 bool hadRenderer = d->renderer != nullptr;
549 // If the scene graph was touched since the last sync() make sure it sends the
550 // changed signal.
551 if (d->renderer)
552 d->renderer->clearChangedFlag();
553 d->syncSceneGraph();
554 sgrc->endSync();
555 if (!hadRenderer && d->renderer) {
556 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- renderer was created");
557 syncResultedInChanges = true;
558 connect(sender: d->renderer, SIGNAL(sceneGraphChanged()), receiver: this, SLOT(sceneGraphChanged()), Qt::DirectConnection);
559 }
560
561 // Process deferred deletes now, directly after the sync as
562 // deleteLater on the GUI must now also have resulted in SG changes
563 // and the delete is a safe operation.
564 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
565 } else {
566 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window has bad size, sync aborted");
567 }
568
569 // Two special cases: For grabs we do not care about blocking the gui
570 // (main) thread. When this is from an expose, we will keep locked until
571 // the frame is rendered (submitted), so in that case waking happens later
572 // in syncAndRender(). Otherwise, wake now and let the main thread go on
573 // while we render.
574 if (!inExpose) {
575 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui");
576 waitCondition.wakeOne();
577 mutex.unlock();
578 }
579}
580
581void QSGRenderThread::teardownGraphics()
582{
583 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
584 wd->cleanupNodesOnShutdown();
585 sgrc->invalidate();
586 wm->releaseSwapchain(window);
587 if (ownRhi)
588 QSGRhiSupport::instance()->destroyRhi(rhi, config: {});
589 rhi = nullptr;
590}
591
592void QSGRenderThread::handleDeviceLoss()
593{
594 if (!rhi || !rhi->isDeviceLost())
595 return;
596
597 qWarning(msg: "Graphics device lost, cleaning up scenegraph and releasing RHI");
598 teardownGraphics();
599 rhiDeviceLost = true;
600}
601
602void QSGRenderThread::syncAndRender()
603{
604 const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
605 QElapsedTimer threadTimer;
606 qint64 syncTime = 0, renderTime = 0;
607 if (profileFrames)
608 threadTimer.start();
609 Q_TRACE_SCOPE(QSG_syncAndRender);
610 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
611 Q_TRACE(QSG_sync_entry);
612
613 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "syncAndRender()");
614
615 if (profileFrames) {
616 const qint64 elapsedSinceLastMs = m_threadTimeBetweenRenders.restart();
617 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][render thread %p] syncAndRender: start, elapsed since last call: %d ms",
618 window,
619 QThread::currentThread(),
620 int(elapsedSinceLastMs));
621 }
622
623 syncResultedInChanges = false;
624 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
625
626 const bool syncRequested = (pendingUpdate & SyncRequest);
627 const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
628 pendingUpdate = 0;
629
630 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
631 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
632 // Begin the frame before syncing -> sync is where we may invoke
633 // updatePaintNode() on the items and they may want to do resource updates.
634 // Also relevant for applications that connect to the before/afterSynchronizing
635 // signals and want to do graphics stuff already there.
636 const bool hasValidSwapChain = (cd->swapchain && windowSize.width() > 0 && windowSize.height() > 0);
637 if (hasValidSwapChain) {
638 cd->swapchain->setProxyData(scProxyData);
639 // always prefer what the surface tells us, not the QWindow
640 const QSize effectiveOutputSize = cd->swapchain->surfacePixelSize();
641 // An update request could still be delivered right before we get an
642 // unexpose. With Vulkan on Windows for example attempting to render
643 // leads to failures at this stage since the surface size is already 0.
644 if (effectiveOutputSize.isEmpty())
645 return;
646
647 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
648 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
649 if (cd->swapchainJustBecameRenderable)
650 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
651
652 cd->hasActiveSwapchain = cd->swapchain->createOrResize();
653 if (!cd->hasActiveSwapchain) {
654 bool bailOut = false;
655 if (rhi->isDeviceLost()) {
656 handleDeviceLoss();
657 bailOut = true;
658 } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
659 qWarning(msg: "Failed to create swapchain."
660 " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
661 swRastFallbackDueToSwapchainFailure = true;
662 teardownGraphics();
663 bailOut = true;
664 }
665 if (bailOut) {
666 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
667 if (syncRequested) {
668 // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
669 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed swapchain init, wake Gui");
670 mutex.lock();
671 waitCondition.wakeOne();
672 mutex.unlock();
673 }
674 return;
675 }
676 }
677
678 cd->swapchainJustBecameRenderable = false;
679 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
680
681 if (!cd->hasActiveSwapchain)
682 qWarning(msg: "Failed to build or resize swapchain");
683 else
684 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << cd->swapchain->currentPixelSize();
685 }
686
687 emit window->beforeFrameBegin();
688
689 Q_ASSERT(rhi == cd->rhi);
690 QRhi::FrameOpResult frameResult = rhi->beginFrame(swapChain: cd->swapchain);
691 if (frameResult != QRhi::FrameOpSuccess) {
692 if (frameResult == QRhi::FrameOpDeviceLost)
693 handleDeviceLoss();
694 else if (frameResult == QRhi::FrameOpError)
695 qWarning(msg: "Failed to start frame");
696 // try again later
697 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
698 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
699 // Before returning we need to ensure the same wake up logic that
700 // would have happened if beginFrame() had suceeded.
701 if (syncRequested) {
702 // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
703 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui");
704 mutex.lock();
705 // Go ahead with waking because we will return right after this.
706 waitCondition.wakeOne();
707 mutex.unlock();
708 }
709 emit window->afterFrameEnd();
710 return;
711 }
712 }
713
714 if (syncRequested) {
715 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync");
716 sync(inExpose: exposeRequested);
717 }
718#ifndef QSG_NO_RENDER_TIMING
719 if (profileFrames)
720 syncTime = threadTimer.nsecsElapsed();
721#endif
722 Q_TRACE(QSG_sync_exit);
723 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
724 QQuickProfiler::SceneGraphRenderLoopSync);
725
726 // Qt 6 no longer aborts when !syncResultedInChanges && !RepaintRequest,
727 // meaning this function always completes and presents a frame. This is
728 // more compatible with what the basic render loop (or a custom loop with
729 // QQuickRenderControl) would do, is more accurate due to not having to do
730 // an msleep() with an inaccurate interval, and avoids misunderstandings
731 // for signals like frameSwapped(). (in Qt 5 a continuously "updating"
732 // window is continuously presenting frames with the basic loop, but not
733 // with threaded due to aborting when sync() finds there are no relevant
734 // visual changes in the scene graph; this system proved to be simply too
735 // confusing in practice)
736
737 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering started");
738
739 Q_TRACE(QSG_render_entry);
740
741 // RepaintRequest may have been set in pendingUpdate in an
742 // updatePaintNode() invoked from sync(). We are about to do a repaint
743 // right now, so reset the flag. (bits other than RepaintRequest cannot
744 // be set in pendingUpdate at this point)
745 pendingUpdate = 0;
746
747 // Advance render thread animations (from the QQuickAnimator subclasses).
748 if (animatorDriver->isRunning()) {
749 d->animationController->lock();
750 animatorDriver->advance();
751 d->animationController->unlock();
752 }
753
754 // Zero size windows do not initialize a swapchain and
755 // rendercontext. So no sync or render can be done then.
756 const bool canRender = d->renderer && hasValidSwapChain;
757 double lastCompletedGpuTime = 0;
758 if (canRender) {
759 if (!syncRequested) // else this was already done in sync()
760 rhi->makeThreadLocalNativeContextCurrent();
761
762 d->renderSceneGraph();
763
764 if (profileFrames)
765 renderTime = threadTimer.nsecsElapsed();
766 Q_TRACE(QSG_render_exit);
767 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
768 QQuickProfiler::SceneGraphRenderLoopRender);
769 Q_TRACE(QSG_swap_entry);
770
771 QRhi::FrameOpResult frameResult = rhi->endFrame(swapChain: cd->swapchain);
772 if (frameResult != QRhi::FrameOpSuccess) {
773 if (frameResult == QRhi::FrameOpDeviceLost)
774 handleDeviceLoss();
775 else if (frameResult == QRhi::FrameOpError)
776 qWarning(msg: "Failed to end frame");
777 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
778 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
779 } else {
780 lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
781 }
782 d->fireFrameSwapped();
783 } else {
784 Q_TRACE(QSG_render_exit);
785 Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
786 QQuickProfiler::SceneGraphRenderLoopSync, 1);
787 Q_TRACE(QSG_swap_entry);
788 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window not ready, skipping render");
789 // Make sure a beginFrame() always gets an endFrame(). We could have
790 // started a frame but then not have a valid renderer (if there was no
791 // sync). So gracefully handle that.
792 if (cd->swapchain && rhi->isRecordingFrame())
793 rhi->endFrame(swapChain: cd->swapchain, flags: QRhi::SkipPresent);
794 }
795
796 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering done");
797
798 // beforeFrameBegin - afterFrameEnd must always come in pairs; if there was
799 // no before due to 0 size then there shouldn't be an after either
800 if (hasValidSwapChain)
801 emit window->afterFrameEnd();
802
803 // Though it would be more correct to put this block directly after
804 // fireFrameSwapped in the if (current) branch above, we don't do
805 // that to avoid blocking the GUI thread in the case where it
806 // has started rendering with a bad window, causing makeCurrent to
807 // fail or if the window has a bad size.
808 if (exposeRequested) {
809 // With expose sync() did not wake gui, do it now.
810 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- wake Gui after expose");
811 waitCondition.wakeOne();
812 mutex.unlock();
813 }
814
815 if (profileFrames) {
816 // Beware that there is no guarantee the graphics stack always
817 // blocks for a full vsync in beginFrame() or endFrame(). (because
818 // e.g. there is no guarantee that OpenGL blocks in swapBuffers(),
819 // it may block elsewhere; also strategies may change once there
820 // are multiple windows) So process the results printed here with
821 // caution and pay attention to the elapsed-since-last-call time
822 // printed at the beginning of the function too.
823 qCDebug(QSG_LOG_TIME_RENDERLOOP,
824 "[window %p][render thread %p] syncAndRender: frame rendered in %dms, sync=%d, render=%d, swap=%d",
825 window,
826 QThread::currentThread(),
827 int(threadTimer.elapsed()),
828 int((syncTime/1000000)),
829 int((renderTime - syncTime) / 1000000),
830 int((threadTimer.nsecsElapsed() - renderTime) / 1000000));
831 if (!qFuzzyIsNull(d: lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
832 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][render thread %p] syncAndRender: last retrieved GPU frame time was %.4f ms",
833 window,
834 QThread::currentThread(),
835 lastCompletedGpuTime * 1000.0);
836 }
837 }
838
839 Q_TRACE(QSG_swap_exit);
840 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
841 QQuickProfiler::SceneGraphRenderLoopSwap);
842}
843
844
845
846void QSGRenderThread::postEvent(QEvent *e)
847{
848 eventQueue.addEvent(e);
849}
850
851
852
853void QSGRenderThread::processEvents()
854{
855 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEvents()");
856 while (eventQueue.hasMoreEvents()) {
857 QEvent *e = eventQueue.takeEvent(wait: false);
858 event(e);
859 delete e;
860 }
861 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEvents()");
862}
863
864void QSGRenderThread::processEventsAndWaitForMore()
865{
866 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEventsAndWaitForMore()");
867 stopEventProcessing = false;
868 while (!stopEventProcessing) {
869 QEvent *e = eventQueue.takeEvent(wait: true);
870 event(e);
871 delete e;
872 }
873 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEventsAndWaitForMore()");
874}
875
876void QSGRenderThread::ensureRhi()
877{
878 if (!rhi) {
879 if (rhiDoomed) // no repeated attempts if the initial attempt failed
880 return;
881 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
882 const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
883 QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
884 rhi = rhiResult.rhi;
885 ownRhi = rhiResult.own;
886 if (rhi) {
887 rhiDeviceLost = false;
888 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
889 } else {
890 if (!rhiDeviceLost) {
891 rhiDoomed = true;
892 qWarning(msg: "Failed to create QRhi on the render thread; scenegraph is not functional");
893 }
894 // otherwise no error, will retry on a subsequent rendering attempt
895 return;
896 }
897 }
898 if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) {
899 // We need to guarantee that sceneGraphInitialized is emitted
900 // with a context current, if running with OpenGL.
901 rhi->makeThreadLocalNativeContextCurrent();
902 QSGDefaultRenderContext::InitParams rcParams;
903 rcParams.rhi = rhi;
904 rcParams.sampleCount = rhiSampleCount;
905 rcParams.initialSurfacePixelSize = windowSize * qreal(dpr);
906 rcParams.maybeSurface = window;
907 sgrc->initialize(params: &rcParams);
908 }
909 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
910 if (rhi && !cd->swapchain) {
911 cd->rhi = rhi;
912 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
913 const QSurfaceFormat requestedFormat = window->requestedFormat();
914
915 // QQ is always premul alpha. Decide based on alphaBufferSize in
916 // requestedFormat(). (the platform plugin can override format() but
917 // what matters here is what the application wanted, hence using the
918 // requested one)
919 const bool alpha = requestedFormat.alphaBufferSize() > 0;
920 if (alpha)
921 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
922
923 // Request NoVSync if swap interval was set to 0 (either by the app or
924 // by QSG_NO_VSYNC). What this means in practice is another question,
925 // but at least we tried.
926 if (requestedFormat.swapInterval() == 0) {
927 qCDebug(QSG_LOG_INFO, "Swap interval is 0, attempting to disable vsync when presenting.");
928 flags |= QRhiSwapChain::NoVSync;
929 }
930
931 cd->swapchain = rhi->newSwapChain();
932 static bool depthBufferEnabled = qEnvironmentVariableIsEmpty(varName: "QSG_NO_DEPTH_BUFFER");
933 if (depthBufferEnabled) {
934 cd->depthStencilForSwapchain = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil,
935 pixelSize: QSize(),
936 sampleCount: rhiSampleCount,
937 flags: QRhiRenderBuffer::UsedWithSwapChainOnly);
938 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
939 }
940 cd->swapchain->setWindow(window);
941 cd->swapchain->setProxyData(scProxyData);
942 QSGRhiSupport::instance()->applySwapChainFormat(scWithWindowSet: cd->swapchain, window);
943 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
944 rhiSampleCount, alpha ? "yes" : "no");
945 cd->swapchain->setSampleCount(rhiSampleCount);
946 cd->swapchain->setFlags(flags);
947 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
948 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
949 }
950}
951
952void QSGRenderThread::run()
953{
954 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()");
955 animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(parent: nullptr);
956 animatorDriver->install();
957 if (QQmlDebugConnector::service<QQmlProfilerService>())
958 QQuickProfiler::registerAnimationCallback();
959
960 m_threadTimeBetweenRenders.start();
961
962 while (active) {
963#ifdef Q_OS_DARWIN
964 QMacAutoReleasePool frameReleasePool;
965#endif
966
967 if (window) {
968 ensureRhi();
969
970 // We absolutely have to syncAndRender() here, even when QRhi
971 // failed to initialize otherwise the gui thread will be left
972 // in a blocked state. It is up to syncAndRender() to
973 // gracefully skip all graphics stuff when rhi is null.
974
975 syncAndRender();
976
977 // Now we can do something about rhi init failures. (reinit
978 // failure after device reset does not count)
979 if (rhiDoomed && !guiNotifiedAboutRhiFailure) {
980 guiNotifiedAboutRhiFailure = true;
981 QEvent *e = new QEvent(QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure));
982 QCoreApplication::postEvent(receiver: window, event: e);
983 }
984 }
985
986 processEvents();
987 QCoreApplication::processEvents();
988
989 if (active && (pendingUpdate == 0 || !window)) {
990 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "done drawing, sleep...");
991 sleeping = true;
992 processEventsAndWaitForMore();
993 sleeping = false;
994 }
995 }
996
997 Q_ASSERT_X(!rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread...");
998
999 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed");
1000
1001 delete animatorDriver;
1002 animatorDriver = nullptr;
1003
1004 sgrc->moveToThread(thread: wm->thread());
1005 moveToThread(thread: wm->thread());
1006}
1007
1008QSGThreadedRenderLoop::QSGThreadedRenderLoop()
1009 : sg(QSGContext::createDefaultContext())
1010 , m_animation_timer(0)
1011{
1012 m_animation_driver = sg->createAnimationDriver(parent: this);
1013
1014 connect(sender: m_animation_driver, SIGNAL(started()), receiver: this, SLOT(animationStarted()));
1015 connect(sender: m_animation_driver, SIGNAL(stopped()), receiver: this, SLOT(animationStopped()));
1016
1017 m_animation_driver->install();
1018}
1019
1020QSGThreadedRenderLoop::~QSGThreadedRenderLoop()
1021{
1022 qDeleteAll(c: pendingRenderContexts);
1023 delete sg;
1024}
1025
1026QSGRenderContext *QSGThreadedRenderLoop::createRenderContext(QSGContext *sg) const
1027{
1028 auto context = sg->createRenderContext();
1029 pendingRenderContexts.insert(value: context);
1030 return context;
1031}
1032
1033void QSGThreadedRenderLoop::postUpdateRequest(Window *w)
1034{
1035 w->window->requestUpdate();
1036}
1037
1038QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const
1039{
1040 return m_animation_driver;
1041}
1042
1043QSGContext *QSGThreadedRenderLoop::sceneGraphContext() const
1044{
1045 return sg;
1046}
1047
1048bool QSGThreadedRenderLoop::anyoneShowing() const
1049{
1050 for (int i=0; i<m_windows.size(); ++i) {
1051 QQuickWindow *c = m_windows.at(i).window;
1052 if (c->isVisible() && c->isExposed())
1053 return true;
1054 }
1055 return false;
1056}
1057
1058bool QSGThreadedRenderLoop::interleaveIncubation() const
1059{
1060 return m_animation_driver->isRunning() && anyoneShowing();
1061}
1062
1063void QSGThreadedRenderLoop::animationStarted()
1064{
1065 qCDebug(QSG_LOG_RENDERLOOP, "- animationStarted()");
1066 startOrStopAnimationTimer();
1067
1068 for (int i=0; i<m_windows.size(); ++i)
1069 postUpdateRequest(w: const_cast<Window *>(&m_windows.at(i)));
1070}
1071
1072void QSGThreadedRenderLoop::animationStopped()
1073{
1074 qCDebug(QSG_LOG_RENDERLOOP, "- animationStopped()");
1075 startOrStopAnimationTimer();
1076}
1077
1078
1079void QSGThreadedRenderLoop::startOrStopAnimationTimer()
1080{
1081 if (!sg->isVSyncDependent(driver: m_animation_driver))
1082 return;
1083
1084 int exposedWindows = 0;
1085 int unthrottledWindows = 0;
1086 int badVSync = 0;
1087 const Window *theOne = nullptr;
1088 for (int i=0; i<m_windows.size(); ++i) {
1089 const Window &w = m_windows.at(i);
1090 if (w.window->isVisible() && w.window->isExposed()) {
1091 ++exposedWindows;
1092 theOne = &w;
1093 if (w.actualWindowFormat.swapInterval() == 0)
1094 ++unthrottledWindows;
1095 if (w.badVSync)
1096 ++badVSync;
1097 }
1098 }
1099
1100 // Best case: with 1 exposed windows we can advance regular animations in
1101 // polishAndSync() and rely on being throttled to vsync. (no normal system
1102 // timer needed)
1103 //
1104 // Special case: with no windows exposed (e.g. on Windows: all of them are
1105 // minimized) run a normal system timer to make non-visual animation
1106 // functional still.
1107 //
1108 // Not so ideal case: with more than one window exposed we have to use the
1109 // same path as the no-windows case since polishAndSync() is now called
1110 // potentially for multiple windows over time so it cannot take care of
1111 // advancing the animation driver anymore.
1112 //
1113 // On top, another case: a window with vsync disabled should disable all the
1114 // good stuff and go with the system timer.
1115 //
1116 // Similarly, if there is at least one window where we determined that
1117 // vsync based blocking is not working as expected, that should make us
1118 // choose the timer based way.
1119
1120 const bool canUseVSyncBasedAnimation = exposedWindows == 1 && unthrottledWindows == 0 && badVSync == 0;
1121
1122 if (m_animation_timer != 0 && (canUseVSyncBasedAnimation || !m_animation_driver->isRunning())) {
1123 qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
1124 exposedWindows, unthrottledWindows, badVSync);
1125 killTimer(id: m_animation_timer);
1126 m_animation_timer = 0;
1127 // If animations are running, make sure we keep on animating
1128 if (m_animation_driver->isRunning())
1129 postUpdateRequest(w: const_cast<Window *>(theOne));
1130 } else if (m_animation_timer == 0 && !canUseVSyncBasedAnimation && m_animation_driver->isRunning()) {
1131 qCDebug(QSG_LOG_RENDERLOOP, "*** Starting system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
1132 exposedWindows, unthrottledWindows, badVSync);
1133 m_animation_timer = startTimer(interval: int(sg->vsyncIntervalForAnimationDriver(driver: m_animation_driver)));
1134 }
1135}
1136
1137/*
1138 Removes this window from the list of tracked windowes in this
1139 window manager. hide() will trigger obscure, which in turn will
1140 stop rendering.
1141
1142 This function will be called during QWindow::close() which will
1143 also destroy the QPlatformWindow so it is important that this
1144 triggers handleObscurity() and that rendering for that window
1145 is fully done and over with by the time this function exits.
1146 */
1147
1148void QSGThreadedRenderLoop::hide(QQuickWindow *window)
1149{
1150 qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
1151
1152 if (window->isExposed())
1153 handleObscurity(w: windowFor(window));
1154
1155 releaseResources(window);
1156}
1157
1158void QSGThreadedRenderLoop::resize(QQuickWindow *window)
1159{
1160 qCDebug(QSG_LOG_RENDERLOOP) << "reisze()" << window;
1161
1162 Window *w = windowFor(window);
1163 if (!w)
1164 return;
1165
1166 w->psTimeAccumulator = 0.0f;
1167 w->psTimeSampleCount = 0;
1168}
1169
1170/*
1171 If the window is first hide it, then perform a complete cleanup
1172 with releaseResources which will take down the GL context and
1173 exit the rendering thread.
1174 */
1175void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
1176{
1177 qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
1178
1179 Window *w = windowFor(window);
1180 if (!w)
1181 return;
1182
1183 handleObscurity(w);
1184 releaseResources(window: w, inDestructor: true);
1185
1186 QSGRenderThread *thread = w->thread;
1187 while (thread->isRunning())
1188 QThread::yieldCurrentThread();
1189 Q_ASSERT(thread->thread() == QThread::currentThread());
1190 delete thread;
1191
1192 for (int i=0; i<m_windows.size(); ++i) {
1193 if (m_windows.at(i).window == window) {
1194 m_windows.removeAt(i);
1195 break;
1196 }
1197 }
1198
1199 // Now that we altered the window list, we may need to stop the animation
1200 // timer even if we didn't via handleObscurity. This covers the case where
1201 // we destroy a visible & exposed QQuickWindow.
1202 startOrStopAnimationTimer();
1203
1204 qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
1205}
1206
1207void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
1208{
1209 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1210 delete wd->rpDescForSwapchain;
1211 wd->rpDescForSwapchain = nullptr;
1212 delete wd->swapchain;
1213 wd->swapchain = nullptr;
1214 delete wd->depthStencilForSwapchain;
1215 wd->depthStencilForSwapchain = nullptr;
1216 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
1217}
1218
1219void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
1220{
1221 qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
1222
1223 // This is tricker than used to be. We want to detect having an empty
1224 // surface size (which may be the case even when window->size() is
1225 // non-empty, on some platforms with some graphics APIs!) as well as the
1226 // case when the window just became "newly exposed" (e.g. after a
1227 // minimize-restore on Windows, or when switching between fully obscured -
1228 // not fully obscured on macOS)
1229 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1230 if (!window->isExposed())
1231 wd->hasRenderableSwapchain = false;
1232
1233 bool skipThisExpose = false;
1234 if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) {
1235 wd->hasRenderableSwapchain = false;
1236 skipThisExpose = true;
1237 }
1238
1239 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
1240 && !wd->swapchain->surfacePixelSize().isEmpty())
1241 {
1242 wd->hasRenderableSwapchain = true;
1243 wd->swapchainJustBecameRenderable = true;
1244 }
1245
1246 if (window->isExposed()) {
1247 if (!skipThisExpose)
1248 handleExposure(w: window);
1249 } else {
1250 Window *w = windowFor(window);
1251 if (w)
1252 handleObscurity(w);
1253 }
1254}
1255
1256/*
1257 Will post an event to the render thread that this window should
1258 start to render.
1259 */
1260void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
1261{
1262 qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
1263
1264 Window *w = windowFor(window);
1265 if (!w) {
1266 qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
1267 Window win;
1268 win.window = window;
1269 win.actualWindowFormat = window->format();
1270 auto renderContext = QQuickWindowPrivate::get(c: window)->context;
1271 // The thread assumes ownership, so we don't need to delete it later.
1272 pendingRenderContexts.remove(value: renderContext);
1273 win.thread = new QSGRenderThread(this, renderContext);
1274 win.updateDuringSync = false;
1275 win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
1276 win.badVSync = false;
1277 win.timeBetweenPolishAndSyncs.start();
1278 win.psTimeAccumulator = 0.0f;
1279 win.psTimeSampleCount = 0;
1280 m_windows << win;
1281 w = &m_windows.last();
1282 } else {
1283 if (!QQuickWindowPrivate::get(c: window)->updatesEnabled) {
1284 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1285 return;
1286 }
1287 }
1288
1289 // set this early as we'll be rendering shortly anyway and this avoids
1290 // specialcasing exposure in polishAndSync.
1291 w->thread->window = window;
1292
1293#ifndef QT_NO_DEBUG
1294 if (w->window->width() <= 0 || w->window->height() <= 0
1295 || (w->window->isTopLevel() && !w->window->geometry().intersects(r: w->window->screen()->availableGeometry()))) {
1296 qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
1297 << w->window << " with invalid geometry: " << w->window->geometry()
1298 << " on " << w->window->screen();
1299 }
1300#endif
1301
1302 // Because we are going to bind a GL context to it, make sure it
1303 // is created.
1304 if (!w->window->handle())
1305 w->window->create();
1306
1307 // Start render thread if it is not running
1308 if (!w->thread->isRunning()) {
1309 qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread");
1310
1311 if (!w->thread->rhi) {
1312 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
1313 if (!w->thread->offscreenSurface)
1314 w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
1315 w->thread->scProxyData = QRhi::updateSwapChainProxyData(impl: rhiSupport->rhiBackend(), window);
1316 window->installEventFilter(filterObj: this);
1317 }
1318
1319 QQuickAnimatorController *controller
1320 = QQuickWindowPrivate::get(c: w->window)->animationController.get();
1321 if (controller->thread() != w->thread)
1322 controller->moveToThread(thread: w->thread);
1323
1324 w->thread->active = true;
1325 if (w->thread->thread() == QThread::currentThread()) {
1326 w->thread->sgrc->moveToThread(thread: w->thread);
1327 w->thread->moveToThread(thread: w->thread);
1328 }
1329 w->thread->start();
1330 if (!w->thread->isRunning())
1331 qFatal(msg: "Render thread failed to start, aborting application.");
1332
1333 } else {
1334 qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running");
1335 }
1336
1337 polishAndSync(w, inExpose: true);
1338 qCDebug(QSG_LOG_RENDERLOOP, "- done with handleExposure()");
1339
1340 startOrStopAnimationTimer();
1341}
1342
1343/*
1344 This function posts an event to the render thread to remove the window
1345 from the list of windowses to render.
1346
1347 It also starts up the non-vsync animation tick if no more windows
1348 are showing.
1349 */
1350void QSGThreadedRenderLoop::handleObscurity(Window *w)
1351{
1352 if (!w)
1353 return;
1354
1355 qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
1356 if (w->thread->isRunning()) {
1357 if (!QQuickWindowPrivate::get(c: w->window)->updatesEnabled) {
1358 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1359 return;
1360 }
1361 w->thread->mutex.lock();
1362 w->thread->postEvent(e: new WMWindowEvent(w->window, QEvent::Type(WM_Obscure)));
1363 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1364 w->thread->mutex.unlock();
1365 }
1366 startOrStopAnimationTimer();
1367}
1368
1369bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
1370{
1371 switch (event->type()) {
1372 case QEvent::PlatformSurface:
1373 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
1374 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1375 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: watched);
1376 if (window) {
1377 Window *w = windowFor(window);
1378 if (w && w->thread->isRunning()) {
1379 w->thread->mutex.lock();
1380 w->thread->postEvent(e: new WMReleaseSwapchainEvent(window));
1381 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1382 w->thread->mutex.unlock();
1383 }
1384 }
1385 // keep this filter on the window - needed for uncommon but valid
1386 // sequences of calls like window->destroy(); window->show();
1387 }
1388 break;
1389 default:
1390 break;
1391 }
1392 return QObject::eventFilter(watched, event);
1393}
1394
1395void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
1396{
1397 qCDebug(QSG_LOG_RENDERLOOP) << "- update request" << window;
1398 if (!QQuickWindowPrivate::get(c: window)->updatesEnabled) {
1399 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1400 return;
1401 }
1402 Window *w = windowFor(window);
1403 if (w)
1404 polishAndSync(w);
1405}
1406
1407void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
1408{
1409 Window *w = windowFor(window);
1410 if (w)
1411 maybeUpdate(window: w);
1412}
1413
1414/*
1415 Called whenever the QML scene has changed. Will post an event to
1416 ourselves that a sync is needed.
1417 */
1418void QSGThreadedRenderLoop::maybeUpdate(Window *w)
1419{
1420 if (!QCoreApplication::instance())
1421 return;
1422
1423 if (!w || !w->thread->isRunning())
1424 return;
1425
1426 QThread *current = QThread::currentThread();
1427 if (current == w->thread && w->thread->rhi && w->thread->rhi->isDeviceLost())
1428 return;
1429 if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
1430 qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1431 return;
1432 }
1433
1434 qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
1435
1436 // Call this function from the Gui thread later as startTimer cannot be
1437 // called from the render thread.
1438 if (current == w->thread) {
1439 qCDebug(QSG_LOG_RENDERLOOP, "- on render thread");
1440 w->updateDuringSync = true;
1441 return;
1442 }
1443
1444 // An updatePolish() implementation may call update() to get the QQuickItem
1445 // dirtied. That's fine but it also leads to calling this function.
1446 // Requesting another update is a waste then since the updatePolish() call
1447 // will be followed up with a round of sync and render.
1448 if (m_inPolish)
1449 return;
1450
1451 postUpdateRequest(w);
1452}
1453
1454/*
1455 Called when the QQuickWindow should be explicitly repainted. This function
1456 can also be called on the render thread when the GUI thread is blocked to
1457 keep render thread animations alive.
1458 */
1459void QSGThreadedRenderLoop::update(QQuickWindow *window)
1460{
1461 Window *w = windowFor(window);
1462 if (!w)
1463 return;
1464
1465 const bool isRenderThread = QThread::currentThread() == w->thread;
1466
1467#if defined(Q_OS_MACOS)
1468 using namespace QNativeInterface::Private;
1469 if (auto *cocoaWindow = dynamic_cast<QCocoaWindow*>(window->handle())) {
1470 // If the window is being resized we don't want to schedule unthrottled
1471 // updates on the render thread, as this will starve the main thread
1472 // from getting drawables for displaying the updated window size.
1473 if (isRenderThread && cocoaWindow->inLiveResize()) {
1474 // In most cases the window will already have update requested
1475 // due to the animator triggering a sync, but just in case we
1476 // schedule an update request on the main thread explicitly.
1477 qCDebug(QSG_LOG_RENDERLOOP) << "window is resizing. update on window" << w->window;
1478 QTimer::singleShot(0, window, [=]{ window->requestUpdate(); });
1479 return;
1480 }
1481 }
1482#endif
1483
1484 if (isRenderThread) {
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
1498void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
1499{
1500 Window *w = windowFor(window);
1501 if (w)
1502 releaseResources(window: w, inDestructor: 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 */
1509void 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 // RHI resources.
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)->deliveryAgentPrivate()->flushFrameSynchronousEvents(win: window);
1557 // The delivery of the event might have caused the window to stop rendering
1558 w = windowFor(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
1570 const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
1571
1572 if (w->actualWindowFormat.swapInterval() != 0 && sg->isVSyncDependent(driver: m_animation_driver)) {
1573 w->psTimeAccumulator += elapsedSinceLastMs;
1574 w->psTimeSampleCount += 1;
1575 // cannot be too high because we'd then delay recognition of broken vsync at start
1576 static const int PS_TIME_SAMPLE_LENGTH = 20;
1577 if (w->psTimeSampleCount > PS_TIME_SAMPLE_LENGTH) {
1578 const float t = w->psTimeAccumulator / w->psTimeSampleCount;
1579 const float vsyncRate = sg->vsyncIntervalForAnimationDriver(driver: m_animation_driver);
1580
1581 // What this means is that the last PS_TIME_SAMPLE_LENGTH frames
1582 // average to an elapsed time of t milliseconds, whereas the animation
1583 // driver (assuming a single window, vsync-based advancing) assumes a
1584 // vsyncRate milliseconds for a frame. If now we see that the elapsed
1585 // time is way too low (less than half of the approx. expected value),
1586 // then we assume that something is wrong with vsync.
1587 //
1588 // This will not capture everything. Consider a 144 Hz screen with 6.9
1589 // ms vsync rate, the half of that is below the default 5 ms timer of
1590 // QWindow::requestUpdate(), so this will not trigger even if the
1591 // graphics stack does not throttle. But then the whole workaround is
1592 // not that important because the animations advance anyway closer to
1593 // what's expected (e.g. advancing as if 6-7 ms passed after ca. 5 ms),
1594 // the gap is a lot smaller than with the 60 Hz case (animations
1595 // advancing as if 16 ms passed after just ca. 5 ms) The workaround
1596 // here is present mainly for virtual machines and other broken
1597 // environments, most of which will persumably report a 60 Hz screen.
1598
1599 const float threshold = vsyncRate * 0.5f;
1600 const bool badVSync = t < threshold;
1601 if (badVSync && !w->badVSync) {
1602 // Once we determine something is wrong with the frame rate, set
1603 // the flag for the rest of the lifetime of the window. This is
1604 // saner and more deterministic than allowing it to be turned on
1605 // and off. (a window resize can take up time, leading to higher
1606 // elapsed times, thus unnecessarily starting to switch modes,
1607 // while some platforms seem to have advanced logic (and adaptive
1608 // refresh rates an whatnot) that can eventually start throttling
1609 // an unthrottled window, potentially leading to a continuous
1610 // switching of modes back and forth which is not desirable.
1611 w->badVSync = true;
1612 qCDebug(QSG_LOG_INFO, "Window %p is determined to have broken vsync throttling (%f < %f) "
1613 "switching to system timer to drive gui thread animations to remedy this "
1614 "(however, render thread animators will likely advance at an incorrect rate).",
1615 w->window, t, threshold);
1616 startOrStopAnimationTimer();
1617 }
1618
1619 w->psTimeAccumulator = 0.0f;
1620 w->psTimeSampleCount = 0;
1621 }
1622 }
1623
1624 const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
1625 if (profileFrames) {
1626 timer.start();
1627 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] polishAndSync: start, elapsed since last call: %d ms",
1628 window,
1629 int(elapsedSinceLastMs));
1630 }
1631 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1632 Q_TRACE(QSG_polishItems_entry);
1633
1634 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
1635 m_inPolish = true;
1636 d->polishItems();
1637 m_inPolish = false;
1638
1639 if (profileFrames)
1640 polishTime = timer.nsecsElapsed();
1641 Q_TRACE(QSG_polishItems_exit);
1642 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1643 QQuickProfiler::SceneGraphPolishAndSyncPolish);
1644 Q_TRACE(QSG_wait_entry);
1645
1646 w->updateDuringSync = false;
1647
1648 emit window->afterAnimating();
1649
1650 const QRhiSwapChainProxyData scProxyData =
1651 QRhi::updateSwapChainProxyData(impl: QSGRhiSupport::instance()->rhiBackend(), window);
1652
1653 qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
1654 w->thread->mutex.lock();
1655 m_lockedForSync = true;
1656 w->thread->postEvent(e: new WMSyncEvent(window, inExpose, w->forceRenderPass, scProxyData));
1657 w->forceRenderPass = false;
1658
1659 qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
1660 if (profileFrames)
1661 waitTime = timer.nsecsElapsed();
1662 Q_TRACE(QSG_wait_exit);
1663 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1664 QQuickProfiler::SceneGraphPolishAndSyncWait);
1665 Q_TRACE(QSG_sync_entry);
1666
1667 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1668 m_lockedForSync = false;
1669 w->thread->mutex.unlock();
1670 qCDebug(QSG_LOG_RENDERLOOP, "- unlock after sync");
1671
1672 if (profileFrames)
1673 syncTime = timer.nsecsElapsed();
1674 Q_TRACE(QSG_sync_exit);
1675 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1676 QQuickProfiler::SceneGraphPolishAndSyncSync);
1677 Q_TRACE(QSG_animations_entry);
1678
1679 // Now is the time to advance the regular animations (as we are throttled
1680 // to vsync due to the wait above), but this is only relevant when there is
1681 // one single window. With multiple windows m_animation_timer is active,
1682 // and advance() happens instead in response to a good old timer event, not
1683 // here. (the above applies only when the QSGAnimationDriver reports
1684 // isVSyncDependent() == true, if not then we always use the driver and
1685 // just advance here)
1686 if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
1687 auto advanceAnimations = [this, window=QPointer(window)] {
1688 qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
1689 m_animation_driver->advance();
1690 qCDebug(QSG_LOG_RENDERLOOP, "- animations done..");
1691
1692 // We need to trigger another update round to keep all animations
1693 // running correctly. For animations that lead to a visual change (a
1694 // property change in some item leading to dirtying the item and so
1695 // ending up in maybeUpdate()) this would not be needed, but other
1696 // animations would then stop functioning since there is nothing
1697 // advancing the animation system if we do not call postUpdateRequest()
1698 // here and nothing else leads to it either. This has an unfortunate
1699 // side effect in multi window cases: one can end up in a situation
1700 // where a non-animating window gets updates continuously because there
1701 // is an animation running in some other window that is non-exposed or
1702 // even closed already (if it was exposed we would not hit this branch,
1703 // however). Sadly, there is nothing that can be done about it.
1704 if (window)
1705 window->requestUpdate();
1706
1707 emit timeToIncubate();
1708 };
1709
1710#if defined(Q_OS_APPLE)
1711 if (inExpose) {
1712 // If we are handling an expose event the system is expecting us to
1713 // produce a frame that it can present on screen to the user. Advancing
1714 // animations at this point might result in changing properties of the
1715 // window in a way that invalidates the current frame, resulting in the
1716 // discarding of the current frame before the user ever sees it. To give
1717 // the system a chance to present the current frame we defer the advance
1718 // of the animations until the start of the next event loop pass, which
1719 // should still give plenty of time to compute the new render state before
1720 // the next expose event or update request.
1721 QMetaObject::invokeMethod(this, advanceAnimations, Qt::QueuedConnection);
1722 } else
1723#endif // Q_OS_APPLE
1724 {
1725 // For regular update requests we assume we can advance here synchronously
1726 advanceAnimations();
1727 }
1728 } else if (w->updateDuringSync) {
1729 postUpdateRequest(w);
1730 }
1731
1732 if (profileFrames) {
1733 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] Frame prepared, polish=%d ms, lock=%d ms, blockedForSync=%d ms, animations=%d ms",
1734 window,
1735 int(polishTime / 1000000),
1736 int((waitTime - polishTime) / 1000000),
1737 int((syncTime - waitTime) / 1000000),
1738 int((timer.nsecsElapsed() - syncTime) / 1000000));
1739 }
1740
1741 Q_TRACE(QSG_animations_exit);
1742 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1743 QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1744}
1745
1746bool QSGThreadedRenderLoop::event(QEvent *e)
1747{
1748 switch ((int) e->type()) {
1749
1750 case QEvent::Timer: {
1751 Q_ASSERT(sg->isVSyncDependent(m_animation_driver));
1752 QTimerEvent *te = static_cast<QTimerEvent *>(e);
1753 if (te->timerId() == m_animation_timer) {
1754 qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-render thread timer");
1755 m_animation_driver->advance();
1756 emit timeToIncubate();
1757 return true;
1758 }
1759 break;
1760 }
1761
1762 default:
1763 break;
1764 }
1765
1766 return QObject::event(event: e);
1767}
1768
1769
1770
1771/*
1772 Locks down GUI and performs a grab the scene graph, then returns the result.
1773
1774 Since the QML scene could have changed since the last time it was rendered,
1775 we need to polish and sync the scene graph. This might seem superfluous, but
1776 - QML changes could have triggered deleteLater() which could have removed
1777 textures or other objects from the scene graph, causing render to crash.
1778 - Autotests rely on grab(), setProperty(), grab(), compare behavior.
1779 */
1780
1781QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
1782{
1783 qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
1784
1785 Window *w = windowFor(window);
1786 Q_ASSERT(w);
1787
1788 if (!w->thread->isRunning())
1789 return QImage();
1790
1791 if (!window->handle())
1792 window->create();
1793
1794 qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
1795 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
1796 m_inPolish = true;
1797 d->polishItems();
1798 m_inPolish = false;
1799
1800 QImage result;
1801 w->thread->mutex.lock();
1802 m_lockedForSync = true;
1803 qCDebug(QSG_LOG_RENDERLOOP, "- posting grab event");
1804 w->thread->postEvent(e: new WMGrabEvent(window, &result));
1805 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
1806 m_lockedForSync = false;
1807 w->thread->mutex.unlock();
1808
1809 qCDebug(QSG_LOG_RENDERLOOP, "- grab complete");
1810
1811 return result;
1812}
1813
1814/*
1815 * Posts a new job event to the render thread.
1816 * Returns true if posting succeeded.
1817 */
1818void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
1819{
1820 Window *w = windowFor(window);
1821 if (w && w->thread && w->thread->window)
1822 w->thread->postEvent(e: new WMJobEvent(window, job));
1823 else
1824 delete job;
1825}
1826
1827QT_END_NAMESPACE
1828
1829#include "qsgthreadedrenderloop.moc"
1830#include "moc_qsgthreadedrenderloop_p.cpp"
1831

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