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