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