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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/quick/scenegraph/qsgrenderloop.cpp