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