diff options
Diffstat (limited to 'src/quick/scenegraph/qsgthreadedrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop.cpp | 462 |
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 |