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