Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qsgrenderloop_p.h"
++ #include "qsgthreadedrenderloop_p.h"
++ #include "qsgrhisupport_p.h"
++ #include <private/qquickanimatorcontroller_p.h>
++
++ #include <QtCore/QCoreApplication>
++ #include <QtCore/QElapsedTimer>
++ #include <QtCore/QLibraryInfo>
++ #include <QtCore/private/qabstractanimation_p.h>
++
++ #include <QtGui/QOffscreenSurface>
++ #include <QtGui/private/qguiapplication_p.h>
++ #include <qpa/qplatformintegration.h>
++ #include <QPlatformSurfaceEvent>
++
++ #include <QtQml/private/qqmlglobal_p.h>
++
++ #include <QtQuick/QQuickWindow>
++ #include <QtQuick/private/qquickwindow_p.h>
++ #include <QtQuick/private/qquickitem_p.h>
++ #include <QtQuick/private/qsgcontext_p.h>
++ #include <QtQuick/private/qsgrenderer_p.h>
++ #include <private/qquickprofiler_p.h>
++ #include <qtquick_tracepoints_p.h>
++
++ #include <private/qsgrhishadereffectnode_p.h>
++
++ #include <private/qsgdefaultrendercontext_p.h>
++
++ #ifdef Q_OS_WIN
++ #include <QtCore/qt_windows.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ extern bool qsg_useConsistentTiming();
++
++ #define ENABLE_DEFAULT_BACKEND
++
++ Q_TRACE_POINT(qtquick, QSG_renderWindow_entry)
++ Q_TRACE_POINT(qtquick, QSG_renderWindow_exit)
++ Q_TRACE_POINT(qtquick, QSG_polishItems_entry)
++ Q_TRACE_POINT(qtquick, QSG_polishItems_exit)
++ Q_TRACE_POINT(qtquick, QSG_sync_entry)
++ Q_TRACE_POINT(qtquick, QSG_sync_exit)
++ Q_TRACE_POINT(qtquick, QSG_render_entry)
++ Q_TRACE_POINT(qtquick, QSG_render_exit)
++ Q_TRACE_POINT(qtquick, QSG_swap_entry)
++ Q_TRACE_POINT(qtquick, QSG_swap_exit)
++
++
++ /*
++ - Uses one QRhi per window. (and so each window has its own QOpenGLContext or ID3D11Device(Context) etc.)
++ - Animations are advanced using the standard timer (no custom animation
++ driver is installed), so QML animations run as expected even when vsync
++ throttling is broken.
++ */
++
++ DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP);
++ DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk
++
++ QSGRenderLoop *QSGRenderLoop::s_instance = nullptr;
++
++ QSGRenderLoop::~QSGRenderLoop()
++ {
++ }
++
++ void QSGRenderLoop::cleanup()
++ {
++ if (!s_instance)
++ return;
++ const auto windows = s_instance->windows();
++ for (QQuickWindow *w : windows) {
++ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(w);
++ if (wd->windowManager == s_instance) {
++ s_instance->windowDestroyed(w);
++ wd->windowManager = nullptr;
++ }
++ }
++ delete s_instance;
++ s_instance = nullptr;
++ }
++
++ QSurface::SurfaceType QSGRenderLoop::windowSurfaceType() const
++ {
++ #ifdef ENABLE_DEFAULT_BACKEND
++ return QSGRhiSupport::instance()->windowSurfaceType();
++ #else
++ return QSurface::RasterSurface;
++ #endif
++ }
++
++ void QSGRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
++ {
++ Q_ASSERT(job);
++ #ifdef ENABLE_DEFAULT_BACKEND
++ Q_ASSERT(window);
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ if (cd->rhi)
++ cd->rhi->makeThreadLocalNativeContextCurrent();
++ job->run();
++ #else
++ Q_UNUSED(window);
++ job->run();
++ #endif
++ delete job;
++ }
++
++ #ifdef ENABLE_DEFAULT_BACKEND
++ class QSGGuiThreadRenderLoop : public QSGRenderLoop
++ {
++ Q_OBJECT
++ public:
++ QSGGuiThreadRenderLoop();
++ ~QSGGuiThreadRenderLoop();
++
++ void show(QQuickWindow *window) override;
++ void hide(QQuickWindow *window) override;
++
++ void windowDestroyed(QQuickWindow *window) override;
++
++ void renderWindow(QQuickWindow *window);
++ void exposureChanged(QQuickWindow *window) override;
++ QImage grab(QQuickWindow *window) override;
++
++ void maybeUpdate(QQuickWindow *window) override;
++ void update(QQuickWindow *window) override { maybeUpdate(window); } // identical for this implementation.
++ void handleUpdateRequest(QQuickWindow *) override;
++
++ void releaseResources(QQuickWindow *) override;
++
++ QAnimationDriver *animationDriver() const override { return nullptr; }
++
++ QSGContext *sceneGraphContext() const override;
++ QSGRenderContext *createRenderContext(QSGContext *) const override;
++
++ void releaseSwapchain(QQuickWindow *window);
++ void handleDeviceLoss();
++ void teardownGraphics();
++
++ bool eventFilter(QObject *watched, QEvent *event) override;
++
++ struct WindowData {
++ WindowData()
++ : updatePending(false),
++ rhiDeviceLost(false),
++ rhiDoomed(false)
++ { }
++ QRhi *rhi = nullptr;
++ bool ownRhi = true;
++ QSGRenderContext *rc = nullptr;
++ QElapsedTimer timeBetweenRenders;
++ int sampleCount = 1;
++ bool updatePending : 1;
++ bool rhiDeviceLost : 1;
++ bool rhiDoomed : 1;
++ };
++
++ bool ensureRhi(QQuickWindow *window, WindowData &data);
++
++ QHash<QQuickWindow *, WindowData> m_windows;
++
++ QOffscreenSurface *offscreenSurface = nullptr;
++ QSGContext *sg;
++ mutable QSet<QSGRenderContext *> pendingRenderContexts;
++
++ bool m_inPolish = false;
++
++ bool swRastFallbackDueToSwapchainFailure = false;
++ };
++ #endif
++
++ QSGRenderLoop *QSGRenderLoop::instance()
++ {
++ if (!s_instance) {
++
++ QSGRhiSupport::checkEnvQSgInfo();
++
++ s_instance = QSGContext::createWindowManager();
++ #ifdef ENABLE_DEFAULT_BACKEND
++ if (!s_instance) {
++ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
++
++ QSGRenderLoopType loopType;
++ if (rhiSupport->rhiBackend() != QRhi::OpenGLES2) {
++ loopType = ThreadedRenderLoop;
++ } else {
++ if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
++ loopType = ThreadedRenderLoop;
++ else
++ loopType = BasicRenderLoop;
++ }
++
++ switch (rhiSupport->rhiBackend()) {
++ case QRhi::Null:
++ loopType = BasicRenderLoop;
++ break;
++
++ case QRhi::D3D11:
++ // The threaded loop's model may not be suitable for DXGI
++ // due to the possibility of having the main thread (with
++ // the Windows message pump) blocked while issuing a
++ // Present on the render thread. However, according to the
++ // docs this can be a problem for fullscreen swapchains
++ // only. So leave threaded enabled by default for now and
++ // revisit later if there are problems.
++ break;
++
++ default:
++ break;
++ }
++
++ // The environment variables can always override. This is good
++ // because in some situations it makes perfect sense to try out a
++ // render loop that is otherwise disabled by default.
++
++ if (qmlNoThreadedRenderer())
++ loopType = BasicRenderLoop;
++ else if (qmlForceThreadedRenderer())
++ loopType = ThreadedRenderLoop;
++
++ if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDER_LOOP"))) {
++ const QByteArray loopName = qgetenv("QSG_RENDER_LOOP");
++ if (loopName == "windows") {
++ qWarning("The 'windows' render loop is no longer supported. Using 'basic' instead.");
++ loopType = BasicRenderLoop;
++ } else if (loopName == "basic") {
++ loopType = BasicRenderLoop;
++ } else if (loopName == "threaded") {
++ loopType = ThreadedRenderLoop;
++ }
++ }
++
++ switch (loopType) {
++ #if QT_CONFIG(thread)
++ case ThreadedRenderLoop:
++ qCDebug(QSG_LOG_INFO, "threaded render loop");
++ s_instance = new QSGThreadedRenderLoop();
++ break;
++ #endif
++ default:
++ qCDebug(QSG_LOG_INFO, "basic render loop");
++ s_instance = new QSGGuiThreadRenderLoop();
++ break;
++ }
++ }
++ #endif
++ qAddPostRoutine(QSGRenderLoop::cleanup);
++ }
++
++ return s_instance;
++ }
++
++ void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
++ {
++ Q_ASSERT(!s_instance);
++ s_instance = instance;
++ }
++
++ void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
++ {
++ // Must always be called on the gui thread.
++
++ // Guard for recursion; relevant due to the MessageBox() on Windows.
++ static QSet<QQuickWindow *> recurseGuard;
++ if (recurseGuard.contains(window))
++ return;
++
++ recurseGuard.insert(window);
++
++ QString translatedMessage;
++ QString untranslatedMessage;
++ QQuickWindowPrivate::rhiCreationFailureMessage(QSGRhiSupport::instance()->rhiBackendName(),
++ &translatedMessage,
++ &untranslatedMessage);
++ // If there is a slot connected to the error signal, emit it and leave it to
++ // the application to do something with the message. If nothing is connected,
++ // show a message on our own and terminate.
++ const bool signalEmitted =
++ QQuickWindowPrivate::get(window)->emitError(QQuickWindow::ContextNotAvailable,
++ translatedMessage);
++ #if defined(Q_OS_WIN)
++ if (!signalEmitted && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) {
++ MessageBox(0, (LPCTSTR) translatedMessage.utf16(),
++ (LPCTSTR)(QCoreApplication::applicationName().utf16()),
++ MB_OK | MB_ICONERROR);
++ }
++ #endif // Q_OS_WIN
++ if (!signalEmitted)
++ qFatal("%s", qPrintable(untranslatedMessage));
++
++ recurseGuard.remove(window);
++ }
++
++ #ifdef ENABLE_DEFAULT_BACKEND
++ QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
++ {
++ if (qsg_useConsistentTiming()) {
++ QUnifiedTimer::instance(true)->setConsistentTiming(true);
++ qCDebug(QSG_LOG_INFO, "using fixed animation steps");
++ }
++
++ sg = QSGContext::createDefaultContext();
++ }
++
++ QSGGuiThreadRenderLoop::~QSGGuiThreadRenderLoop()
++ {
++ qDeleteAll(pendingRenderContexts);
++ delete sg;
++ }
++
++ void QSGGuiThreadRenderLoop::show(QQuickWindow *window)
++ {
++ if (!m_windows.contains(window))
++ m_windows.insert(window, {});
++
++ m_windows[window].timeBetweenRenders.start();
++ maybeUpdate(window);
++ }
++
++ void QSGGuiThreadRenderLoop::hide(QQuickWindow *window)
++ {
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ cd->fireAboutToStop();
++ auto it = m_windows.find(window);
++ if (it != m_windows.end())
++ it->updatePending = false;
++ }
++
++ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
++ {
++ hide(window);
++
++ WindowData data = m_windows.value(window, {});
++ m_windows.remove(window);
++
++ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
++
++ if (data.rhi) {
++ // Direct OpenGL calls in user code need a current context, like
++ // when rendering; ensure this (no-op when not running on GL).
++ // Also works when there is no handle() anymore.
++ data.rhi->makeThreadLocalNativeContextCurrent();
++ }
++
++ if (d->swapchain) {
++ if (window->handle()) {
++ // We get here when exiting via QCoreApplication::quit() instead of
++ // through QWindow::close().
++ releaseSwapchain(window);
++ } else {
++ qWarning("QSGGuiThreadRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
++ window, d->swapchain);
++ }
++ }
++
++ d->cleanupNodesOnShutdown();
++
++ #if QT_CONFIG(quick_shadereffect)
++ QSGRhiShaderEffectNode::resetMaterialTypeCache(window);
++ #endif
++
++ if (data.rc) {
++ data.rc->invalidate();
++ delete data.rc;
++ }
++
++ if (data.ownRhi)
++ QSGRhiSupport::instance()->destroyRhi(data.rhi, d->graphicsConfig);
++
++ d->rhi = nullptr;
++
++ d->animationController.reset();
++
++ if (m_windows.isEmpty()) {
++ delete offscreenSurface;
++ offscreenSurface = nullptr;
++ }
++ }
++
++ void QSGGuiThreadRenderLoop::teardownGraphics()
++ {
++ for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
++ if (it->rhi) {
++ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
++ wd->cleanupNodesOnShutdown();
++ if (it->rc)
++ it->rc->invalidate();
++ releaseSwapchain(it.key());
++ if (it->ownRhi)
++ QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
++ it->rhi = nullptr;
++ wd->rhi = nullptr;
++ }
++ }
++ }
++
++ void QSGGuiThreadRenderLoop::handleDeviceLoss()
++ {
++ qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs");
++
++ for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
++ if (!it->rhi || !it->rhi->isDeviceLost())
++ continue;
++
++ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key());
++ wd->cleanupNodesOnShutdown();
++
++ if (it->rc)
++ it->rc->invalidate();
++
++ releaseSwapchain(it.key());
++ it->rhiDeviceLost = true;
++
++ if (it->ownRhi)
++ QSGRhiSupport::instance()->destroyRhi(it->rhi, {});
++ it->rhi = nullptr;
++ wd->rhi = nullptr;
++ }
++ }
++
++ void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window)
++ {
++ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
++ delete wd->rpDescForSwapchain;
++ wd->rpDescForSwapchain = nullptr;
++ delete wd->swapchain;
++ wd->swapchain = nullptr;
++ delete wd->depthStencilForSwapchain;
++ wd->depthStencilForSwapchain = nullptr;
++ wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
++ }
++
++ bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
++ {
++ switch (event->type()) {
++ case QEvent::PlatformSurface:
++ // this is the proper time to tear down the swapchain (while the native window and surface are still around)
++ if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
++ QQuickWindow *w = qobject_cast<QQuickWindow *>(watched);
++ if (w)
++ releaseSwapchain(w);
++ // keep this filter on the window - needed for uncommon but valid
++ // sequences of calls like window->destroy(); window->show();
++ }
++ break;
++ default:
++ break;
++ }
++ return QObject::eventFilter(watched, event);
++ }
++
++ bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data)
++ {
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
++ bool ok = data.rhi != nullptr;
++
++ if (!data.rhi) {
++ // This block below handles both the initial QRhi initialization and
++ // also the subsequent reinitialization attempts after a device lost
++ // (reset) situation.
++
++ if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
++ return false;
++
++ if (!offscreenSurface)
++ offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
++
++ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
++ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
++ data.rhi = rhiResult.rhi;
++ data.ownRhi = rhiResult.own;
++
++ if (data.rhi) {
++ data.rhiDeviceLost = false;
++
++ ok = true;
++ // We need to guarantee that sceneGraphInitialized is
++ // emitted with a context current, if running with OpenGL.
++ data.rhi->makeThreadLocalNativeContextCurrent();
++
++ // The sample count cannot vary between windows as we use the same
++ // rendercontext for all of them. Decide it here and now.
++ data.sampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, data.rhi);
++
++ cd->rhi = data.rhi; // set this early in case something hooked up to rc initialized() accesses it
++
++ QSGDefaultRenderContext::InitParams rcParams;
++ rcParams.rhi = data.rhi;
++ rcParams.sampleCount = data.sampleCount;
++ rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
++ rcParams.maybeSurface = window;
++ cd->context->initialize(&rcParams);
++ } else {
++ if (!data.rhiDeviceLost) {
++ data.rhiDoomed = true;
++ handleContextCreationFailure(window);
++ }
++ // otherwise no error, just return false so that we will retry on a subsequent rendering attempt
++ }
++ }
++
++ if (data.rhi && !cd->swapchain) {
++ // if it's not the first window then the rhi is not yet stored to
++ // QQuickWindowPrivate, do it now
++ cd->rhi = data.rhi;
++
++ // Unlike the threaded render loop, we use the same rhi for all windows
++ // and so createRhi() is called only once. Certain initialization may
++ // need to be done on a per window basis still, so make sure it is done.
++ rhiSupport->prepareWindowForRhi(window);
++
++ QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
++ const QSurfaceFormat requestedFormat = window->requestedFormat();
++
++ // QQ is always premul alpha. Decide based on alphaBufferSize in
++ // requestedFormat(). (the platform plugin can override format() but
++ // what matters here is what the application wanted, hence using the
++ // requested one)
++ const bool alpha = requestedFormat.alphaBufferSize() > 0;
++ if (alpha)
++ flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
++
++ // Request NoVSync if swap interval was set to 0 (either by the app or
++ // by QSG_NO_VSYNC). What this means in practice is another question,
++ // but at least we tried.
++ if (requestedFormat.swapInterval() == 0) {
++ qCDebug(QSG_LOG_INFO, "Swap interval is 0, attempting to disable vsync when presenting.");
++ flags |= QRhiSwapChain::NoVSync;
++ }
++
++ cd->swapchain = data.rhi->newSwapChain();
++ static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
++ if (depthBufferEnabled) {
++ cd->depthStencilForSwapchain = data.rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
++ QSize(),
++ data.sampleCount,
++ QRhiRenderBuffer::UsedWithSwapChainOnly
++ | QSGRhiSupport::depthStencilBufferFlags());
++ cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
++ }
++ cd->swapchain->setWindow(window);
++ rhiSupport->applySwapChainFormat(cd->swapchain, window);
++ qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
++ data.sampleCount, alpha ? "yes" : "no");
++ cd->swapchain->setSampleCount(data.sampleCount);
++ cd->swapchain->setFlags(flags);
++ cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
++ cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
++
++ window->installEventFilter(this);
++ }
++
++ if (!data.rc) {
++ QSGRenderContext *rc = cd->context;
++ pendingRenderContexts.remove(rc);
++ data.rc = rc;
++ if (!data.rc)
++ qWarning("No QSGRenderContext for window %p, this should not happen", window);
++ }
++
++ return ok;
++ }
++
++ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
++ {
++ auto winDataIt = m_windows.find(window);
++ if (winDataIt == m_windows.end())
++ return;
++
++ WindowData &data(*winDataIt);
++ bool alsoSwap = data.updatePending;
++ data.updatePending = false;
++
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ if (!cd->isRenderable())
++ return;
++
++ if (!cd->updatesEnabled)
++ return;
++
++ if (!ensureRhi(window, data))
++ return;
++
++ cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
++ // Event delivery/processing triggered the window to be deleted or stop rendering.
++ if (!m_windows.contains(window))
++ return;
++
++ QSize effectiveOutputSize; // always prefer what the surface tells us, not the QWindow
++ if (cd->swapchain) {
++ effectiveOutputSize = cd->swapchain->surfacePixelSize();
++ // An update request could still be delivered right before we get an
++ // unexpose. With Vulkan on Windows for example attempting to render
++ // leads to failures at this stage since the surface size is already 0.
++ if (effectiveOutputSize.isEmpty())
++ return;
++ }
++
++ Q_TRACE_SCOPE(QSG_renderWindow);
++ QElapsedTimer renderTimer;
++ qint64 renderTime = 0, syncTime = 0, polishTime = 0;
++ const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
++ if (profileFrames)
++ renderTimer.start();
++ Q_TRACE(QSG_polishItems_entry);
++ Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
++
++ m_inPolish = true;
++ cd->polishItems();
++ m_inPolish = false;
++
++ if (profileFrames)
++ polishTime = renderTimer.nsecsElapsed();
++
++ Q_TRACE(QSG_polishItems_exit);
++ Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
++ QQuickProfiler::SceneGraphRenderLoopFrame,
++ QQuickProfiler::SceneGraphPolishPolish);
++ Q_TRACE(QSG_sync_entry);
++
++ emit window->afterAnimating();
++
++ // Begin the frame before syncing -> sync is where we may invoke
++ // updatePaintNode() on the items and they may want to do resource updates.
++ // Also relevant for applications that connect to the before/afterSynchronizing
++ // signals and want to do graphics stuff already there.
++ if (cd->swapchain) {
++ Q_ASSERT(!effectiveOutputSize.isEmpty());
++ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
++ const QSize previousOutputSize = cd->swapchain->currentPixelSize();
++ if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
++ if (cd->swapchainJustBecameRenderable)
++ qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
++
++ cd->hasActiveSwapchain = cd->swapchain->createOrResize();
++ if (!cd->hasActiveSwapchain) {
++ if (data.rhi->isDeviceLost()) {
++ handleDeviceLoss();
++ return;
++ } else if (previousOutputSize.isEmpty() && !swRastFallbackDueToSwapchainFailure && rhiSupport->attemptReinitWithSwRastUponFail()) {
++ qWarning("Failed to create swapchain."
++ " Retrying by requesting a software rasterizer, if applicable for the 3D API implementation.");
++ swRastFallbackDueToSwapchainFailure = true;
++ teardownGraphics();
++ return;
++ }
++ }
++
++ cd->swapchainJustBecameRenderable = false;
++ cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
++
++ if (cd->hasActiveSwapchain) {
++ // surface size atomicity: now that buildOrResize() succeeded,
++ // query the size that was used in there by the swapchain, and
++ // that is the size we will use while preparing the next frame.
++ effectiveOutputSize = cd->swapchain->currentPixelSize();
++ qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize;
++ } else {
++ qWarning("Failed to build or resize swapchain");
++ }
++ }
++
++ emit window->beforeFrameBegin();
++
++ Q_ASSERT(data.rhi == cd->rhi);
++ QRhi::FrameOpResult frameResult = data.rhi->beginFrame(cd->swapchain);
++ if (frameResult != QRhi::FrameOpSuccess) {
++ if (frameResult == QRhi::FrameOpDeviceLost)
++ handleDeviceLoss();
++ else if (frameResult == QRhi::FrameOpError)
++ qWarning("Failed to start frame");
++ // out of date is not worth warning about - it may happen even during resizing on some platforms
++ emit window->afterFrameEnd();
++ return;
++ }
++ }
++
++ // Enable external OpenGL rendering connected to one of the
++ // QQuickWindow signals (beforeSynchronizing, beforeRendering,
++ // etc.) to function like it did on the direct OpenGL path,
++ // i.e. ensure there is a context current, just in case.
++ data.rhi->makeThreadLocalNativeContextCurrent();
++
++ cd->syncSceneGraph();
++ data.rc->endSync();
++
++ if (profileFrames)
++ syncTime = renderTimer.nsecsElapsed();
++
++ Q_TRACE(QSG_sync_exit);
++ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
++ QQuickProfiler::SceneGraphRenderLoopSync);
++ Q_TRACE(QSG_render_entry);
++
++ cd->renderSceneGraph();
++
++ if (profileFrames)
++ renderTime = renderTimer.nsecsElapsed();
++ Q_TRACE(QSG_render_exit);
++ Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
++ QQuickProfiler::SceneGraphRenderLoopRender);
++ Q_TRACE(QSG_swap_entry);
++
++ const bool needsPresent = alsoSwap && window->isVisible();
++ double lastCompletedGpuTime = 0;
++ if (cd->swapchain) {
++ QRhi::EndFrameFlags flags;
++ if (!needsPresent)
++ flags |= QRhi::SkipPresent;
++ QRhi::FrameOpResult frameResult = data.rhi->endFrame(cd->swapchain, flags);
++ if (frameResult != QRhi::FrameOpSuccess) {
++ if (frameResult == QRhi::FrameOpDeviceLost)
++ handleDeviceLoss();
++ else if (frameResult == QRhi::FrameOpError)
++ qWarning("Failed to end frame");
++ } else {
++ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
++ }
++ }
++ if (needsPresent)
++ cd->fireFrameSwapped();
++
++ emit window->afterFrameEnd();
++
++ qint64 swapTime = 0;
++ if (profileFrames)
++ swapTime = renderTimer.nsecsElapsed();
++
++ Q_TRACE(QSG_swap_exit);
++ Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
++ QQuickProfiler::SceneGraphRenderLoopSwap);
++
++ if (profileFrames) {
++ qCDebug(QSG_LOG_TIME_RENDERLOOP,
++ "[window %p][gui thread] syncAndRender: frame rendered in %dms, polish=%d, sync=%d, render=%d, swap=%d, perWindowFrameDelta=%d",
++ window,
++ int(swapTime / 1000000),
++ int(polishTime / 1000000),
++ int((syncTime - polishTime) / 1000000),
++ int((renderTime - syncTime) / 1000000),
++ int((swapTime - renderTime) / 1000000),
++ int(data.timeBetweenRenders.restart()));
++ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
++ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] syncAndRender: last retrieved GPU frame time was %.4f ms",
++ window,
++ lastCompletedGpuTime * 1000.0);
++ }
++ }
++
++ // Might have been set during syncSceneGraph()
++ if (data.updatePending)
++ maybeUpdate(window);
++ }
++
++ void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
++ {
++ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
++
++ // This is tricker than used to be. We want to detect having an empty
++ // surface size (which may be the case even when window->size() is
++ // non-empty, on some platforms with some graphics APIs!) as well as the
++ // case when the window just became "newly exposed" (e.g. after a
++ // minimize-restore on Windows, or when switching between fully obscured -
++ // not fully obscured on macOS)
++
++ if (!window->isExposed() || (wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()))
++ wd->hasRenderableSwapchain = false;
++
++ if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
++ && !wd->swapchain->surfacePixelSize().isEmpty())
++ {
++ wd->hasRenderableSwapchain = true;
++ wd->swapchainJustBecameRenderable = true;
++ }
++
++ auto winDataIt = m_windows.find(window);
++ if (winDataIt != m_windows.end()) {
++ if (window->isExposed() && (!winDataIt->rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) {
++ winDataIt->updatePending = true;
++ renderWindow(window);
++ }
++ }
++ }
++
++ QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
++ {
++ auto winDataIt = m_windows.find(window);
++ if (winDataIt == m_windows.end())
++ return QImage();
++
++ if (!ensureRhi(window, *winDataIt))
++ return QImage();
++
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ m_inPolish = true;
++ cd->polishItems();
++ m_inPolish = false;
++
++ // The assumption is that the swapchain is usable since on expose we do a
++ // renderWindow() so one cannot get to grab() without having done at least
++ // one on-screen frame.
++ cd->rhi->beginFrame(cd->swapchain);
++ cd->rhi->makeThreadLocalNativeContextCurrent(); // for custom GL rendering before/during/after sync
++ cd->syncSceneGraph();
++ cd->renderSceneGraph();
++ QImage image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(cd->rhi, cd->swapchain->currentFrameCommandBuffer());
++ cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
++
++ image.setDevicePixelRatio(window->effectiveDevicePixelRatio());
++ return image;
++ }
++
++ void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
++ {
++ auto winDataIt = m_windows.find(window);
++ if (winDataIt == m_windows.end())
++ return;
++
++ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
++ if (!cd->isRenderable())
++ return;
++
++ winDataIt->updatePending = true;
++
++ // An updatePolish() implementation may call update() to get the QQuickItem
++ // dirtied. That's fine but it also leads to calling this function.
++ // Requesting another update is a waste then since the updatePolish() call
++ // will be followed up with a round of sync and render.
++ if (m_inPolish)
++ return;
++
++ window->requestUpdate();
++ }
++
++ QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const
++ {
++ return sg;
++ }
++
++ QSGRenderContext *QSGGuiThreadRenderLoop::createRenderContext(QSGContext *sg) const
++ {
++ QSGRenderContext *rc = sg->createRenderContext();
++ pendingRenderContexts.insert(rc);
++ return rc;
++ }
++
++ void QSGGuiThreadRenderLoop::releaseResources(QQuickWindow *w)
++ {
++ // No full invalidation of the rendercontext, just clear some caches.
++ QQuickWindowPrivate *d = QQuickWindowPrivate::get(w);
++ emit d->context->releaseCachedResourcesRequested();
++ if (d->renderer)
++ d->renderer->releaseCachedResources();
++ #if QT_CONFIG(quick_shadereffect)
++ QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(w);
++ #endif
++ }
++
++ void QSGGuiThreadRenderLoop::handleUpdateRequest(QQuickWindow *window)
++ {
++ renderWindow(window);
++ }
++
++ #endif // ENABLE_DEFAULT_BACKEND
++
++ QT_END_NAMESPACE
++
++ #include "qsgrenderloop.moc"
++ #include "moc_qsgrenderloop_p.cpp"