aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgrenderloop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgrenderloop.cpp')
-rw-r--r--src/quick/scenegraph/qsgrenderloop.cpp702
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