diff options
Diffstat (limited to 'src/quick/scenegraph/qsgrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgrenderloop.cpp | 702 |
1 files changed, 358 insertions, 344 deletions
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index f609055677..3c8da0fc6a 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -1,50 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQuick module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 #include "qsgrenderloop_p.h" #include "qsgthreadedrenderloop_p.h" -#include "qsgwindowsrenderloop_p.h" #include "qsgrhisupport_p.h" #include <private/qquickanimatorcontroller_p.h> #include <QtCore/QCoreApplication> -#include <QtCore/QTime> +#include <QtCore/QElapsedTimer> #include <QtCore/QLibraryInfo> #include <QtCore/private/qabstractanimation_p.h> @@ -57,19 +20,15 @@ #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> -#if QT_CONFIG(opengl) -#include <QtGui/QOpenGLContext> -#if QT_CONFIG(quick_shadereffect) -#include <private/qquickopenglshadereffectnode_p.h> -#endif #include <private/qsgdefaultrendercontext_p.h> -#endif #ifdef Q_OS_WIN #include <QtCore/qt_windows.h> @@ -78,30 +37,30 @@ QT_BEGIN_NAMESPACE extern bool qsg_useConsistentTiming(); -extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); -// ### We do not yet support using Qt Quick with QRhi (and Vulkan, D3D -// or Metal) in -no-opengl builds as of Qt 5.14. This is due to to the -// widespread direct OpenGL usage all over the place in qsgdefault* -// and the related classes. To be cleaned up in Qt 6 when the direct -// GL code path is removed. +#define ENABLE_DEFAULT_BACKEND -#if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ +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) -#define ENABLE_DEFAULT_BACKEND /* - expectations for this manager to work: - - one opengl context to render multiple windows - - OpenGL pipeline will not block for vsync in swap - - OpenGL pipeline will block based on a full buffer queue. - - Multiple screens can share the OpenGL context - - Animations are advanced for all windows once per swap + - 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 -#endif QSGRenderLoop *QSGRenderLoop::s_instance = nullptr; @@ -122,11 +81,6 @@ void QSGRenderLoop::cleanup() } delete s_instance; s_instance = nullptr; - -#ifdef ENABLE_DEFAULT_BACKEND - QSGRhiSupport::instance()->cleanup(); - QSGRhiProfileConnection::instance()->cleanup(); -#endif } QSurface::SurfaceType QSGRenderLoop::windowSurfaceType() const @@ -143,19 +97,12 @@ void QSGRenderLoop::postJob(QQuickWindow *window, QRunnable *job) Q_ASSERT(job); #ifdef ENABLE_DEFAULT_BACKEND Q_ASSERT(window); - if (!QSGRhiSupport::instance()->isRhiEnabled()) { - if (window->openglContext()) { - window->openglContext()->makeCurrent(window); - job->run(); - } - } else { - QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); - if (cd->rhi) - cd->rhi->makeThreadLocalNativeContextCurrent(); - job->run(); - } + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); + if (cd->rhi) + cd->rhi->makeThreadLocalNativeContextCurrent(); + job->run(); #else - Q_UNUSED(window) + Q_UNUSED(window); job->run(); #endif delete job; @@ -187,27 +134,41 @@ public: QAnimationDriver *animationDriver() const override { return nullptr; } QSGContext *sceneGraphContext() const override; - QSGRenderContext *createRenderContext(QSGContext *) const override { return rc; } + 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 grabOnly : 1; + bool rhiDeviceLost : 1; + bool rhiDoomed : 1; }; + bool ensureRhi(QQuickWindow *window, WindowData &data); + QHash<QQuickWindow *, WindowData> m_windows; - QOpenGLContext *gl = nullptr; QOffscreenSurface *offscreenSurface = nullptr; - QRhi *rhi = nullptr; QSGContext *sg; - QSGRenderContext *rc; + mutable QSet<QSGRenderContext *> pendingRenderContexts; + + bool m_inPolish = false; - QImage grabContent; + bool swRastFallbackDueToSwapchainFailure = false; }; #endif @@ -223,48 +184,32 @@ QSGRenderLoop *QSGRenderLoop::instance() QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); QSGRenderLoopType loopType; - if (rhiSupport->isRhiEnabled() && rhiSupport->rhiBackend() != QRhi::OpenGLES2) { + if (rhiSupport->rhiBackend() != QRhi::OpenGLES2) { loopType = ThreadedRenderLoop; } else { - loopType = BasicRenderLoop; -#ifdef Q_OS_WIN - // With desktop OpenGL (opengl32.dll), use threaded. Otherwise (ANGLE) use windows. - if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL - && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) - { - loopType = ThreadedRenderLoop; - } else { - loopType = WindowsRenderLoop; - } -#else if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) loopType = ThreadedRenderLoop; -#endif + else + loopType = BasicRenderLoop; } - if (rhiSupport->isRhiEnabled()) { - 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; - } + switch (rhiSupport->rhiBackend()) { + case QRhi::Null: + loopType = BasicRenderLoop; + break; - // no 'windows' because that's not yet ported to the rhi - if (loopType == WindowsRenderLoop) - loopType = BasicRenderLoop; + 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 @@ -278,12 +223,14 @@ QSGRenderLoop *QSGRenderLoop::instance() if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDER_LOOP"))) { const QByteArray loopName = qgetenv("QSG_RENDER_LOOP"); - if (loopName == "windows") - loopType = WindowsRenderLoop; - else if (loopName == "basic") + 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") + } else if (loopName == "threaded") { loopType = ThreadedRenderLoop; + } } switch (loopType) { @@ -293,12 +240,8 @@ QSGRenderLoop *QSGRenderLoop::instance() s_instance = new QSGThreadedRenderLoop(); break; #endif - case WindowsRenderLoop: - qCDebug(QSG_LOG_INFO, "windows render loop"); - s_instance = new QSGWindowsRenderLoop(); - break; default: - qCDebug(QSG_LOG_INFO, "QSG: basic render loop"); + qCDebug(QSG_LOG_INFO, "basic render loop"); s_instance = new QSGGuiThreadRenderLoop(); break; } @@ -316,30 +259,39 @@ void QSGRenderLoop::setInstance(QSGRenderLoop *instance) s_instance = instance; } -void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window, - bool isEs) +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::contextCreationFailureMessage(window->requestedFormat(), - &translatedMessage, - &untranslatedMessage, - isEs); + 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) && !defined(Q_OS_WINRT) +#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 && !Q_OS_WINRT +#endif // Q_OS_WIN if (!signalEmitted) qFatal("%s", qPrintable(untranslatedMessage)); + + recurseGuard.remove(window); } #ifdef ENABLE_DEFAULT_BACKEND @@ -351,22 +303,20 @@ QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop() } sg = QSGContext::createDefaultContext(); - rc = sg->createRenderContext(); } QSGGuiThreadRenderLoop::~QSGGuiThreadRenderLoop() { - delete rc; + qDeleteAll(pendingRenderContexts); delete sg; } void QSGGuiThreadRenderLoop::show(QQuickWindow *window) { - WindowData data; - data.updatePending = false; - data.grabOnly = false; - m_windows[window] = data; + if (!m_windows.contains(window)) + m_windows.insert(window, {}); + m_windows[window].timeBetweenRenders.start(); maybeUpdate(window); } @@ -374,42 +324,27 @@ void QSGGuiThreadRenderLoop::hide(QQuickWindow *window) { QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); cd->fireAboutToStop(); - if (m_windows.contains(window)) - m_windows[window].updatePending = false; + auto it = m_windows.find(window); + if (it != m_windows.end()) + it->updatePending = false; } void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window) { - m_windows.remove(window); hide(window); + + WindowData data = m_windows.value(window, {}); + m_windows.remove(window); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); - bool current = false; - if (gl || rhi) { - if (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. - rhi->makeThreadLocalNativeContextCurrent(); - current = true; - } else { - QSurface *surface = window; - // There may be no platform window if the window got closed. - if (!window->handle()) - surface = offscreenSurface; - current = gl->makeCurrent(surface); - } - if (Q_UNLIKELY(!current)) - qCDebug(QSG_LOG_RENDERLOOP, "cleanup without an OpenGL context"); + 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 QT_CONFIG(quick_shadereffect) - QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); -#if QT_CONFIG(opengl) - QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); -#endif -#endif - if (d->swapchain) { if (window->handle()) { // We get here when exiting via QCoreApplication::quit() instead of @@ -422,40 +357,64 @@ void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window) } d->cleanupNodesOnShutdown(); - if (m_windows.size() == 0) { - rc->invalidate(); - d->rhi = nullptr; - delete rhi; - rhi = nullptr; - delete gl; - gl = nullptr; + +#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; - } else if (gl && window == gl->surface() && current) { - if (!rhi) - gl->doneCurrent(); } +} - d->animationController.reset(); +void QSGGuiThreadRenderLoop::teardownGraphics() +{ + for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) { + if (it->rhi) { + QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown(); + if (it->rc) + it->rc->invalidate(); + releaseSwapchain(it.key()); + if (it->ownRhi) + QSGRhiSupport::instance()->destroyRhi(it->rhi, {}); + it->rhi = nullptr; + } + } } void QSGGuiThreadRenderLoop::handleDeviceLoss() { - if (!rhi || !rhi->isDeviceLost()) - return; + qWarning("Graphics device lost, cleaning up scenegraph and releasing RHIs"); - qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI"); + for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) { + if (!it->rhi || !it->rhi->isDeviceLost()) + continue; - for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it) QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown(); - rc->invalidate(); + if (it->rc) + it->rc->invalidate(); - for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it) releaseSwapchain(it.key()); + it->rhiDeviceLost = true; - delete rhi; - rhi = nullptr; + if (it->ownRhi) + QSGRhiSupport::instance()->destroyRhi(it->rhi, {}); + it->rhi = nullptr; + } } void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window) @@ -477,10 +436,10 @@ bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event) // 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) { + if (w) releaseSwapchain(w); - w->removeEventFilter(this); - } + // keep this filter on the window - needed for uncommon but valid + // sequences of calls like window->destroy(); window->show(); } break; default: @@ -489,119 +448,100 @@ bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event) return QObject::eventFilter(watched, event); } -void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) +bool QSGGuiThreadRenderLoop::ensureRhi(QQuickWindow *window, WindowData &data) { - if (!m_windows.contains(window)) - return; - - WindowData &data = const_cast<WindowData &>(m_windows[window]); - bool alsoSwap = data.updatePending; - data.updatePending = false; - QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); - if (!cd->isRenderable()) - return; - - bool current = false; QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); - int rhiSampleCount = 1; - const bool enableRhi = rhiSupport->isRhiEnabled(); + 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 (enableRhi && !rhi) { if (!offscreenSurface) offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window); - rhi = rhiSupport->createRhi(window, offscreenSurface); + const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure; + QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer); + data.rhi = rhiResult.rhi; + data.ownRhi = rhiResult.own; - if (rhi) { - if (rhiSupport->isProfilingRequested()) - QSGRhiProfileConnection::instance()->initialize(rhi); + if (data.rhi) { + data.rhiDeviceLost = false; - current = true; - rhi->makeThreadLocalNativeContextCurrent(); + 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. - rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi); + data.sampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, data.rhi); - cd->rhi = rhi; // set this early in case something hooked up to rc initialized() accesses it + cd->rhi = data.rhi; // set this early in case something hooked up to rc initialized() accesses it QSGDefaultRenderContext::InitParams rcParams; - rcParams.rhi = rhi; - rcParams.sampleCount = rhiSampleCount; + rcParams.rhi = data.rhi; + rcParams.sampleCount = data.sampleCount; rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio(); rcParams.maybeSurface = window; cd->context->initialize(&rcParams); } else { - handleContextCreationFailure(window, false); - } - } else if (!enableRhi && !gl) { - gl = new QOpenGLContext(); - gl->setFormat(window->requestedFormat()); - gl->setScreen(window->screen()); - if (qt_gl_global_share_context()) - gl->setShareContext(qt_gl_global_share_context()); - if (!gl->create()) { - const bool isEs = gl->isOpenGLES(); - delete gl; - gl = nullptr; - handleContextCreationFailure(window, isEs); - } else { - if (!offscreenSurface) { - offscreenSurface = new QOffscreenSurface; - offscreenSurface->setFormat(gl->format()); - offscreenSurface->create(); + if (!data.rhiDeviceLost) { + data.rhiDoomed = true; + handleContextCreationFailure(window); } - cd->fireOpenGLContextCreated(gl); - current = gl->makeCurrent(window); - } - if (current) { - QSGDefaultRenderContext::InitParams rcParams; - rcParams.sampleCount = qMax(1, gl->format().samples()); - rcParams.openGLContext = gl; - rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio(); - rcParams.maybeSurface = window; - cd->context->initialize(&rcParams); - } - } else { - if (rhi) { - current = true; - // With the rhi making the (OpenGL) context current serves only one - // purpose: to enable external OpenGL rendering connected to one of - // the QQuickWindow signals (beforeSynchronizing, beforeRendering, - // etc.) to function like it did on the direct OpenGL path. For our - // own rendering this call would not be necessary. - rhi->makeThreadLocalNativeContextCurrent(); - } else { - current = gl->makeCurrent(window); + // otherwise no error, just return false so that we will retry on a subsequent rendering attempt } } - if (enableRhi && rhi && !cd->swapchain) { + 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 = rhi; + 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 = window->requestedFormat().alphaBufferSize() > 0; + const bool alpha = requestedFormat.alphaBufferSize() > 0; if (alpha) flags |= QRhiSwapChain::SurfaceHasPreMulAlpha; - cd->swapchain = rhi->newSwapChain(); - cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, - QSize(), - rhiSampleCount, - QRhiRenderBuffer::UsedWithSwapChainOnly); + // 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); + cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); + } cd->swapchain->setWindow(window); - cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); + rhiSupport->applySwapChainFormat(cd->swapchain, window); qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s", - rhiSampleCount, alpha ? "yes" : "no"); - cd->swapchain->setSampleCount(rhiSampleCount); + data.sampleCount, alpha ? "yes" : "no"); + cd->swapchain->setSampleCount(data.sampleCount); cd->swapchain->setFlags(flags); cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor(); cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain); @@ -609,44 +549,50 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) 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; + bool lastDirtyWindow = true; - auto i = m_windows.constBegin(); - while (i != m_windows.constEnd()) { - if (i.value().updatePending) { + for (auto it = m_windows.cbegin(), end = m_windows.cend(); it != end; ++it) { + if (it->updatePending) { lastDirtyWindow = false; break; } - i++; - } - - // Check for context loss. - if (!current && !rhi && !gl->isValid()) { - for (auto it = m_windows.constBegin() ; it != m_windows.constEnd(); it++) { - QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(it.key()); - windowPrivate->cleanupNodesOnShutdown(); - } - rc->invalidate(); - current = gl->create() && gl->makeCurrent(window); - if (current) { - QSGDefaultRenderContext::InitParams rcParams; - rcParams.sampleCount = qMax(1, gl->format().samples()); - rcParams.openGLContext = gl; - rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio(); - rcParams.maybeSurface = window; - rc->initialize(&rcParams); - } } - if (!current) + cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(window); + // Event delivery/processing triggered the window to be deleted or stop rendering. + if (!m_windows.contains(window)) return; - if (!data.grabOnly) { - cd->flushFrameSynchronousEvents(); - // 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(); @@ -657,20 +603,27 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) return; } + Q_TRACE_SCOPE(QSG_renderWindow); QElapsedTimer renderTimer; qint64 renderTime = 0, syncTime = 0, polishTime = 0; - bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled(); + 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(); @@ -680,15 +633,24 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) // 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->buildOrResize(); - if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) { - handleDeviceLoss(); - return; + 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; @@ -705,86 +667,94 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) } } - Q_ASSERT(rhi == cd->rhi); - // ### the flag should only be set when the app requests it, but there's no way to do that right now - QRhi::BeginFrameFlags frameFlags = QRhi::ExternalContentsInPass; - QRhi::FrameOpResult frameResult = rhi->beginFrame(cd->swapchain, frameFlags); + 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(); if (lastDirtyWindow) - rc->endSync(); + 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(window->size(), effectiveOutputSize); + cd->renderSceneGraph(); if (profileFrames) renderTime = renderTimer.nsecsElapsed(); + Q_TRACE(QSG_render_exit); Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopRender); - - if (data.grabOnly) { - const bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() != 255; - if (cd->swapchain) - grabContent = rhiSupport->grabAndBlockInCurrentFrame(rhi, cd->swapchain); - else - grabContent = qt_gl_read_framebuffer(window->size() * window->effectiveDevicePixelRatio(), alpha, alpha); - grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio()); - data.grabOnly = false; - } + Q_TRACE(QSG_swap_entry); const bool needsPresent = alsoSwap && window->isVisible(); + double lastCompletedGpuTime = 0; if (cd->swapchain) { - QRhi::EndFrameFlags flags = 0; + QRhi::EndFrameFlags flags; if (!needsPresent) flags |= QRhi::SkipPresent; - QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain, flags); + 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(); } - } else if (needsPresent) { - if (!cd->customRenderStage || !cd->customRenderStage->swap()) - gl->swapBuffers(window); } 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 (QSG_LOG_TIME_RENDERLOOP().isDebugEnabled()) { - static QTime lastFrameTime = QTime::currentTime(); + if (profileFrames) { qCDebug(QSG_LOG_TIME_RENDERLOOP, - "Frame rendered with 'basic' renderloop in %dms, polish=%d, sync=%d, render=%d, swap=%d, frameDelta=%d", + "[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) / 10000000), - int(lastFrameTime.msecsTo(QTime::currentTime()))); - lastFrameTime = QTime::currentTime(); + 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); + } } - QSGRhiProfileConnection::instance()->send(rhi); - // Might have been set during syncSceneGraph() if (data.updatePending) maybeUpdate(window); @@ -811,33 +781,66 @@ void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window) wd->swapchainJustBecameRenderable = true; } - if (window->isExposed() && (!rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) { - m_windows[window].updatePending = true; - renderWindow(window); + 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) { - if (!m_windows.contains(window)) + auto winDataIt = m_windows.find(window); + if (winDataIt == m_windows.end()) + return QImage(); + + if (!ensureRhi(window, *winDataIt)) return QImage(); - m_windows[window].grabOnly = true; + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); + m_inPolish = true; + cd->polishItems(); + m_inPolish = false; - renderWindow(window); + // 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); - QImage grabbed = grabContent; - grabContent = QImage(); - return grabbed; + image.setDevicePixelRatio(window->effectiveDevicePixelRatio()); + return image; } void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window) { + auto winDataIt = m_windows.find(window); + if (winDataIt == m_windows.end()) + return; + + // Even if the window is not renderable, + // renderWindow() called on different window + // should not delete QSGTexture's + // from this unrenderable window. + winDataIt->updatePending = true; + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); - if (!cd->isRenderable() || !m_windows.contains(window)) + if (!cd->isRenderable()) + return; + + // 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; - m_windows[window].updatePending = true; window->requestUpdate(); } @@ -846,12 +849,23 @@ 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) @@ -861,7 +875,7 @@ void QSGGuiThreadRenderLoop::handleUpdateRequest(QQuickWindow *window) #endif // ENABLE_DEFAULT_BACKEND +QT_END_NAMESPACE + #include "qsgrenderloop.moc" #include "moc_qsgrenderloop_p.cpp" - -QT_END_NAMESPACE |