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