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