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 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
942 }
943 cd->swapchain->setWindow(window);
944 cd->swapchain->setProxyData(scProxyData);
945 QSGRhiSupport::instance()->applySwapChainFormat(cd->swapchain, window);
946 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.",
947 rhiSampleCount, alpha ? "yes" : "no");
948 cd->swapchain->setSampleCount(rhiSampleCount);
949 cd->swapchain->setFlags(flags);
950 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
951 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
952 }
953}
954
956{
957 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()");
958 animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(nullptr);
959 animatorDriver->install();
960 if (QQmlDebugConnector::service<QQmlProfilerService>())
961 QQuickProfiler::registerAnimationCallback();
962
963 m_threadTimeBetweenRenders.start();
964
965 while (active) {
966#ifdef Q_OS_DARWIN
967 QMacAutoReleasePool frameReleasePool;
968#endif
969
970 if (window) {
972
973 // We absolutely have to syncAndRender() here, even when QRhi
974 // failed to initialize otherwise the gui thread will be left
975 // in a blocked state. It is up to syncAndRender() to
976 // gracefully skip all graphics stuff when rhi is null.
977
979
980 // Now we can do something about rhi init failures. (reinit
981 // failure after device reset does not count)
984 QEvent *e = new QEvent(QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure));
985 QCoreApplication::postEvent(window, e);
986 }
987 }
988
990 QCoreApplication::processEvents();
991
992 if (active && (pendingUpdate == 0 || !window)) {
993 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "done drawing, sleep...");
994 sleeping = true;
996 sleeping = false;
997 }
998 }
999
1000 Q_ASSERT_X(!rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread...");
1001
1002 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed");
1003
1004 delete animatorDriver;
1005 animatorDriver = nullptr;
1006
1007 sgrc->moveToThread(wm->thread());
1008 moveToThread(wm->thread());
1009}
1010
1011QSGThreadedRenderLoop::QSGThreadedRenderLoop()
1012 : sg(QSGContext::createDefaultContext())
1013 , m_animation_timer(0)
1014{
1015 m_animation_driver = sg->createAnimationDriver(this);
1016
1017 connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted()));
1018 connect(m_animation_driver, SIGNAL(stopped()), this, SLOT(animationStopped()));
1019
1020 m_animation_driver->install();
1021}
1022
1024{
1025 qDeleteAll(pendingRenderContexts);
1026 delete sg;
1027}
1028
1029QSGRenderContext *QSGThreadedRenderLoop::createRenderContext(QSGContext *sg) const
1030{
1031 auto context = sg->createRenderContext();
1032 pendingRenderContexts.insert(context);
1033 return context;
1034}
1035
1036void QSGThreadedRenderLoop::postUpdateRequest(Window *w)
1037{
1038 w->window->requestUpdate();
1039}
1040
1041QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const
1042{
1043 return m_animation_driver;
1044}
1045
1047{
1048 return sg;
1049}
1050
1051bool QSGThreadedRenderLoop::anyoneShowing() const
1052{
1053 for (int i=0; i<m_windows.size(); ++i) {
1054 QQuickWindow *c = m_windows.at(i).window;
1055 if (c->isVisible() && c->isExposed())
1056 return true;
1057 }
1058 return false;
1059}
1060
1062{
1063 return m_animation_driver->isRunning() && anyoneShowing();
1064}
1065
1066void QSGThreadedRenderLoop::animationStarted()
1067{
1068 qCDebug(QSG_LOG_RENDERLOOP, "- animationStarted()");
1069 startOrStopAnimationTimer();
1070
1071 for (int i=0; i<m_windows.size(); ++i)
1072 postUpdateRequest(const_cast<Window *>(&m_windows.at(i)));
1073}
1074
1076{
1077 qCDebug(QSG_LOG_RENDERLOOP, "- animationStopped()");
1078 startOrStopAnimationTimer();
1079}
1080
1081
1082void QSGThreadedRenderLoop::startOrStopAnimationTimer()
1083{
1084 if (!sg->isVSyncDependent(m_animation_driver))
1085 return;
1086
1087 int exposedWindows = 0;
1088 int unthrottledWindows = 0;
1089 int badVSync = 0;
1090 const Window *theOne = nullptr;
1091 for (int i=0; i<m_windows.size(); ++i) {
1092 const Window &w = m_windows.at(i);
1093 if (w.window->isVisible() && w.window->isExposed()) {
1094 ++exposedWindows;
1095 theOne = &w;
1096 if (w.actualWindowFormat.swapInterval() == 0)
1097 ++unthrottledWindows;
1098 if (w.badVSync)
1099 ++badVSync;
1100 }
1101 }
1102
1103 // Best case: with 1 exposed windows we can advance regular animations in
1104 // polishAndSync() and rely on being throttled to vsync. (no normal system
1105 // timer needed)
1106 //
1107 // Special case: with no windows exposed (e.g. on Windows: all of them are
1108 // minimized) run a normal system timer to make non-visual animation
1109 // functional still.
1110 //
1111 // Not so ideal case: with more than one window exposed we have to use the
1112 // same path as the no-windows case since polishAndSync() is now called
1113 // potentially for multiple windows over time so it cannot take care of
1114 // advancing the animation driver anymore.
1115 //
1116 // On top, another case: a window with vsync disabled should disable all the
1117 // good stuff and go with the system timer.
1118 //
1119 // Similarly, if there is at least one window where we determined that
1120 // vsync based blocking is not working as expected, that should make us
1121 // choose the timer based way.
1122
1123 const bool canUseVSyncBasedAnimation = exposedWindows == 1 && unthrottledWindows == 0 && badVSync == 0;
1124
1125 if (m_animation_timer != 0 && (canUseVSyncBasedAnimation || !m_animation_driver->isRunning())) {
1126 qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
1127 exposedWindows, unthrottledWindows, badVSync);
1128 killTimer(m_animation_timer);
1129 m_animation_timer = 0;
1130 // If animations are running, make sure we keep on animating
1131 if (m_animation_driver->isRunning())
1132 postUpdateRequest(const_cast<Window *>(theOne));
1133 } else if (m_animation_timer == 0 && !canUseVSyncBasedAnimation && m_animation_driver->isRunning()) {
1134 qCDebug(QSG_LOG_RENDERLOOP, "*** Starting system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
1135 exposedWindows, unthrottledWindows, badVSync);
1136 m_animation_timer = startTimer(int(sg->vsyncIntervalForAnimationDriver(m_animation_driver)));
1137 }
1138}
1139
1140/*
1141 Removes this window from the list of tracked windowes in this
1142 window manager. hide() will trigger obscure, which in turn will
1143 stop rendering.
1144
1145 This function will be called during QWindow::close() which will
1146 also destroy the QPlatformWindow so it is important that this
1147 triggers handleObscurity() and that rendering for that window
1148 is fully done and over with by the time this function exits.
1149 */
1150
1151void QSGThreadedRenderLoop::hide(QQuickWindow *window)
1152{
1153 qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
1154
1155 if (window->isExposed())
1156 handleObscurity(windowFor(window));
1157
1158 releaseResources(window);
1159}
1160
1161void QSGThreadedRenderLoop::resize(QQuickWindow *window)
1162{
1163 qCDebug(QSG_LOG_RENDERLOOP) << "resize()" << window;
1164
1165 Window *w = windowFor(window);
1166 if (!w)
1167 return;
1168
1169 w->psTimeAccumulator = 0.0f;
1170 w->psTimeSampleCount = 0;
1171}
1172
1173/*
1174 If the window is first hide it, then perform a complete cleanup
1175 with releaseResources which will take down the GL context and
1176 exit the rendering thread.
1177 */
1178void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
1179{
1180 qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
1181
1182 Window *w = windowFor(window);
1183 if (!w)
1184 return;
1185
1186 handleObscurity(w);
1187 releaseResources(w, true);
1188
1189 QSGRenderThread *thread = w->thread;
1190 while (thread->isRunning())
1191 QThread::yieldCurrentThread();
1192 Q_ASSERT(thread->thread() == QThread::currentThread());
1193 delete thread;
1194
1195 for (int i=0; i<m_windows.size(); ++i) {
1196 if (m_windows.at(i).window == window) {
1197 m_windows.removeAt(i);
1198 break;
1199 }
1200 }
1201
1202 // Now that we altered the window list, we may need to stop the animation
1203 // timer even if we didn't via handleObscurity. This covers the case where
1204 // we destroy a visible & exposed QQuickWindow.
1205 startOrStopAnimationTimer();
1206
1207 qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
1208}
1209
1210void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
1211{
1212 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1213 delete wd->rpDescForSwapchain;
1214 wd->rpDescForSwapchain = nullptr;
1215 delete wd->swapchain;
1216 wd->swapchain = nullptr;
1217 delete wd->depthStencilForSwapchain;
1218 wd->depthStencilForSwapchain = nullptr;
1219 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
1220}
1221
1222void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
1223{
1224 qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
1225
1226 // This is tricker than used to be. We want to detect having an empty
1227 // surface size (which may be the case even when window->size() is
1228 // non-empty, on some platforms with some graphics APIs!) as well as the
1229 // case when the window just became "newly exposed" (e.g. after a
1230 // minimize-restore on Windows, or when switching between fully obscured -
1231 // not fully obscured on macOS)
1232 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1233 if (!window->isExposed())
1234 wd->hasRenderableSwapchain = false;
1235
1236 bool skipThisExpose = false;
1237 if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) {
1238 wd->hasRenderableSwapchain = false;
1239 skipThisExpose = true;
1240 }
1241
1242 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
1243 && !wd->swapchain->surfacePixelSize().isEmpty())
1244 {
1245 wd->hasRenderableSwapchain = true;
1246 wd->swapchainJustBecameRenderable = true;
1247 }
1248
1249 if (window->isExposed()) {
1250 if (!skipThisExpose)
1251 handleExposure(window);
1252 } else {
1253 Window *w = windowFor(window);
1254 if (w)
1255 handleObscurity(w);
1256 }
1257}
1258
1259/*
1260 Will post an event to the render thread that this window should
1261 start to render.
1262 */
1263void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
1264{
1265 qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
1266
1267 Window *w = windowFor(window);
1268 if (!w) {
1269 qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
1270 Window win;
1271 win.window = window;
1272 win.actualWindowFormat = window->format();
1273 auto renderContext = QQuickWindowPrivate::get(window)->context;
1274 // The thread assumes ownership, so we don't need to delete it later.
1275 pendingRenderContexts.remove(renderContext);
1276 win.thread = new QSGRenderThread(this, renderContext);
1277 win.updateDuringSync = false;
1278 win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
1279 win.badVSync = false;
1280 win.timeBetweenPolishAndSyncs.start();
1281 win.psTimeAccumulator = 0.0f;
1282 win.psTimeSampleCount = 0;
1283 m_windows << win;
1284 w = &m_windows.last();
1285 } else {
1286 if (!QQuickWindowPrivate::get(window)->updatesEnabled) {
1287 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1288 return;
1289 }
1290 }
1291
1292#ifndef QT_NO_DEBUG
1293 if (w->window->width() <= 0 || w->window->height() <= 0
1294 || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
1295 qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
1296 << w->window << " with invalid geometry: " << w->window->geometry()
1297 << " on " << w->window->screen();
1298 }
1299#endif
1300
1301 // Because we are going to bind a GL context to it, make sure it
1302 // is created.
1303 if (!w->window->handle())
1304 w->window->create();
1305
1306 // Start render thread if it is not running
1307 if (!w->thread->isRunning()) {
1308 qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread");
1309
1310 // set this early as we'll be rendering shortly anyway and this avoids
1311 // specialcasing exposure in polishAndSync.
1312 w->thread->window = window;
1313
1314 if (!w->thread->rhi) {
1315 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
1316 if (!w->thread->offscreenSurface)
1317 w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
1318 w->thread->scProxyData = QRhi::updateSwapChainProxyData(rhiSupport->rhiBackend(), window);
1319 window->installEventFilter(this);
1320 }
1321
1322 QQuickAnimatorController *controller
1323 = QQuickWindowPrivate::get(w->window)->animationController.get();
1324 if (controller->thread() != w->thread)
1325 controller->moveToThread(w->thread);
1326
1327 w->thread->active = true;
1328 if (w->thread->thread() == QThread::currentThread()) {
1329 w->thread->sgrc->moveToThread(w->thread);
1330 w->thread->moveToThread(w->thread);
1331 }
1332 w->thread->start();
1333 if (!w->thread->isRunning())
1334 qFatal("Render thread failed to start, aborting application.");
1335
1336 } else {
1337 qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running");
1338
1339 // set w->thread->window here too, but using an event so it's thread-safe
1340 w->thread->mutex.lock();
1341 w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Exposed)));
1342 w->thread->waitCondition.wait(&w->thread->mutex);
1343 w->thread->mutex.unlock();
1344 }
1345
1346 polishAndSync(w, true);
1347 qCDebug(QSG_LOG_RENDERLOOP, "- done with handleExposure()");
1348
1349 startOrStopAnimationTimer();
1350}
1351
1352/*
1353 This function posts an event to the render thread to remove the window
1354 from the list of windowses to render.
1355
1356 It also starts up the non-vsync animation tick if no more windows
1357 are showing.
1358 */
1359void QSGThreadedRenderLoop::handleObscurity(Window *w)
1360{
1361 if (!w)
1362 return;
1363
1364 qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
1365 if (w->thread->isRunning()) {
1366 if (!QQuickWindowPrivate::get(w->window)->updatesEnabled) {
1367 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1368 return;
1369 }
1370 w->thread->mutex.lock();
1371 w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Obscure)));
1372 w->thread->waitCondition.wait(&w->thread->mutex);
1373 w->thread->mutex.unlock();
1374 }
1375 startOrStopAnimationTimer();
1376}
1377
1378bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
1379{
1380 switch (event->type()) {
1381 case QEvent::PlatformSurface:
1382 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
1383 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1384 QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
1385 if (window) {
1386 Window *w = windowFor(window);
1387 if (w && w->thread->isRunning()) {
1388 w->thread->mutex.lock();
1389 w->thread->postEvent(new WMReleaseSwapchainEvent(window));
1390 w->thread->waitCondition.wait(&w->thread->mutex);
1391 w->thread->mutex.unlock();
1392 }
1393 }
1394 // keep this filter on the window - needed for uncommon but valid
1395 // sequences of calls like window->destroy(); window->show();
1396 }
1397 break;
1398 default:
1399 break;
1400 }
1401 return QObject::eventFilter(watched, event);
1402}
1403
1405{
1406 qCDebug(QSG_LOG_RENDERLOOP) << "- update request" << window;
1407 if (!QQuickWindowPrivate::get(window)->updatesEnabled) {
1408 qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
1409 return;
1410 }
1411 Window *w = windowFor(window);
1412 if (w)
1413 polishAndSync(w);
1414}
1415
1416void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
1417{
1418 Window *w = windowFor(window);
1419 if (w)
1420 maybeUpdate(w);
1421}
1422
1423/*
1424 Called whenever the QML scene has changed. Will post an event to
1425 ourselves that a sync is needed.
1426 */
1427void QSGThreadedRenderLoop::maybeUpdate(Window *w)
1428{
1429 if (!QCoreApplication::instance())
1430 return;
1431
1432 if (!w || !w->thread->isRunning())
1433 return;
1434
1435 QThread *current = QThread::currentThread();
1436 if (current == w->thread && w->thread->rhi && w->thread->rhi->isDeviceLost())
1437 return;
1438 if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
1439 qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1440 return;
1441 }
1442
1443 qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
1444
1445 // Call this function from the Gui thread later as startTimer cannot be
1446 // called from the render thread.
1447 if (current == w->thread) {
1448 qCDebug(QSG_LOG_RENDERLOOP, "- on render thread");
1449 w->updateDuringSync = true;
1450 return;
1451 }
1452
1453 // An updatePolish() implementation may call update() to get the QQuickItem
1454 // dirtied. That's fine but it also leads to calling this function.
1455 // Requesting another update is a waste then since the updatePolish() call
1456 // will be followed up with a round of sync and render.
1457 if (m_inPolish)
1458 return;
1459
1460 postUpdateRequest(w);
1461}
1462
1463/*
1464 Called when the QQuickWindow should be explicitly repainted. This function
1465 can also be called on the render thread when the GUI thread is blocked to
1466 keep render thread animations alive.
1467 */
1468void QSGThreadedRenderLoop::update(QQuickWindow *window)
1469{
1470 Window *w = windowFor(window);
1471 if (!w)
1472 return;
1473
1474 const bool isRenderThread = QThread::currentThread() == w->thread;
1475
1476 if (QPlatformWindow *platformWindow = window->handle()) {
1477 // If the window is being resized we don't want to schedule unthrottled
1478 // updates on the render thread, as this will starve the main thread
1479 // from getting drawables for displaying the updated window size.
1480 if (isRenderThread && !platformWindow->allowsIndependentThreadedRendering()) {
1481 // In most cases the window will already have update requested
1482 // due to the animator triggering a sync, but just in case we
1483 // schedule an update request on the main thread explicitly.
1484 qCDebug(QSG_LOG_RENDERLOOP) << "window is resizing. update on window" << w->window;
1485 QTimer::singleShot(0, window, [=]{ window->requestUpdate(); });
1486 return;
1487 }
1488 }
1489
1490 if (isRenderThread) {
1491 qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
1492 w->thread->requestRepaint();
1493 return;
1494 }
1495
1496 qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
1497 // We set forceRenderPass because we want to make sure the QQuickWindow
1498 // actually does a full render pass after the next sync.
1499 w->forceRenderPass = true;
1500 maybeUpdate(w);
1501}
1502
1503
1504void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
1505{
1506 Window *w = windowFor(window);
1507 if (w)
1508 releaseResources(w, false);
1509}
1510
1511/*
1512 * Release resources will post an event to the render thread to
1513 * free up the SG and GL resources and exists the render thread.
1514 */
1515void QSGThreadedRenderLoop::releaseResources(Window *w, bool inDestructor)
1516{
1517 qCDebug(QSG_LOG_RENDERLOOP) << "releaseResources()" << (inDestructor ? "in destructor" : "in api-call") << w->window;
1518
1519 w->thread->mutex.lock();
1520 if (w->thread->isRunning() && w->thread->active) {
1521 QQuickWindow *window = w->window;
1522
1523 // The platform window might have been destroyed before
1524 // hide/release/windowDestroyed is called, so we may need to have a
1525 // fallback surface to perform the cleanup of the scene graph and the
1526 // RHI resources.
1527
1528 qCDebug(QSG_LOG_RENDERLOOP, "- posting release request to render thread");
1529 w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, window->handle() == nullptr));
1530 w->thread->waitCondition.wait(&w->thread->mutex);
1531
1532 // Avoid a shutdown race condition.
1533 // If SG is invalidated and 'active' becomes false, the thread's run()
1534 // method will exit. handleExposure() relies on QThread::isRunning() (because it
1535 // potentially needs to start the thread again) and our mutex cannot be used to
1536 // track the thread stopping, so we wait a few nanoseconds extra so the thread
1537 // can exit properly.
1538 if (!w->thread->active) {
1539 qCDebug(QSG_LOG_RENDERLOOP) << " - waiting for render thread to exit" << w->window;
1540 w->thread->wait();
1541 qCDebug(QSG_LOG_RENDERLOOP) << " - render thread finished" << w->window;
1542 }
1543 }
1544 w->thread->mutex.unlock();
1545}
1546
1547
1548/* Calls polish on all items, then requests synchronization with the render thread
1549 * and blocks until that is complete. Returns false if it aborted; otherwise true.
1550 */
1551void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
1552{
1553 qCDebug(QSG_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
1554
1555 QQuickWindow *window = w->window;
1556 if (!w->thread || !w->thread->window) {
1557 qCDebug(QSG_LOG_RENDERLOOP, "- not exposed, abort");
1558 return;
1559 }
1560
1561 // Flush pending touch events.
1562 QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
1563 // The delivery of the event might have caused the window to stop rendering
1564 w = windowFor(window);
1565 if (!w || !w->thread || !w->thread->window) {
1566 qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
1567 return;
1568 }
1569
1570 Q_TRACE_SCOPE(QSG_polishAndSync);
1571 QElapsedTimer timer;
1572 qint64 polishTime = 0;
1573 qint64 waitTime = 0;
1574 qint64 syncTime = 0;
1575
1576 const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
1577
1578 if (w->actualWindowFormat.swapInterval() != 0 && sg->isVSyncDependent(m_animation_driver)) {
1579 w->psTimeAccumulator += elapsedSinceLastMs;
1580 w->psTimeSampleCount += 1;
1581 // cannot be too high because we'd then delay recognition of broken vsync at start
1582 static const int PS_TIME_SAMPLE_LENGTH = 20;
1583 if (w->psTimeSampleCount > PS_TIME_SAMPLE_LENGTH) {
1584 const float t = w->psTimeAccumulator / w->psTimeSampleCount;
1585 const float vsyncRate = sg->vsyncIntervalForAnimationDriver(m_animation_driver);
1586
1587 // What this means is that the last PS_TIME_SAMPLE_LENGTH frames
1588 // average to an elapsed time of t milliseconds, whereas the animation
1589 // driver (assuming a single window, vsync-based advancing) assumes a
1590 // vsyncRate milliseconds for a frame. If now we see that the elapsed
1591 // time is way too low (less than half of the approx. expected value),
1592 // then we assume that something is wrong with vsync.
1593 //
1594 // This will not capture everything. Consider a 144 Hz screen with 6.9
1595 // ms vsync rate, the half of that is below the default 5 ms timer of
1596 // QWindow::requestUpdate(), so this will not trigger even if the
1597 // graphics stack does not throttle. But then the whole workaround is
1598 // not that important because the animations advance anyway closer to
1599 // what's expected (e.g. advancing as if 6-7 ms passed after ca. 5 ms),
1600 // the gap is a lot smaller than with the 60 Hz case (animations
1601 // advancing as if 16 ms passed after just ca. 5 ms) The workaround
1602 // here is present mainly for virtual machines and other broken
1603 // environments, most of which will persumably report a 60 Hz screen.
1604
1605 const float threshold = vsyncRate * 0.5f;
1606 const bool badVSync = t < threshold;
1607 if (badVSync && !w->badVSync) {
1608 // Once we determine something is wrong with the frame rate, set
1609 // the flag for the rest of the lifetime of the window. This is
1610 // saner and more deterministic than allowing it to be turned on
1611 // and off. (a window resize can take up time, leading to higher
1612 // elapsed times, thus unnecessarily starting to switch modes,
1613 // while some platforms seem to have advanced logic (and adaptive
1614 // refresh rates an whatnot) that can eventually start throttling
1615 // an unthrottled window, potentially leading to a continuous
1616 // switching of modes back and forth which is not desirable.
1617 w->badVSync = true;
1618 qCDebug(QSG_LOG_INFO, "Window %p is determined to have broken vsync throttling (%f < %f) "
1619 "switching to system timer to drive gui thread animations to remedy this "
1620 "(however, render thread animators will likely advance at an incorrect rate).",
1621 w->window, t, threshold);
1622 startOrStopAnimationTimer();
1623 }
1624
1625 w->psTimeAccumulator = 0.0f;
1626 w->psTimeSampleCount = 0;
1627 }
1628 }
1629
1630 const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
1631 if (profileFrames) {
1632 timer.start();
1633 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] polishAndSync: start, elapsed since last call: %d ms",
1634 window,
1635 int(elapsedSinceLastMs));
1636 }
1637 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1638 Q_TRACE(QSG_polishItems_entry);
1639
1640 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1641 m_inPolish = true;
1642 d->polishItems();
1643 m_inPolish = false;
1644
1645 if (profileFrames)
1646 polishTime = timer.nsecsElapsed();
1647 Q_TRACE(QSG_polishItems_exit);
1648 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1649 QQuickProfiler::SceneGraphPolishAndSyncPolish);
1650
1651 w = windowFor(window);
1652 if (!w || !w->thread || !w->thread->window) {
1653 qCDebug(QSG_LOG_RENDERLOOP, "- removed after polishing, abort");
1654 return;
1655 }
1656
1657 Q_TRACE(QSG_wait_entry);
1658 w->updateDuringSync = false;
1659
1660 emit window->afterAnimating();
1661
1662 const QRhiSwapChainProxyData scProxyData =
1663 QRhi::updateSwapChainProxyData(QSGRhiSupport::instance()->rhiBackend(), window);
1664
1665 qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
1666 w->thread->mutex.lock();
1667 m_lockedForSync = true;
1668 w->thread->postEvent(new WMSyncEvent(window, inExpose, w->forceRenderPass, scProxyData));
1669 w->forceRenderPass = false;
1670
1671 qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
1672 if (profileFrames)
1673 waitTime = timer.nsecsElapsed();
1674 Q_TRACE(QSG_wait_exit);
1675 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1676 QQuickProfiler::SceneGraphPolishAndSyncWait);
1677 Q_TRACE(QSG_sync_entry);
1678
1679 w->thread->waitCondition.wait(&w->thread->mutex);
1680 m_lockedForSync = false;
1681 w->thread->mutex.unlock();
1682 qCDebug(QSG_LOG_RENDERLOOP, "- unlock after sync");
1683
1684 if (profileFrames)
1685 syncTime = timer.nsecsElapsed();
1686 Q_TRACE(QSG_sync_exit);
1687 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1688 QQuickProfiler::SceneGraphPolishAndSyncSync);
1689 Q_TRACE(QSG_animations_entry);
1690
1691 // Now is the time to advance the regular animations (as we are throttled
1692 // to vsync due to the wait above), but this is only relevant when there is
1693 // one single window. With multiple windows m_animation_timer is active,
1694 // and advance() happens instead in response to a good old timer event, not
1695 // here. (the above applies only when the QSGAnimationDriver reports
1696 // isVSyncDependent() == true, if not then we always use the driver and
1697 // just advance here)
1698 if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
1699 auto advanceAnimations = [this, window=QPointer(window)] {
1700 qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
1701 m_animation_driver->advance();
1702 qCDebug(QSG_LOG_RENDERLOOP, "- animations done..");
1703
1704 // We need to trigger another update round to keep all animations
1705 // running correctly. For animations that lead to a visual change (a
1706 // property change in some item leading to dirtying the item and so
1707 // ending up in maybeUpdate()) this would not be needed, but other
1708 // animations would then stop functioning since there is nothing
1709 // advancing the animation system if we do not call postUpdateRequest()
1710 // here and nothing else leads to it either. This has an unfortunate
1711 // side effect in multi window cases: one can end up in a situation
1712 // where a non-animating window gets updates continuously because there
1713 // is an animation running in some other window that is non-exposed or
1714 // even closed already (if it was exposed we would not hit this branch,
1715 // however). Sadly, there is nothing that can be done about it.
1716 if (window)
1717 window->requestUpdate();
1718
1719 emit timeToIncubate();
1720 };
1721
1722#if defined(Q_OS_APPLE)
1723 if (inExpose) {
1724 // If we are handling an expose event the system is expecting us to
1725 // produce a frame that it can present on screen to the user. Advancing
1726 // animations at this point might result in changing properties of the
1727 // window in a way that invalidates the current frame, resulting in the
1728 // discarding of the current frame before the user ever sees it. To give
1729 // the system a chance to present the current frame we defer the advance
1730 // of the animations until the start of the next event loop pass, which
1731 // should still give plenty of time to compute the new render state before
1732 // the next expose event or update request.
1733 QMetaObject::invokeMethod(this, advanceAnimations, Qt::QueuedConnection);
1734 } else
1735#endif // Q_OS_APPLE
1736 {
1737 // For regular update requests we assume we can advance here synchronously
1738 advanceAnimations();
1739 }
1740 } else if (w->updateDuringSync) {
1741 postUpdateRequest(w);
1742 }
1743
1744 if (profileFrames) {
1745 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] Frame prepared, polish=%d ms, lock=%d ms, blockedForSync=%d ms, animations=%d ms",
1746 window,
1747 int(polishTime / 1000000),
1748 int((waitTime - polishTime) / 1000000),
1749 int((syncTime - waitTime) / 1000000),
1750 int((timer.nsecsElapsed() - syncTime) / 1000000));
1751 }
1752
1753 Q_TRACE(QSG_animations_exit);
1754 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1755 QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1756}
1757
1759{
1760 switch ((int) e->type()) {
1761
1762 case QEvent::Timer: {
1763 Q_ASSERT(sg->isVSyncDependent(m_animation_driver));
1764 QTimerEvent *te = static_cast<QTimerEvent *>(e);
1765 if (te->timerId() == m_animation_timer) {
1766 qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-render thread timer");
1767 m_animation_driver->advance();
1768 emit timeToIncubate();
1769 return true;
1770 }
1771 break;
1772 }
1773
1774 default:
1775 break;
1776 }
1777
1778 return QObject::event(e);
1779}
1780
1781
1782
1783/*
1784 Locks down GUI and performs a grab the scene graph, then returns the result.
1785
1786 Since the QML scene could have changed since the last time it was rendered,
1787 we need to polish and sync the scene graph. This might seem superfluous, but
1788 - QML changes could have triggered deleteLater() which could have removed
1789 textures or other objects from the scene graph, causing render to crash.
1790 - Autotests rely on grab(), setProperty(), grab(), compare behavior.
1791 */
1792
1793QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
1794{
1795 qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
1796
1797 Window *w = windowFor(window);
1798 Q_ASSERT(w);
1799
1800 if (!w->thread->isRunning())
1801 return QImage();
1802
1803 if (!window->handle())
1804 window->create();
1805
1806 qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
1807 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1808 m_inPolish = true;
1809 d->polishItems();
1810 m_inPolish = false;
1811
1812 QImage result;
1813 w->thread->mutex.lock();
1814 m_lockedForSync = true;
1815 qCDebug(QSG_LOG_RENDERLOOP, "- posting grab event");
1816 w->thread->postEvent(new WMGrabEvent(window, &result));
1817 w->thread->waitCondition.wait(&w->thread->mutex);
1818 m_lockedForSync = false;
1819 w->thread->mutex.unlock();
1820
1821 qCDebug(QSG_LOG_RENDERLOOP, "- grab complete");
1822
1823 return result;
1824}
1825
1826/*
1827 * Posts a new job event to the render thread.
1828 * Returns true if posting succeeded.
1829 */
1830void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
1831{
1832 Window *w = windowFor(window);
1833 if (w && w->thread && w->thread->window)
1834 w->thread->postEvent(new WMJobEvent(window, job));
1835 else
1836 delete job;
1837}
1838
1839QT_END_NAMESPACE
1840
1841#include "qsgthreadedrenderloop.moc"
1842#include "moc_qsgthreadedrenderloop_p.cpp"
\inmodule QtGui
Definition qimage.h:37
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:1590