Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgrenderloop.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8#include <private/qquickanimatorcontroller_p.h>
9
10#include <QtCore/QCoreApplication>
11#include <QtCore/QElapsedTimer>
12#include <QtCore/QLibraryInfo>
13#include <QtCore/private/qabstractanimation_p.h>
14
15#include <QtGui/QOffscreenSurface>
16#include <QtGui/private/qguiapplication_p.h>
17#include <qpa/qplatformintegration.h>
18#include <QPlatformSurfaceEvent>
19
20#include <QtQml/private/qqmlglobal_p.h>
21
22#include <QtQuick/QQuickWindow>
23#include <QtQuick/private/qquickwindow_p.h>
24#include <QtQuick/private/qquickitem_p.h>
25#include <QtQuick/private/qsgcontext_p.h>
26#include <QtQuick/private/qsgrenderer_p.h>
27#include <private/qquickprofiler_p.h>
28#include <qtquick_tracepoints_p.h>
29
30#include <private/qsgrhishadereffectnode_p.h>
31
32#include <private/qsgdefaultrendercontext_p.h>
33
34#ifdef Q_OS_WIN
35#include <QtCore/qt_windows.h>
36#endif
37
39
40extern bool qsg_useConsistentTiming();
41
42#define ENABLE_DEFAULT_BACKEND
43
44Q_TRACE_POINT(qtquick, QSG_renderWindow_entry)
45Q_TRACE_POINT(qtquick, QSG_renderWindow_exit)
46Q_TRACE_POINT(qtquick, QSG_polishItems_entry)
47Q_TRACE_POINT(qtquick, QSG_polishItems_exit)
48Q_TRACE_POINT(qtquick, QSG_sync_entry)
49Q_TRACE_POINT(qtquick, QSG_sync_exit)
50Q_TRACE_POINT(qtquick, QSG_render_entry)
51Q_TRACE_POINT(qtquick, QSG_render_exit)
52Q_TRACE_POINT(qtquick, QSG_swap_entry)
53Q_TRACE_POINT(qtquick, QSG_swap_exit)
54
55
56/*
57 - Uses one QRhi per window. (and so each window has its own QOpenGLContext or ID3D11Device(Context) etc.)
58 - Animations are advanced using the standard timer (no custom animation
59 driver is installed), so QML animations run as expected even when vsync
60 throttling is broken.
61 */
62
63DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP);
64DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk
65
66QSGRenderLoop *QSGRenderLoop::s_instance = nullptr;
67
68QSGRenderLoop::~QSGRenderLoop()
69{
70}
71
72void QSGRenderLoop::cleanup()
73{
74 if (!s_instance)
75 return;
76 const auto windows = s_instance->windows();
77 for (QQuickWindow *w : windows) {
78 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(w);
79 if (wd->windowManager == s_instance) {
80 s_instance->windowDestroyed(w);
81 wd->windowManager = nullptr;
82 }
83 }
84 delete s_instance;
85 s_instance = nullptr;
86}
87
88QSurface::SurfaceType QSGRenderLoop::windowSurfaceType() const
89{
91 return QSGRhiSupport::instance()->windowSurfaceType();
92#else
93 return QSurface::RasterSurface;
94#endif
95}
96
97void QSGRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
98{
99 Q_ASSERT(job);
101 Q_ASSERT(window);
102 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
103 if (cd->rhi)
104 cd->rhi->makeThreadLocalNativeContextCurrent();
105 job->run();
106#else
107 Q_UNUSED(window);
108 job->run();
109#endif
110 delete job;
111}
112
115{
117public:
120
121 void show(QQuickWindow *window) override;
122 void hide(QQuickWindow *window) override;
123
124 void windowDestroyed(QQuickWindow *window) override;
125
126 void renderWindow(QQuickWindow *window);
127 void exposureChanged(QQuickWindow *window) override;
128 QImage grab(QQuickWindow *window) override;
129
130 void maybeUpdate(QQuickWindow *window) override;
131 void update(QQuickWindow *window) override { maybeUpdate(window); } // identical for this implementation.
132 void handleUpdateRequest(QQuickWindow *) override;
133
134 void releaseResources(QQuickWindow *) override;
135
136 QAnimationDriver *animationDriver() const override { return nullptr; }
137
138 QSGContext *sceneGraphContext() const override;
139 QSGRenderContext *createRenderContext(QSGContext *) const override;
140
141 void releaseSwapchain(QQuickWindow *window);
144
145 bool eventFilter(QObject *watched, QEvent *event) override;
146
147 struct WindowData {
149 : updatePending(false),
150 rhiDeviceLost(false),
151 rhiDoomed(false)
152 { }
153 QRhi *rhi = nullptr;
154 bool ownRhi = true;
155 QSGRenderContext *rc = nullptr;
157 int sampleCount = 1;
160 bool rhiDoomed : 1;
161 };
162
163 bool ensureRhi(QQuickWindow *window, WindowData &data);
164
166
167 QOffscreenSurface *offscreenSurface = nullptr;
168 QSGContext *sg;
170
171 bool m_inPolish = false;
172
174};
175#endif
176
177QSGRenderLoop *QSGRenderLoop::instance()
178{
179 if (!s_instance) {
180
181 QSGRhiSupport::checkEnvQSgInfo();
182
183 s_instance = QSGContext::createWindowManager();
185 if (!s_instance) {
186 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
187
188 QSGRenderLoopType loopType;
189 if (rhiSupport->rhiBackend() != QRhi::OpenGLES2) {
190 loopType = ThreadedRenderLoop;
191 } else {
192 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
193 loopType = ThreadedRenderLoop;
194 else
195 loopType = BasicRenderLoop;
196 }
197
198 switch (rhiSupport->rhiBackend()) {
199 case QRhi::Null:
200 loopType = BasicRenderLoop;
201 break;
202
203 case QRhi::D3D11:
204 // The threaded loop's model may not be suitable for DXGI
205 // due to the possibility of having the main thread (with
206 // the Windows message pump) blocked while issuing a
207 // Present on the render thread. However, according to the
208 // docs this can be a problem for fullscreen swapchains
209 // only. So leave threaded enabled by default for now and
210 // revisit later if there are problems.
211 break;
212
213 default:
214 break;
215 }
216
217 // The environment variables can always override. This is good
218 // because in some situations it makes perfect sense to try out a
219 // render loop that is otherwise disabled by default.
220
221 if (qmlNoThreadedRenderer())
222 loopType = BasicRenderLoop;
223 else if (qmlForceThreadedRenderer())
224 loopType = ThreadedRenderLoop;
225
226 if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDER_LOOP"))) {
227 const QByteArray loopName = qgetenv("QSG_RENDER_LOOP");
228 if (loopName == "windows") {
229 qWarning("The 'windows' render loop is no longer supported. Using 'basic' instead.");
230 loopType = BasicRenderLoop;
231 } else if (loopName == "basic") {
232 loopType = BasicRenderLoop;
233 } else if (loopName == "threaded") {
234 loopType = ThreadedRenderLoop;
235 }
236 }
237
238 switch (loopType) {
239#if QT_CONFIG(thread)
240 case ThreadedRenderLoop:
241 qCDebug(QSG_LOG_INFO, "threaded render loop");
242 s_instance = new QSGThreadedRenderLoop();
243 break;
244#endif
245 default:
246 qCDebug(QSG_LOG_INFO, "basic render loop");
247 s_instance = new QSGGuiThreadRenderLoop();
248 break;
249 }
250 }
251#endif
252 qAddPostRoutine(QSGRenderLoop::cleanup);
253 }
254
255 return s_instance;
256}
257
258void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
259{
260 Q_ASSERT(!s_instance);
261 s_instance = instance;
262}
263
264void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
265{
266 // Must always be called on the gui thread.
267
268 // Guard for recursion; relevant due to the MessageBox() on Windows.
269 static QSet<QQuickWindow *> recurseGuard;
270 if (recurseGuard.contains(window))
271 return;
272
273 recurseGuard.insert(window);
274
275 QString translatedMessage;
276 QString untranslatedMessage;
277 QQuickWindowPrivate::rhiCreationFailureMessage(QSGRhiSupport::instance()->rhiBackendName(),
278 &translatedMessage,
279 &untranslatedMessage);
280 // If there is a slot connected to the error signal, emit it and leave it to
281 // the application to do something with the message. If nothing is connected,
282 // show a message on our own and terminate.
283 const bool signalEmitted =
284 QQuickWindowPrivate::get(window)->emitError(QQuickWindow::ContextNotAvailable,
285 translatedMessage);
286#if defined(Q_OS_WIN)
287 if (!signalEmitted && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) {
288 MessageBox(0, (LPCTSTR) translatedMessage.utf16(),
289 (LPCTSTR)(QCoreApplication::applicationName().utf16()),
290 MB_OK | MB_ICONERROR);
291 }
292#endif // Q_OS_WIN
293 if (!signalEmitted)
294 qFatal("%s", qPrintable(untranslatedMessage));
295
296 recurseGuard.remove(window);
297}
298
300QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
301{
303 QUnifiedTimer::instance(true)->setConsistentTiming(true);
304 qCDebug(QSG_LOG_INFO, "using fixed animation steps");
305 }
306
307 sg = QSGContext::createDefaultContext();
308}
309
311{
312 qDeleteAll(pendingRenderContexts);
313 delete sg;
314}
315
316void QSGGuiThreadRenderLoop::show(QQuickWindow *window)
317{
318 if (!m_windows.contains(window))
319 m_windows.insert(window, {});
320
321 m_windows[window].timeBetweenRenders.start();
322 maybeUpdate(window);
323}
324
325void QSGGuiThreadRenderLoop::hide(QQuickWindow *window)
326{
327 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
328 cd->fireAboutToStop();
329 auto it = m_windows.find(window);
330 if (it != m_windows.end())
331 it->updatePending = false;
332}
333
334void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
335{
336 hide(window);
337
338 WindowData data = m_windows.value(window, {});
339 m_windows.remove(window);
340
341 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
342
343 if (data.rhi) {
344 // Direct OpenGL calls in user code need a current context, like
345 // when rendering; ensure this (no-op when not running on GL).
346 // Also works when there is no handle() anymore.
347 data.rhi->makeThreadLocalNativeContextCurrent();
348 }
349
350 if (d->swapchain) {
351 if (window->handle()) {
352 // We get here when exiting via QCoreApplication::quit() instead of
353 // through QWindow::close().
354 releaseSwapchain(window);
355 } else {
356 qWarning("QSGGuiThreadRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
357 window, d->swapchain);
358 }
359 }
360
361 d->cleanupNodesOnShutdown();
362
363#if QT_CONFIG(quick_shadereffect)
364 QSGRhiShaderEffectNode::resetMaterialTypeCache(window);
365#endif
366
367 if (data.rc) {
368 data.rc->invalidate();
369 delete data.rc;
370 }
371
372 if (data.ownRhi)
373 QSGRhiSupport::instance()->destroyRhi(data.rhi, d->graphicsConfig);
374
375 d->rhi = nullptr;
376
377 d->animationController.reset();
378
379 if (m_windows.isEmpty()) {
380 delete offscreenSurface;
381 offscreenSurface = nullptr;
382 }
383}
384
386{
387 for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
388 if (it->rhi) {
389 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
390 wd->cleanupNodesOnShutdown();
391 if (it->rc)
392 it->rc->invalidate();
393 releaseSwapchain(it.key());
394 if (it->ownRhi)
395 QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
396 it->rhi = nullptr;
397 wd->rhi = nullptr;
398 }
399 }
400}
401
403{
404 qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs");
405
406 for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
407 if (!it->rhi || !it->rhi->isDeviceLost())
408 continue;
409
410 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
411 wd->cleanupNodesOnShutdown();
412
413 if (it->rc)
414 it->rc->invalidate();
415
416 releaseSwapchain(it.key());
417 it->rhiDeviceLost = true;
418
419 if (it->ownRhi)
420 QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
421 it->rhi = nullptr;
422 wd->rhi = nullptr;
423 }
424}
425
426void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window)
427{
428 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
429 delete wd->rpDescForSwapchain;
430 wd->rpDescForSwapchain = nullptr;
431 delete wd->swapchain;
432 wd->swapchain = nullptr;
433 delete wd->depthStencilForSwapchain;
434 wd->depthStencilForSwapchain = nullptr;
435 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
436}
437
438bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
439{
440 switch (event->type()) {
441 case QEvent::PlatformSurface:
442 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
443 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
444 QQuickWindow *w = qobject_cast<QQuickWindow *>(watched);
445 if (w)
447 // keep this filter on the window - needed for uncommon but valid
448 // sequences of calls like window->destroy(); window->show();
449 }
450 break;
451 default:
452 break;
453 }
454 return QObject::eventFilter(watched, event);
455}
456
457bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
458{
459 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
460 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
461 bool ok = data.rhi != nullptr;
462
463 if (!data.rhi) {
464 // This block below handles both the initial QRhi initialization and
465 // also the subsequent reinitialization attempts after a device lost
466 // (reset) situation.
467
468 if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
469 return false;
470
471 if (!offscreenSurface)
472 offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
473
474 const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
475 QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
476 data.rhi = rhiResult.rhi;
477 data.ownRhi = rhiResult.own;
478
479 if (data.rhi) {
480 data.rhiDeviceLost = false;
481
482 ok = true;
483 // We need to guarantee that sceneGraphInitialized is
484 // emitted with a context current, if running with OpenGL.
485 data.rhi->makeThreadLocalNativeContextCurrent();
486
487 // The sample count cannot vary between windows as we use the same
488 // rendercontext for all of them. Decide it here and now.
489 data.sampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, data.rhi);
490
491 cd->rhi = data.rhi; // set this early in case something hooked up to rc initialized() accesses it
492
493 QSGDefaultRenderContext::InitParams rcParams;
494 rcParams.rhi = data.rhi;
495 rcParams.sampleCount = data.sampleCount;
496 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
497 rcParams.maybeSurface = window;
498 cd->context->initialize(&rcParams);
499 } else {
500 if (!data.rhiDeviceLost) {
501 data.rhiDoomed = true;
502 handleContextCreationFailure(window);
503 }
504 // otherwise no error, just return false so that we will retry on a subsequent rendering attempt
505 }
506 }
507
508 if (data.rhi && !cd->swapchain) {
509 // if it's not the first window then the rhi is not yet stored to
510 // QQuickWindowPrivate, do it now
511 cd->rhi = data.rhi;
512
513 // Unlike the threaded render loop, we use the same rhi for all windows
514 // and so createRhi() is called only once. Certain initialization may
515 // need to be done on a per window basis still, so make sure it is done.
516 rhiSupport->prepareWindowForRhi(window);
517
518 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
519 const QSurfaceFormat requestedFormat = window->requestedFormat();
520
521 // QQ is always premul alpha. Decide based on alphaBufferSize in
522 // requestedFormat(). (the platform plugin can override format() but
523 // what matters here is what the application wanted, hence using the
524 // requested one)
525 const bool alpha = requestedFormat.alphaBufferSize() > 0;
526 if (alpha)
527 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
528
529 // Request NoVSync if swap interval was set to 0 (either by the app or
530 // by QSG_NO_VSYNC). What this means in practice is another question,
531 // but at least we tried.
532 if (requestedFormat.swapInterval() == 0) {
533 qCDebug(QSG_LOG_INFO, "Swap interval is 0, attempting to disable vsync when presenting.");
534 flags |= QRhiSwapChain::NoVSync;
535 }
536
537 cd->swapchain = data.rhi->newSwapChain();
538 static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
539 if (depthBufferEnabled) {
540 cd->depthStencilForSwapchain = data.rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
541 QSize(),
542 data.sampleCount,
543 QRhiRenderBuffer::UsedWithSwapChainOnly);
544 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
545 }
546 cd->swapchain->setWindow(window);
547 rhiSupport->applySwapChainFormat(cd->swapchain, window);
548 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
549 data.sampleCount, alpha ? "yes" : "no");
550 cd->swapchain->setSampleCount(data.sampleCount);
551 cd->swapchain->setFlags(flags);
552 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
553 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
554
555 window->installEventFilter(this);
556 }
557
558 if (!data.rc) {
559 QSGRenderContext *rc = cd->context;
560 pendingRenderContexts.remove(rc);
561 data.rc = rc;
562 if (!data.rc)
563 qWarning("No QSGRenderContext for window %p, this should not happen", window);
564 }
565
566 return ok;
567}
568
569void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
570{
571 auto winDataIt = m_windows.find(window);
572 if (winDataIt == m_windows.end())
573 return;
574
575 WindowData &data(*winDataIt);
576 bool alsoSwap = data.updatePending;
577 data.updatePending = false;
578
579 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
580 if (!cd->isRenderable())
581 return;
582
583 if (!cd->updatesEnabled)
584 return;
585
586 if (!ensureRhi(window, data))
587 return;
588
589 cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
590 // Event delivery/processing triggered the window to be deleted or stop rendering.
591 if (!m_windows.contains(window))
592 return;
593
594 QSize effectiveOutputSize; // always prefer what the surface tells us, not the QWindow
595 if (cd->swapchain) {
596 effectiveOutputSize = cd->swapchain->surfacePixelSize();
597 // An update request could still be delivered right before we get an
598 // unexpose. With Vulkan on Windows for example attempting to render
599 // leads to failures at this stage since the surface size is already 0.
600 if (effectiveOutputSize.isEmpty())
601 return;
602 }
603
604 Q_TRACE_SCOPE(QSG_renderWindow);
605 QElapsedTimer renderTimer;
606 qint64 renderTime = 0, syncTime = 0, polishTime = 0;
607 const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
608 if (profileFrames)
609 renderTimer.start();
610 Q_TRACE(QSG_polishItems_entry);
611 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
612
613 m_inPolish = true;
614 cd->polishItems();
615 m_inPolish = false;
616
617 if (profileFrames)
618 polishTime = renderTimer.nsecsElapsed();
619
620 Q_TRACE(QSG_polishItems_exit);
621 Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
622 QQuickProfiler::SceneGraphRenderLoopFrame,
623 QQuickProfiler::SceneGraphPolishPolish);
624 Q_TRACE(QSG_sync_entry);
625
626 emit window->afterAnimating();
627
628 // Begin the frame before syncing -> sync is where we may invoke
629 // updatePaintNode() on the items and they may want to do resource updates.
630 // Also relevant for applications that connect to the before/afterSynchronizing
631 // signals and want to do graphics stuff already there.
632 if (cd->swapchain) {
633 Q_ASSERT(!effectiveOutputSize.isEmpty());
634 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
635 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
636 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
637 if (cd->swapchainJustBecameRenderable)
638 qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
639
640 cd->hasActiveSwapchain = cd->swapchain->createOrResize();
641 if (!cd->hasActiveSwapchain) {
642 if (data.rhi->isDeviceLost()) {
644 return;
645 } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
646 qWarning("Failed to create swapchain."
647 " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
650 return;
651 }
652 }
653
654 cd->swapchainJustBecameRenderable = false;
655 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
656
657 if (cd->hasActiveSwapchain) {
658 // surface size atomicity: now that buildOrResize() succeeded,
659 // query the size that was used in there by the swapchain, and
660 // that is the size we will use while preparing the next frame.
661 effectiveOutputSize = cd->swapchain->currentPixelSize();
662 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize;
663 } else {
664 qWarning("Failed to build or resize swapchain");
665 }
666 }
667
668 emit window->beforeFrameBegin();
669
670 Q_ASSERT(data.rhi == cd->rhi);
671 QRhi::FrameOpResult frameResult = data.rhi->beginFrame(cd->swapchain);
672 if (frameResult != QRhi::FrameOpSuccess) {
673 if (frameResult == QRhi::FrameOpDeviceLost)
675 else if (frameResult == QRhi::FrameOpError)
676 qWarning("Failed to start frame");
677 // out of date is not worth warning about - it may happen even during resizing on some platforms
678 emit window->afterFrameEnd();
679 return;
680 }
681 }
682
683 // Enable external OpenGL rendering connected to one of the
684 // QQuickWindow signals (beforeSynchronizing, beforeRendering,
685 // etc.) to function like it did on the direct OpenGL path,
686 // i.e. ensure there is a context current, just in case.
687 data.rhi->makeThreadLocalNativeContextCurrent();
688
689 cd->syncSceneGraph();
690 data.rc->endSync();
691
692 if (profileFrames)
693 syncTime = renderTimer.nsecsElapsed();
694
695 Q_TRACE(QSG_sync_exit);
696 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
697 QQuickProfiler::SceneGraphRenderLoopSync);
698 Q_TRACE(QSG_render_entry);
699
700 cd->renderSceneGraph();
701
702 if (profileFrames)
703 renderTime = renderTimer.nsecsElapsed();
704 Q_TRACE(QSG_render_exit);
705 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
706 QQuickProfiler::SceneGraphRenderLoopRender);
707 Q_TRACE(QSG_swap_entry);
708
709 const bool needsPresent = alsoSwap && window->isVisible();
710 double lastCompletedGpuTime = 0;
711 if (cd->swapchain) {
712 QRhi::EndFrameFlags flags;
713 if (!needsPresent)
714 flags |= QRhi::SkipPresent;
715 QRhi::FrameOpResult frameResult = data.rhi->endFrame(cd->swapchain, flags);
716 if (frameResult != QRhi::FrameOpSuccess) {
717 if (frameResult == QRhi::FrameOpDeviceLost)
719 else if (frameResult == QRhi::FrameOpError)
720 qWarning("Failed to end frame");
721 } else {
722 lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
723 }
724 }
725 if (needsPresent)
726 cd->fireFrameSwapped();
727
728 emit window->afterFrameEnd();
729
730 qint64 swapTime = 0;
731 if (profileFrames)
732 swapTime = renderTimer.nsecsElapsed();
733
734 Q_TRACE(QSG_swap_exit);
735 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
736 QQuickProfiler::SceneGraphRenderLoopSwap);
737
738 if (profileFrames) {
739 qCDebug(QSG_LOG_TIME_RENDERLOOP,
740 "[window %p][gui thread] syncAndRender: frame rendered in %dms, polish=%d, sync=%d, render=%d, swap=%d, perWindowFrameDelta=%d",
741 window,
742 int(swapTime / 1000000),
743 int(polishTime / 1000000),
744 int((syncTime - polishTime) / 1000000),
745 int((renderTime - syncTime) / 1000000),
746 int((swapTime - renderTime) / 1000000),
747 int(data.timeBetweenRenders.restart()));
748 if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
749 qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] syncAndRender: last retrieved GPU frame time was %.4f ms",
750 window,
751 lastCompletedGpuTime * 1000.0);
752 }
753 }
754
755 // Might have been set during syncSceneGraph()
756 if (data.updatePending)
757 maybeUpdate(window);
758}
759
760void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
761{
762 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
763
764 // This is tricker than used to be. We want to detect having an empty
765 // surface size (which may be the case even when window->size() is
766 // non-empty, on some platforms with some graphics APIs!) as well as the
767 // case when the window just became "newly exposed" (e.g. after a
768 // minimize-restore on Windows, or when switching between fully obscured -
769 // not fully obscured on macOS)
770
771 if (!window->isExposed() || (wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()))
772 wd->hasRenderableSwapchain = false;
773
774 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
775 && !wd->swapchain->surfacePixelSize().isEmpty())
776 {
777 wd->hasRenderableSwapchain = true;
778 wd->swapchainJustBecameRenderable = true;
779 }
780
781 auto winDataIt = m_windows.find(window);
782 if (winDataIt != m_windows.end()) {
783 if (window->isExposed() && (!winDataIt->rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) {
784 winDataIt->updatePending = true;
785 renderWindow(window);
786 }
787 }
788}
789
790QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
791{
792 auto winDataIt = m_windows.find(window);
793 if (winDataIt == m_windows.end())
794 return QImage();
795
796 if (!ensureRhi(window, *winDataIt))
797 return QImage();
798
799 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
800 m_inPolish = true;
801 cd->polishItems();
802 m_inPolish = false;
803
804 // The assumption is that the swapchain is usable since on expose we do a
805 // renderWindow() so one cannot get to grab() without having done at least
806 // one on-screen frame.
807 cd->rhi->beginFrame(cd->swapchain);
808 cd->rhi->makeThreadLocalNativeContextCurrent(); // for custom GL rendering before/during/after sync
809 cd->syncSceneGraph();
810 cd->renderSceneGraph();
811 QImage image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(cd->rhi, cd->swapchain->currentFrameCommandBuffer());
812 cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
813
814 image.setDevicePixelRatio(window->effectiveDevicePixelRatio());
815 return image;
816}
817
818void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
819{
820 auto winDataIt = m_windows.find(window);
821 if (winDataIt == m_windows.end())
822 return;
823
824 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
825 if (!cd->isRenderable())
826 return;
827
828 winDataIt->updatePending = true;
829
830 // An updatePolish() implementation may call update() to get the QQuickItem
831 // dirtied. That's fine but it also leads to calling this function.
832 // Requesting another update is a waste then since the updatePolish() call
833 // will be followed up with a round of sync and render.
834 if (m_inPolish)
835 return;
836
837 window->requestUpdate();
838}
839
841{
842 return sg;
843}
844
845QSGRenderContext *QSGGuiThreadRenderLoop::createRenderContext(QSGContext *sg) const
846{
847 QSGRenderContext *rc = sg->createRenderContext();
848 pendingRenderContexts.insert(rc);
849 return rc;
850}
851
853{
854 // No full invalidation of the rendercontext, just clear some caches.
855 QQuickWindowPrivate *d = QQuickWindowPrivate::get(w);
856 emit d->context->releaseCachedResourcesRequested();
857 if (d->renderer)
858 d->renderer->releaseCachedResources();
859#if QT_CONFIG(quick_shadereffect)
860 QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(w);
861#endif
862}
863
865{
866 renderWindow(window);
867}
868
869#endif // ENABLE_DEFAULT_BACKEND
870
871QT_END_NAMESPACE
872
873#include "qsgrenderloop.moc"
874#include "moc_qsgrenderloop_p.cpp"
QAnimationDriver * animationDriver() const override
void update(QQuickWindow *window) override
void renderWindow(QQuickWindow *window)
void releaseSwapchain(QQuickWindow *window)
void show(QQuickWindow *window) override
bool eventFilter(QObject *watched, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
QSGRenderContext * createRenderContext(QSGContext *) const override
void windowDestroyed(QQuickWindow *window) override
bool ensureRhi(QQuickWindow *window, WindowData &data)
QOffscreenSurface * offscreenSurface
void releaseResources(QQuickWindow *) override
void exposureChanged(QQuickWindow *window) override
QSGContext * sceneGraphContext() const override
QHash< QQuickWindow *, WindowData > m_windows
QSet< QSGRenderContext * > pendingRenderContexts
void handleUpdateRequest(QQuickWindow *) override
void hide(QQuickWindow *window) override
QImage grab(QQuickWindow *window) override
void maybeUpdate(QQuickWindow *window) override
Combined button and popup list for selecting options.
bool qsg_useConsistentTiming()
#define ENABLE_DEFAULT_BACKEND
DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE)