aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgthreadedrenderloop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgthreadedrenderloop.cpp')
-rw-r--r--src/quick/scenegraph/qsgthreadedrenderloop.cpp462
1 files changed, 272 insertions, 190 deletions
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
index 8a0202ede9..84450c692b 100644
--- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp
+++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
-** 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.
+// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtCore/QMutex>
@@ -70,6 +34,10 @@
#include <qtquick_tracepoints_p.h>
+#ifdef Q_OS_DARWIN
+#include <QtCore/private/qcore_mac_p.h>
+#endif
+
/*
Overall design:
@@ -117,56 +85,32 @@
QT_BEGIN_NAMESPACE
-#define QSG_RT_PAD " (RT) %s"
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_entry)
+Q_TRACE_POINT(qtquick, QSG_polishAndSync_exit)
+Q_TRACE_POINT(qtquick, QSG_wait_entry)
+Q_TRACE_POINT(qtquick, QSG_wait_exit)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_entry)
+Q_TRACE_POINT(qtquick, QSG_syncAndRender_exit)
+Q_TRACE_POINT(qtquick, QSG_animations_entry)
+Q_TRACE_POINT(qtquick, QSG_animations_exit)
-static inline int qsgrl_animation_interval() {
- qreal refreshRate = QGuiApplication::primaryScreen()->refreshRate();
- // To work around that some platforms wrongfully return 0 or something
- // bogus for refreshrate
- if (refreshRate < 1)
- return 16;
- return int(1000 / refreshRate);
-}
+#define QSG_RT_PAD " (RT) %s"
extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
// RL: Render Loop
// RT: Render Thread
-// Passed from the RL to the RT when a window is removed obscured and
-// should be removed from the render loop.
-const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
-
-// Passed from the RL to RT when GUI has been locked, waiting for sync
-// (updatePaintNode())
-const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
-// Passed by the RL to the RT to free up maybe release SG and GL contexts
-// if no windows are rendering.
-const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
-
-// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
-// called.
-const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
-
-// Passed by the window when there is a render job to run
-const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
-
-// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
-// the event filter installed on the QQuickWindow.
-const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
-
-template <typename T> T *windowFor(const QList<T> &list, QQuickWindow *window)
+QSGThreadedRenderLoop::Window *QSGThreadedRenderLoop::windowFor(QQuickWindow *window)
{
- for (int i=0; i<list.size(); ++i) {
- const T &t = list.at(i);
+ for (const auto &t : std::as_const(m_windows)) {
if (t.window == window)
- return const_cast<T *>(&t);
+ return const_cast<Window *>(&t);
}
return nullptr;
}
-
class WMWindowEvent : public QEvent
{
public:
@@ -178,7 +122,7 @@ class WMTryReleaseEvent : public WMWindowEvent
{
public:
WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
- : WMWindowEvent(win, WM_TryRelease)
+ : WMWindowEvent(win, QEvent::Type(WM_TryRelease))
, inDestructor(destroy)
, needsFallback(needsFallbackSurface)
{}
@@ -190,24 +134,27 @@ public:
class WMSyncEvent : public WMWindowEvent
{
public:
- WMSyncEvent(QQuickWindow *c, bool inExpose, bool force)
- : WMWindowEvent(c, WM_RequestSync)
+ WMSyncEvent(QQuickWindow *c, bool inExpose, bool force, const QRhiSwapChainProxyData &scProxyData)
+ : WMWindowEvent(c, QEvent::Type(WM_RequestSync))
, size(c->size())
, dpr(float(c->effectiveDevicePixelRatio()))
, syncInExpose(inExpose)
, forceRenderPass(force)
+ , scProxyData(scProxyData)
{}
QSize size;
float dpr;
bool syncInExpose;
bool forceRenderPass;
+ QRhiSwapChainProxyData scProxyData;
};
class WMGrabEvent : public WMWindowEvent
{
public:
- WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
+ WMGrabEvent(QQuickWindow *c, QImage *result) :
+ WMWindowEvent(c, QEvent::Type(WM_Grab)), image(result) {}
QImage *image;
};
@@ -215,7 +162,7 @@ class WMJobEvent : public WMWindowEvent
{
public:
WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
- : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
+ : WMWindowEvent(c, QEvent::Type(WM_PostJob)), job(postedJob) {}
~WMJobEvent() { delete job; }
QRunnable *job;
};
@@ -223,7 +170,8 @@ public:
class WMReleaseSwapchainEvent : public WMWindowEvent
{
public:
- WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
+ WMReleaseSwapchainEvent(QQuickWindow *c) :
+ WMWindowEvent(c, QEvent::Type(WM_ReleaseSwapchain)) { }
};
class QSGRenderThreadEventQueue : public QQueue<QEvent *>
@@ -275,6 +223,7 @@ public:
QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext)
: wm(w)
, rhi(nullptr)
+ , ownRhi(true)
, offscreenSurface(nullptr)
, animatorDriver(nullptr)
, pendingUpdate(0)
@@ -285,11 +234,10 @@ public:
, stopEventProcessing(false)
{
sgrc = static_cast<QSGDefaultRenderContext *>(renderContext);
-#if defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)
+#if (defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)) || defined(Q_OS_INTEGRITY)
// The SDP 6.6.0 x86 MESA driver requires a larger stack than the default.
setStackSize(1024 * 1024);
#endif
- vsyncDelta = qsgrl_animation_interval();
}
~QSGRenderThread()
@@ -332,10 +280,12 @@ public:
};
void ensureRhi();
+ void teardownGraphics();
void handleDeviceLoss();
QSGThreadedRenderLoop *wm;
QRhi *rhi;
+ bool ownRhi;
QSGDefaultRenderContext *sgrc;
QOffscreenSurface *offscreenSurface;
@@ -347,8 +297,6 @@ public:
volatile bool active;
- float vsyncDelta;
-
QMutex mutex;
QWaitCondition waitCondition;
@@ -357,10 +305,12 @@ public:
QQuickWindow *window; // Will be 0 when window is not exposed
QSize windowSize;
float dpr = 1;
+ QRhiSwapChainProxyData scProxyData;
int rhiSampleCount = 1;
bool rhiDeviceLost = false;
bool rhiDoomed = false;
bool guiNotifiedAboutRhiFailure = false;
+ bool swRastFallbackDueToSwapchainFailure = false;
// Local event queue stuff...
bool stopEventProcessing;
@@ -395,6 +345,7 @@ bool QSGRenderThread::event(QEvent *e)
window = se->window;
windowSize = se->size;
dpr = se->dpr;
+ scProxyData = se->scProxyData;
pendingUpdate |= SyncRequest;
if (se->syncInExpose) {
@@ -423,10 +374,15 @@ bool QSGRenderThread::event(QEvent *e)
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- not releasing because window is still active");
if (window) {
QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting external renderers such as Quick 3D to release cached resources");
+ emit d->context->releaseCachedResourcesRequested();
if (d->renderer) {
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting renderer to release cached resources");
d->renderer->releaseCachedResources();
}
+#if QT_CONFIG(quick_shadereffect)
+ QSGRhiShaderEffectNode::garbageCollectMaterialTypeCache(window);
+#endif
}
}
waitCondition.wakeOne();
@@ -444,14 +400,14 @@ bool QSGRenderThread::event(QEvent *e)
if (ce->window) {
if (rhi) {
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(ce->window);
- cd->rhi->makeThreadLocalNativeContextCurrent();
// The assumption is that the swapchain is usable, because on
// expose the thread starts up and renders a frame so one cannot
// get here 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();
sgrc->endSync();
- cd->renderSceneGraph(ce->window->size());
+ cd->renderSceneGraph();
*ce->image = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain->currentFrameCommandBuffer());
cd->rhi->endFrame(cd->swapchain, QRhi::SkipPresent);
}
@@ -511,27 +467,19 @@ void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor
return;
}
-
bool wipeSG = inDestructor || !window->isPersistentSceneGraph();
bool wipeGraphics = inDestructor || (wipeSG && !window->isPersistentGraphics());
- bool current = true;
- if (rhi)
- rhi->makeThreadLocalNativeContextCurrent();
-
- if (Q_UNLIKELY(!current)) {
- qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- cleanup without an OpenGL context");
- }
+ rhi->makeThreadLocalNativeContextCurrent();
QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window);
-#if QT_CONFIG(quick_shadereffect)
- QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
-#endif
-
// The canvas nodes must be cleaned up regardless if we are in the destructor..
if (wipeSG) {
dd->cleanupNodesOnShutdown();
+#if QT_CONFIG(quick_shadereffect)
+ QSGRhiShaderEffectNode::resetMaterialTypeCache(window);
+#endif
} else {
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup");
return;
@@ -556,8 +504,10 @@ void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor
window, dd->swapchain);
}
}
- QSGRhiSupport::instance()->destroyRhi(rhi);
+ if (ownRhi)
+ QSGRhiSupport::instance()->destroyRhi(rhi, dd->graphicsConfig);
rhi = nullptr;
+ dd->rhi = nullptr;
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- QRhi destroyed");
} else {
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup");
@@ -575,7 +525,7 @@ void QSGRenderThread::sync(bool inExpose)
Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
- bool current = true;
+ bool canSync = true;
if (rhi) {
if (windowSize.width() > 0 && windowSize.height() > 0) {
// With the rhi making the (OpenGL) context current serves only one
@@ -587,12 +537,12 @@ void QSGRenderThread::sync(bool inExpose)
} else {
// Zero size windows do not initialize a swapchain and
// rendercontext. So no sync or render can be done then.
- current = false;
+ canSync = false;
}
} else {
- current = false;
+ canSync = false;
}
- if (current) {
+ if (canSync) {
QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
bool hadRenderer = d->renderer != nullptr;
// If the scene graph was touched since the last sync() make sure it sends the
@@ -627,18 +577,25 @@ void QSGRenderThread::sync(bool inExpose)
}
}
+void QSGRenderThread::teardownGraphics()
+{
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
+ wd->cleanupNodesOnShutdown();
+ sgrc->invalidate();
+ wm->releaseSwapchain(window);
+ if (ownRhi)
+ QSGRhiSupport::instance()->destroyRhi(rhi, {});
+ rhi = nullptr;
+}
+
void QSGRenderThread::handleDeviceLoss()
{
if (!rhi || !rhi->isDeviceLost())
return;
qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
- QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown();
- sgrc->invalidate();
- wm->releaseSwapchain(window);
+ teardownGraphics();
rhiDeviceLost = true;
- QSGRhiSupport::instance()->destroyRhi(rhi);
- rhi = nullptr;
}
void QSGRenderThread::syncAndRender()
@@ -652,9 +609,6 @@ void QSGRenderThread::syncAndRender()
Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
Q_TRACE(QSG_sync_entry);
- QElapsedTimer waitTimer;
- waitTimer.start();
-
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "syncAndRender()");
if (profileFrames) {
@@ -673,11 +627,14 @@ void QSGRenderThread::syncAndRender()
pendingUpdate = 0;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
// 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 && windowSize.width() > 0 && windowSize.height() > 0) {
+ const bool hasValidSwapChain = (cd->swapchain && windowSize.width() > 0 && windowSize.height() > 0);
+ if (hasValidSwapChain) {
+ cd->swapchain->setProxyData(scProxyData);
// always prefer what the surface tells us, not the QWindow
const QSize effectiveOutputSize = cd->swapchain->surfacePixelSize();
// An update request could still be delivered right before we get an
@@ -692,10 +649,29 @@ void QSGRenderThread::syncAndRender()
qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed");
cd->hasActiveSwapchain = cd->swapchain->createOrResize();
- if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
- handleDeviceLoss();
- QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
- return;
+ if (!cd->hasActiveSwapchain) {
+ bool bailOut = false;
+ if (rhi->isDeviceLost()) {
+ handleDeviceLoss();
+ bailOut = true;
+ } 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();
+ bailOut = true;
+ }
+ if (bailOut) {
+ QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ if (syncRequested) {
+ // Lock like sync() would do. Note that exposeRequested always includes syncRequested.
+ qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed swapchain init, wake Gui");
+ mutex.lock();
+ waitCondition.wakeOne();
+ mutex.unlock();
+ }
+ return;
+ }
}
cd->swapchainJustBecameRenderable = false;
@@ -774,16 +750,15 @@ void QSGRenderThread::syncAndRender()
d->animationController->unlock();
}
- bool current = true;
// Zero size windows do not initialize a swapchain and
// rendercontext. So no sync or render can be done then.
- if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0 && rhi)
- rhi->makeThreadLocalNativeContextCurrent();
- else
- current = false;
+ const bool canRender = d->renderer && hasValidSwapChain;
+ double lastCompletedGpuTime = 0;
+ if (canRender) {
+ if (!syncRequested) // else this was already done in sync()
+ rhi->makeThreadLocalNativeContextCurrent();
- if (current) {
- d->renderSceneGraph(windowSize, rhi ? cd->swapchain->currentPixelSize() : QSize());
+ d->renderSceneGraph();
if (profileFrames)
renderTime = threadTimer.nsecsElapsed();
@@ -792,16 +767,16 @@ void QSGRenderThread::syncAndRender()
QQuickProfiler::SceneGraphRenderLoopRender);
Q_TRACE(QSG_swap_entry);
- if (cd->swapchain) {
- QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain);
- if (frameResult != QRhi::FrameOpSuccess) {
- if (frameResult == QRhi::FrameOpDeviceLost)
- handleDeviceLoss();
- else if (frameResult == QRhi::FrameOpError)
- qWarning("Failed to end frame");
- if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
- QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
- }
+ QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain);
+ if (frameResult != QRhi::FrameOpSuccess) {
+ if (frameResult == QRhi::FrameOpDeviceLost)
+ handleDeviceLoss();
+ else if (frameResult == QRhi::FrameOpError)
+ qWarning("Failed to end frame");
+ if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
+ QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
+ } else {
+ lastCompletedGpuTime = cd->swapchain->currentFrameCommandBuffer()->lastCompletedGpuTime();
}
d->fireFrameSwapped();
} else {
@@ -821,7 +796,7 @@ void QSGRenderThread::syncAndRender()
// beforeFrameBegin - afterFrameEnd must always come in pairs; if there was
// no before due to 0 size then there shouldn't be an after either
- if (current)
+ if (hasValidSwapChain)
emit window->afterFrameEnd();
// Though it would be more correct to put this block directly after
@@ -852,13 +827,17 @@ void QSGRenderThread::syncAndRender()
int((syncTime/1000000)),
int((renderTime - syncTime) / 1000000),
int((threadTimer.nsecsElapsed() - renderTime) / 1000000));
+ if (!qFuzzyIsNull(lastCompletedGpuTime) && cd->graphicsConfig.timestampsEnabled()) {
+ qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][render thread %p] syncAndRender: last retrieved GPU frame time was %.4f ms",
+ window,
+ QThread::currentThread(),
+ lastCompletedGpuTime * 1000.0);
+ }
}
Q_TRACE(QSG_swap_exit);
Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
QQuickProfiler::SceneGraphRenderLoopSwap);
-
- QSGRhiProfileConnection::instance()->send(rhi);
}
@@ -899,12 +878,13 @@ void QSGRenderThread::ensureRhi()
if (rhiDoomed) // no repeated attempts if the initial attempt failed
return;
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
- rhi = rhiSupport->createRhi(window, offscreenSurface);
+ const bool forcePreferSwRenderer = swRastFallbackDueToSwapchainFailure;
+ QSGRhiSupport::RhiCreateResult rhiResult = rhiSupport->createRhi(window, offscreenSurface, forcePreferSwRenderer);
+ rhi = rhiResult.rhi;
+ ownRhi = rhiResult.own;
if (rhi) {
rhiDeviceLost = false;
rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
- if (rhiSupport->isProfilingRequested())
- QSGRhiProfileConnection::instance()->initialize(rhi); // ### this breaks down with multiple windows
} else {
if (!rhiDeviceLost) {
rhiDoomed = true;
@@ -915,6 +895,8 @@ void QSGRenderThread::ensureRhi()
}
}
if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) {
+ // We need to guarantee that sceneGraphInitialized is emitted
+ // with a context current, if running with OpenGL.
rhi->makeThreadLocalNativeContextCurrent();
QSGDefaultRenderContext::InitParams rcParams;
rcParams.rhi = rhi;
@@ -937,10 +919,13 @@ void QSGRenderThread::ensureRhi()
if (alpha)
flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
- // Request NoVSync if swap interval was set to 0. What this means in
- // practice is another question, but at least we tried.
- if (requestedFormat.swapInterval() == 0)
+ // 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 = rhi->newSwapChain();
static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
@@ -952,6 +937,8 @@ void QSGRenderThread::ensureRhi()
cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
}
cd->swapchain->setWindow(window);
+ cd->swapchain->setProxyData(scProxyData);
+ QSGRhiSupport::instance()->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);
@@ -1021,10 +1008,6 @@ QSGThreadedRenderLoop::QSGThreadedRenderLoop()
: sg(QSGContext::createDefaultContext())
, m_animation_timer(0)
{
-#if defined(QSG_RENDER_LOOP_DEBUG)
- qsgrl_timer.start();
-#endif
-
m_animation_driver = sg->createAnimationDriver(this);
connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted()));
@@ -1094,13 +1077,22 @@ void QSGThreadedRenderLoop::animationStopped()
void QSGThreadedRenderLoop::startOrStopAnimationTimer()
{
+ if (!sg->isVSyncDependent(m_animation_driver))
+ return;
+
int exposedWindows = 0;
+ int unthrottledWindows = 0;
+ int badVSync = 0;
const Window *theOne = nullptr;
for (int i=0; i<m_windows.size(); ++i) {
const Window &w = m_windows.at(i);
if (w.window->isVisible() && w.window->isExposed()) {
++exposedWindows;
theOne = &w;
+ if (w.actualWindowFormat.swapInterval() == 0)
+ ++unthrottledWindows;
+ if (w.badVSync)
+ ++badVSync;
}
}
@@ -1116,17 +1108,28 @@ void QSGThreadedRenderLoop::startOrStopAnimationTimer()
// same path as the no-windows case since polishAndSync() is now called
// potentially for multiple windows over time so it cannot take care of
// advancing the animation driver anymore.
+ //
+ // On top, another case: a window with vsync disabled should disable all the
+ // good stuff and go with the system timer.
+ //
+ // Similarly, if there is at least one window where we determined that
+ // vsync based blocking is not working as expected, that should make us
+ // choose the timer based way.
- if (m_animation_timer != 0 && (exposedWindows == 1 || !m_animation_driver->isRunning())) {
- qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping non-render thread animation timer");
+ const bool canUseVSyncBasedAnimation = exposedWindows == 1 && unthrottledWindows == 0 && badVSync == 0;
+
+ if (m_animation_timer != 0 && (canUseVSyncBasedAnimation || !m_animation_driver->isRunning())) {
+ qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
+ exposedWindows, unthrottledWindows, badVSync);
killTimer(m_animation_timer);
m_animation_timer = 0;
// If animations are running, make sure we keep on animating
if (m_animation_driver->isRunning())
postUpdateRequest(const_cast<Window *>(theOne));
- } else if (m_animation_timer == 0 && exposedWindows != 1 && m_animation_driver->isRunning()) {
- qCDebug(QSG_LOG_RENDERLOOP, "*** Starting non-render thread animation timer");
- m_animation_timer = startTimer(qsgrl_animation_interval());
+ } else if (m_animation_timer == 0 && !canUseVSyncBasedAnimation && m_animation_driver->isRunning()) {
+ qCDebug(QSG_LOG_RENDERLOOP, "*** Starting system (not vsync-based) animation timer (exposedWindows=%d unthrottledWindows=%d badVSync=%d)",
+ exposedWindows, unthrottledWindows, badVSync);
+ m_animation_timer = startTimer(int(sg->vsyncIntervalForAnimationDriver(m_animation_driver)));
}
}
@@ -1146,11 +1149,22 @@ void QSGThreadedRenderLoop::hide(QQuickWindow *window)
qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
if (window->isExposed())
- handleObscurity(windowFor(m_windows, window));
+ handleObscurity(windowFor(window));
releaseResources(window);
}
+void QSGThreadedRenderLoop::resize(QQuickWindow *window)
+{
+ qCDebug(QSG_LOG_RENDERLOOP) << "reisze()" << window;
+
+ Window *w = windowFor(window);
+ if (!w)
+ return;
+
+ w->psTimeAccumulator = 0.0f;
+ w->psTimeSampleCount = 0;
+}
/*
If the window is first hide it, then perform a complete cleanup
@@ -1161,7 +1175,7 @@ void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1192,20 +1206,6 @@ void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
{
QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
-
- // Counterintuitive, because this is not needed under normal circumstances
- // due to the render loop using a dedicated rendercontext per thread, so
- // per window. Problem is, there are cases like calling destroy(); show();
- // on the QQuickWindow. (and we get here on SurfaceAboutToBeDestroyed, i.e.
- // from destroy())
- //
- // That means recreating the native window and all the related graphics
- // infrastructure, but the rendercontext stays around. So still have to
- // notify the renderer to invalidate the relevant objects in the caches.
- //
- if (wd->renderer)
- wd->renderer->invalidatePipelineCacheDependency(wd->rpDescForSwapchain);
-
delete wd->rpDescForSwapchain;
wd->rpDescForSwapchain = nullptr;
delete wd->swapchain;
@@ -1246,7 +1246,7 @@ void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
if (!skipThisExpose)
handleExposure(window);
} else {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
handleObscurity(w);
}
@@ -1260,7 +1260,7 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w) {
qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
Window win;
@@ -1272,23 +1272,31 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
win.thread = new QSGRenderThread(this, renderContext);
win.updateDuringSync = false;
win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
+ win.badVSync = false;
win.timeBetweenPolishAndSyncs.start();
+ win.psTimeAccumulator = 0.0f;
+ win.psTimeSampleCount = 0;
m_windows << win;
w = &m_windows.last();
+ } else {
+ if (!QQuickWindowPrivate::get(window)->updatesEnabled) {
+ qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
+ return;
+ }
}
// set this early as we'll be rendering shortly anyway and this avoids
// specialcasing exposure in polishAndSync.
w->thread->window = window;
+#ifndef QT_NO_DEBUG
if (w->window->width() <= 0 || w->window->height() <= 0
|| (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
-#ifndef QT_NO_DEBUG
qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
<< w->window << " with invalid geometry: " << w->window->geometry()
<< " on " << w->window->screen();
-#endif
}
+#endif
// Because we are going to bind a GL context to it, make sure it
// is created.
@@ -1303,6 +1311,7 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
if (!w->thread->offscreenSurface)
w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
+ w->thread->scProxyData = QRhi::updateSwapChainProxyData(rhiSupport->rhiBackend(), window);
window->installEventFilter(this);
}
@@ -1339,10 +1348,17 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::handleObscurity(Window *w)
{
+ if (!w)
+ return;
+
qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
if (w->thread->isRunning()) {
+ if (!QQuickWindowPrivate::get(w->window)->updatesEnabled) {
+ qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
+ return;
+ }
w->thread->mutex.lock();
- w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
+ w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Obscure)));
w->thread->waitCondition.wait(&w->thread->mutex);
w->thread->mutex.unlock();
}
@@ -1357,7 +1373,7 @@ bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
if (window) {
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread->isRunning()) {
w->thread->mutex.lock();
w->thread->postEvent(new WMReleaseSwapchainEvent(window));
@@ -1378,14 +1394,18 @@ bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "- update request" << window;
- Window *w = windowFor(m_windows, window);
+ if (!QQuickWindowPrivate::get(window)->updatesEnabled) {
+ qCDebug(QSG_LOG_RENDERLOOP, "- updatesEnabled is false, abort");
+ return;
+ }
+ Window *w = windowFor(window);
if (w)
polishAndSync(w);
}
void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
maybeUpdate(w);
}
@@ -1437,7 +1457,7 @@ void QSGThreadedRenderLoop::maybeUpdate(Window *w)
*/
void QSGThreadedRenderLoop::update(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (!w)
return;
@@ -1457,7 +1477,7 @@ void QSGThreadedRenderLoop::update(QQuickWindow *window)
void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w)
releaseResources(w, false);
}
@@ -1513,9 +1533,9 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
}
// Flush pending touch events.
- QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
+ QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->flushFrameSynchronousEvents(window);
// The delivery of the event might have caused the window to stop rendering
- w = windowFor(m_windows, window);
+ w = windowFor(window);
if (!w || !w->thread || !w->thread->window) {
qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
return;
@@ -1526,10 +1546,64 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
qint64 polishTime = 0;
qint64 waitTime = 0;
qint64 syncTime = 0;
+
+ const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
+
+ if (w->actualWindowFormat.swapInterval() != 0 && sg->isVSyncDependent(m_animation_driver)) {
+ w->psTimeAccumulator += elapsedSinceLastMs;
+ w->psTimeSampleCount += 1;
+ // cannot be too high because we'd then delay recognition of broken vsync at start
+ static const int PS_TIME_SAMPLE_LENGTH = 20;
+ if (w->psTimeSampleCount > PS_TIME_SAMPLE_LENGTH) {
+ const float t = w->psTimeAccumulator / w->psTimeSampleCount;
+ const float vsyncRate = sg->vsyncIntervalForAnimationDriver(m_animation_driver);
+
+ // What this means is that the last PS_TIME_SAMPLE_LENGTH frames
+ // average to an elapsed time of t milliseconds, whereas the animation
+ // driver (assuming a single window, vsync-based advancing) assumes a
+ // vsyncRate milliseconds for a frame. If now we see that the elapsed
+ // time is way too low (less than half of the approx. expected value),
+ // then we assume that something is wrong with vsync.
+ //
+ // This will not capture everything. Consider a 144 Hz screen with 6.9
+ // ms vsync rate, the half of that is below the default 5 ms timer of
+ // QWindow::requestUpdate(), so this will not trigger even if the
+ // graphics stack does not throttle. But then the whole workaround is
+ // not that important because the animations advance anyway closer to
+ // what's expected (e.g. advancing as if 6-7 ms passed after ca. 5 ms),
+ // the gap is a lot smaller than with the 60 Hz case (animations
+ // advancing as if 16 ms passed after just ca. 5 ms) The workaround
+ // here is present mainly for virtual machines and other broken
+ // environments, most of which will persumably report a 60 Hz screen.
+
+ const float threshold = vsyncRate * 0.5f;
+ const bool badVSync = t < threshold;
+ if (badVSync && !w->badVSync) {
+ // Once we determine something is wrong with the frame rate, set
+ // the flag for the rest of the lifetime of the window. This is
+ // saner and more deterministic than allowing it to be turned on
+ // and off. (a window resize can take up time, leading to higher
+ // elapsed times, thus unnecessarily starting to switch modes,
+ // while some platforms seem to have advanced logic (and adaptive
+ // refresh rates an whatnot) that can eventually start throttling
+ // an unthrottled window, potentially leading to a continuous
+ // switching of modes back and forth which is not desirable.
+ w->badVSync = true;
+ qCDebug(QSG_LOG_INFO, "Window %p is determined to have broken vsync throttling (%f < %f) "
+ "switching to system timer to drive gui thread animations to remedy this "
+ "(however, render thread animators will likely advance at an incorrect rate).",
+ w->window, t, threshold);
+ startOrStopAnimationTimer();
+ }
+
+ w->psTimeAccumulator = 0.0f;
+ w->psTimeSampleCount = 0;
+ }
+ }
+
const bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
if (profileFrames) {
timer.start();
- const qint64 elapsedSinceLastMs = w->timeBetweenPolishAndSyncs.restart();
qCDebug(QSG_LOG_TIME_RENDERLOOP, "[window %p][gui thread] polishAndSync: start, elapsed since last call: %d ms",
window,
int(elapsedSinceLastMs));
@@ -1553,10 +1627,13 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
emit window->afterAnimating();
+ const QRhiSwapChainProxyData scProxyData =
+ QRhi::updateSwapChainProxyData(QSGRhiSupport::instance()->rhiBackend(), window);
+
qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
w->thread->mutex.lock();
m_lockedForSync = true;
- w->thread->postEvent(new WMSyncEvent(window, inExpose, w->forceRenderPass));
+ w->thread->postEvent(new WMSyncEvent(window, inExpose, w->forceRenderPass, scProxyData));
w->forceRenderPass = false;
qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
@@ -1579,10 +1656,13 @@ void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
QQuickProfiler::SceneGraphPolishAndSyncSync);
Q_TRACE(QSG_animations_entry);
- // Now is the time to advance the regular animations (as we are throttled to
- // vsync due to the wait above), but this is only relevant when there is one
- // single window. With multiple windows m_animation_timer is active, and
- // advance() happens instead in response to a good old timer event, not here.
+ // Now is the time to advance the regular animations (as we are throttled
+ // to vsync due to the wait above), but this is only relevant when there is
+ // one single window. With multiple windows m_animation_timer is active,
+ // and advance() happens instead in response to a good old timer event, not
+ // here. (the above applies only when the QSGAnimationDriver reports
+ // isVSyncDependent() == true, if not then we always use the driver and
+ // just advance here)
if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
m_animation_driver->advance();
@@ -1626,6 +1706,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
switch ((int) e->type()) {
case QEvent::Timer: {
+ Q_ASSERT(sg->isVSyncDependent(m_animation_driver));
QTimerEvent *te = static_cast<QTimerEvent *>(e);
if (te->timerId() == m_animation_timer) {
qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-render thread timer");
@@ -1633,6 +1714,7 @@ bool QSGThreadedRenderLoop::event(QEvent *e)
emit timeToIncubate();
return true;
}
+ break;
}
default:
@@ -1658,7 +1740,7 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
{
qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
Q_ASSERT(w);
if (!w->thread->isRunning())
@@ -1693,14 +1775,14 @@ QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
*/
void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
{
- Window *w = windowFor(m_windows, window);
+ Window *w = windowFor(window);
if (w && w->thread && w->thread->window)
w->thread->postEvent(new WMJobEvent(window, job));
else
delete job;
}
+QT_END_NAMESPACE
+
#include "qsgthreadedrenderloop.moc"
#include "moc_qsgthreadedrenderloop_p.cpp"
-
-QT_END_NAMESPACE