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