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

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