diff options
Diffstat (limited to 'src/quick/scenegraph/qsgthreadedrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop.cpp | 555 |
1 files changed, 441 insertions, 114 deletions
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 232ef77324..dc1f97de54 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -57,6 +57,7 @@ #include <QtQuick/private/qsgrenderer_p.h> #include "qsgthreadedrenderloop_p.h" +#include "qsgrhisupport_p.h" #include <private/qquickanimatorcontroller_p.h> #include <private/qquickprofiler_p.h> @@ -66,6 +67,7 @@ #if QT_CONFIG(quick_shadereffect) #include <private/qquickopenglshadereffectnode_p.h> #endif +#include <private/qsgrhishadereffectnode_p.h> #include <private/qsgdefaultrendercontext_p.h> /* @@ -145,10 +147,6 @@ const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1); // (updatePaintNode()) const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2); -// Passed by the RT to itself to trigger another render pass. This is -// typically a result of QQuickWindow::update(). -const QEvent::Type WM_RequestRepaint = QEvent::Type(QEvent::User + 3); - // 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); @@ -160,6 +158,10 @@ 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) { for (int i=0; i<list.size(); ++i) { @@ -181,14 +183,14 @@ public: class WMTryReleaseEvent : public WMWindowEvent { public: - WMTryReleaseEvent(QQuickWindow *win, bool destroy, QOffscreenSurface *fallback) + WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface) : WMWindowEvent(win, WM_TryRelease) , inDestructor(destroy) - , fallbackSurface(fallback) + , needsFallback(needsFallbackSurface) {} bool inDestructor; - QOffscreenSurface *fallbackSurface; + bool needsFallback; }; class WMSyncEvent : public WMWindowEvent @@ -197,10 +199,12 @@ public: WMSyncEvent(QQuickWindow *c, bool inExpose, bool force) : WMWindowEvent(c, WM_RequestSync) , size(c->size()) + , dpr(float(c->effectiveDevicePixelRatio())) , syncInExpose(inExpose) , forceRenderPass(force) {} QSize size; + float dpr; bool syncInExpose; bool forceRenderPass; }; @@ -222,6 +226,12 @@ public: QRunnable *job; }; +class WMReleaseSwapchainEvent : public WMWindowEvent +{ +public: + WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { } +}; + class QSGRenderThreadEventQueue : public QQueue<QEvent *> { public: @@ -271,6 +281,9 @@ public: QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext) : wm(w) , gl(nullptr) + , enableRhi(false) + , rhi(nullptr) + , offscreenSurface(nullptr) , animatorDriver(nullptr) , pendingUpdate(0) , sleeping(false) @@ -290,16 +303,16 @@ public: ~QSGRenderThread() { delete sgrc; + delete offscreenSurface; } - void invalidateOpenGL(QQuickWindow *window, bool inDestructor, QOffscreenSurface *backupSurface); - void initializeOpenGL(); + void invalidateGraphics(QQuickWindow *window, bool inDestructor, QOffscreenSurface *backupSurface); bool event(QEvent *) override; void run() override; - void syncAndRender(); - void sync(bool inExpose); + void syncAndRender(QImage *grabImage = nullptr); + void sync(bool inExpose, bool inGrab); void requestRepaint() { @@ -326,9 +339,15 @@ public: ExposeRequest = 0x04 | RepaintRequest | SyncRequest }; + void ensureRhi(); + void handleDeviceLoss(); + QSGThreadedRenderLoop *wm; QOpenGLContext *gl; + bool enableRhi; + QRhi *rhi; QSGDefaultRenderContext *sgrc; + QOffscreenSurface *offscreenSurface; QAnimationDriver *animatorDriver; @@ -347,6 +366,8 @@ public: QQuickWindow *window; // Will be 0 when window is not exposed QSize windowSize; + float dpr = 1; + int rhiSampleCount = 1; // Local event queue stuff... bool stopEventProcessing; @@ -380,6 +401,7 @@ bool QSGRenderThread::event(QEvent *e) stopEventProcessing = true; window = se->window; windowSize = se->size; + dpr = se->dpr; pendingUpdate |= SyncRequest; if (se->syncInExpose) { @@ -399,9 +421,9 @@ bool QSGRenderThread::event(QEvent *e) WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e); if (!window || wme->inDestructor) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- setting exit flag and invalidating OpenGL"); - invalidateOpenGL(wme->window, wme->inDestructor, wme->fallbackSurface); - active = gl; - Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateOpenGL()", "Thread's active state is not set to false when shutting down"); + invalidateGraphics(wme->window, wme->inDestructor, wme->needsFallback ? offscreenSurface : nullptr); + active = gl || rhi; + Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateGraphics()", "Thread's active state is not set to false when shutting down"); if (sleeping) stopEventProcessing = true; } else { @@ -427,19 +449,25 @@ bool QSGRenderThread::event(QEvent *e) Q_ASSERT(ce->window == window || !window); mutex.lock(); if (ce->window) { - gl->makeCurrent(ce->window); - - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync scene graph"); - QQuickWindowPrivate *d = QQuickWindowPrivate::get(ce->window); - d->syncSceneGraph(); - sgrc->endSync(); - - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering scene graph"); - QQuickWindowPrivate::get(ce->window)->renderSceneGraph(ce->window->size()); - - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- grabbing result"); - bool alpha = ce->window->format().alphaBufferSize() > 0 && ce->window->color().alpha() != 255; - *ce->image = qt_gl_read_framebuffer(windowSize * ce->window->effectiveDevicePixelRatio(), alpha, alpha); + const bool alpha = ce->window->format().alphaBufferSize() > 0 && ce->window->color().alpha() != 255; + const QSize readbackSize = windowSize * ce->window->effectiveDevicePixelRatio(); + if (rhi) { + rhi->makeThreadLocalNativeContextCurrent(); + syncAndRender(ce->image); + } else { + gl->makeCurrent(ce->window); + + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync scene graph"); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(ce->window); + d->syncSceneGraph(); + sgrc->endSync(); + + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering scene graph"); + QQuickWindowPrivate::get(ce->window)->renderSceneGraph(ce->window->size()); + + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- grabbing result"); + *ce->image = qt_gl_read_framebuffer(readbackSize, alpha, alpha); + } ce->image->setDevicePixelRatio(ce->window->effectiveDevicePixelRatio()); } qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- waking gui to handle result"); @@ -453,7 +481,10 @@ bool QSGRenderThread::event(QEvent *e) WMJobEvent *ce = static_cast<WMJobEvent *>(e); Q_ASSERT(ce->window == window); if (window) { - gl->makeCurrent(window); + if (rhi) + rhi->makeThreadLocalNativeContextCurrent(); + else + gl->makeCurrent(window); ce->job->run(); delete ce->job; ce->job = nullptr; @@ -462,12 +493,20 @@ bool QSGRenderThread::event(QEvent *e) return true; } - case WM_RequestRepaint: - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestPaint"); - // When GUI posts this event, it is followed by a polishAndSync, so we mustn't - // exit the event loop yet. - pendingUpdate |= RepaintRequest; - break; + case WM_ReleaseSwapchain: { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_ReleaseSwapchain"); + WMReleaseSwapchainEvent *ce = static_cast<WMReleaseSwapchainEvent *>(e); + // forget about 'window' here that may be null when already unexposed + Q_ASSERT(ce->window); + mutex.lock(); + if (ce->window) { + wm->releaseSwapchain(ce->window); + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- swapchain released"); + } + waitCondition.wakeOne(); + mutex.unlock(); + return true; + } default: break; @@ -475,11 +514,11 @@ bool QSGRenderThread::event(QEvent *e) return QThread::event(e); } -void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, QOffscreenSurface *fallback) +void QSGRenderThread::invalidateGraphics(QQuickWindow *window, bool inDestructor, QOffscreenSurface *fallback) { - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateOpenGL()"); + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateGraphics()"); - if (!gl) + if (!gl && !rhi) return; if (!window) { @@ -491,7 +530,12 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, bool wipeSG = inDestructor || !window->isPersistentSceneGraph(); bool wipeGL = inDestructor || (wipeSG && !window->isPersistentOpenGLContext()); - bool current = gl->makeCurrent(fallback ? static_cast<QSurface *>(fallback) : static_cast<QSurface *>(window)); + bool current = true; + if (gl) + current = gl->makeCurrent(fallback ? static_cast<QSurface *>(fallback) : static_cast<QSurface *>(window)); + else if (rhi) + rhi->makeThreadLocalNativeContextCurrent(); + if (Q_UNLIKELY(!current)) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- cleanup without an OpenGL context"); } @@ -499,16 +543,19 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window); #if QT_CONFIG(quick_shadereffect) + QSGRhiShaderEffectNode::cleanupMaterialTypeCache(); +#if QT_CONFIG(opengl) if (current) QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache(); #endif +#endif // The canvas nodes must be cleaned up regardless if we are in the destructor.. if (wipeSG) { dd->cleanupNodesOnShutdown(); } else { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup"); - if (current) + if (current && gl) gl->doneCurrent(); return; } @@ -517,14 +564,26 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, QCoreApplication::processEvents(); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); if (inDestructor) - delete dd->animationController; - if (current) + dd->animationController.reset(); + if (current && gl) gl->doneCurrent(); qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidating scene graph"); if (wipeGL) { + if (dd->swapchain) { + if (window->handle()) { + // We get here when exiting via QCoreApplication::quit() instead of + // through QWindow::close(). + wm->releaseSwapchain(window); + } else { + qWarning("QSGThreadedRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.", + window, dd->swapchain); + } + } delete gl; gl = nullptr; + delete rhi; + rhi = nullptr; qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidated OpenGL"); } else { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup"); @@ -535,23 +594,41 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, Enters the mutex lock to make sure GUI is blocking and performs sync, then wakes GUI. */ -void QSGRenderThread::sync(bool inExpose) +void QSGRenderThread::sync(bool inExpose, bool inGrab) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()"); - mutex.lock(); + if (!inGrab) + mutex.lock(); Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked..."); - bool current = false; - if (windowSize.width() > 0 && windowSize.height() > 0) - current = gl->makeCurrent(window); - // Check for context loss. - if (!current && !gl->isValid()) { - QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown(); - sgrc->invalidate(); - current = gl->create() && gl->makeCurrent(window); - if (current) - sgrc->initialize(gl); + bool current = true; + if (gl) { + if (windowSize.width() > 0 && windowSize.height() > 0) + current = gl->makeCurrent(window); + else + current = false; + // Check for context loss. + if (!current && !gl->isValid()) { + QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown(); + sgrc->invalidate(); + current = gl->create() && gl->makeCurrent(window); + if (current) { + QSGDefaultRenderContext::InitParams rcParams; + rcParams.sampleCount = qMax(1, gl->format().samples()); + rcParams.openGLContext = gl; + rcParams.initialSurfacePixelSize = windowSize * qreal(dpr); + rcParams.maybeSurface = window; + sgrc->initialize(&rcParams); + } + } + } else { + // 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(); } if (current) { QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); @@ -576,14 +653,27 @@ void QSGRenderThread::sync(bool inExpose) qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window has bad size, sync aborted"); } - if (!inExpose) { + if (!inExpose && !inGrab) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui"); waitCondition.wakeOne(); mutex.unlock(); } } -void QSGRenderThread::syncAndRender() +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); + delete rhi; + rhi = nullptr; +} + +void QSGRenderThread::syncAndRender(QImage *grabImage) { bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled(); if (profileFrames) { @@ -600,14 +690,78 @@ void QSGRenderThread::syncAndRender() syncResultedInChanges = false; QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); - bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage; - bool syncRequested = pendingUpdate & SyncRequest; - bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; + const bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage; + const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage; + const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; pendingUpdate = 0; + const bool grabRequested = grabImage != nullptr; + + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); + // 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) { + // 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 + // unexpose. With Vulkan on Windows for example attempting to render + // leads to failures at this stage since the surface size is already 0. + if (effectiveOutputSize.isEmpty()) + return; + + const QSize previousOutputSize = cd->swapchain->currentPixelSize(); + if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) { + if (cd->swapchainJustBecameRenderable) + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed"); + + cd->hasActiveSwapchain = cd->swapchain->buildOrResize(); + if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) { + handleDeviceLoss(); + QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest))); + return; + } + + cd->swapchainJustBecameRenderable = false; + cd->hasRenderableSwapchain = cd->hasActiveSwapchain; + + if (!cd->hasActiveSwapchain) + qWarning("Failed to build or resize swapchain"); + else + qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << cd->swapchain->currentPixelSize(); + } + + 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); + if (frameResult != QRhi::FrameOpSuccess) { + if (frameResult == QRhi::FrameOpDeviceLost) + handleDeviceLoss(); + else if (frameResult == QRhi::FrameOpError) + qWarning("Failed to start frame"); + // try again later + if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate) + QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest))); + // Before returning we need to ensure the same wake up logic that + // would have happened if beginFrame() had suceeded. + if (exposeRequested) { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui"); + waitCondition.wakeOne(); + mutex.unlock(); + } else if (syncRequested && !grabRequested) { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui like sync would do"); + mutex.lock(); + waitCondition.wakeOne(); + mutex.unlock(); + } + return; + } + } if (syncRequested) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync"); - sync(exposeRequested); + sync(exposeRequested, grabRequested); } #ifndef QSG_NO_RENDER_TIMING if (profileFrames) @@ -616,41 +770,80 @@ void QSGRenderThread::syncAndRender() Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopSync); - if (!syncResultedInChanges && !repaintRequested && sgrc->isValid()) { - qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- no changes, render aborted"); - int waitTime = vsyncDelta - (int) waitTimer.elapsed(); - if (waitTime > 0) - msleep(waitTime); - return; + if (!syncResultedInChanges && !repaintRequested && sgrc->isValid() && !grabImage) { + if (gl || !rhi->isRecordingFrame()) { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- no changes, render aborted"); + int waitTime = vsyncDelta - (int) waitTimer.elapsed(); + if (waitTime > 0) + msleep(waitTime); + return; + } } qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering started"); - if (animatorDriver->isRunning()) { + if (animatorDriver->isRunning() && !grabImage) { d->animationController->lock(); animatorDriver->advance(); d->animationController->unlock(); } - bool current = false; - if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0) - current = gl->makeCurrent(window); - // Check for context loss. - if (!current && !gl->isValid()) { - // Cannot do anything here because gui is not locked. Request a new - // sync+render round on the gui thread and let the sync handle it. - QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest))); + bool current = true; + if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0) { + if (gl) + current = gl->makeCurrent(window); + else if (rhi) + rhi->makeThreadLocalNativeContextCurrent(); + } else { + current = false; + } + // Check for context loss (GL, RHI case handled after the beginFrame() above) + if (gl) { + if (!current && !gl->isValid()) { + // Cannot do anything here because gui is not locked. Request a new + // sync+render round on the gui thread and let the sync handle it. + QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest))); + } } if (current) { - d->renderSceneGraph(windowSize); + d->renderSceneGraph(windowSize, rhi ? cd->swapchain->currentPixelSize() : QSize()); + if (profileFrames) renderTime = threadTimer.nsecsElapsed(); Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopRender); - if (!d->customRenderStage || !d->customRenderStage->swap()) - gl->swapBuffers(window); - d->fireFrameSwapped(); + + // With the rhi grabs can only be done by adding a readback and then + // blocking in a real frame. The legacy GL path never gets here with + // grabs as it rather invokes sync/render directly without going + // through syncAndRender(). + if (grabImage) { + Q_ASSERT(rhi && !gl && cd->swapchain); + *grabImage = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain); + } + + if (cd->swapchain) { + QRhi::EndFrameFlags flags = 0; + if (grabImage) + flags |= QRhi::SkipPresent; + QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain, flags); + 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 { + if (!cd->customRenderStage || !cd->customRenderStage->swap()) + gl->swapBuffers(window); + } + + if (!grabImage) + d->fireFrameSwapped(); + } else { Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopSync, 1); @@ -680,6 +873,8 @@ void QSGRenderThread::syncAndRender() Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopSwap); + + QSGRhiProfileConnection::instance()->send(rhi); } @@ -714,6 +909,57 @@ void QSGRenderThread::processEventsAndWaitForMore() qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEventsAndWaitForMore()"); } +void QSGRenderThread::ensureRhi() +{ + if (!rhi) { + QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); + rhi = rhiSupport->createRhi(window, offscreenSurface); + if (rhi) { + rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi); + if (rhiSupport->isProfilingRequested()) + QSGRhiProfileConnection::instance()->initialize(rhi); // ### this breaks down with multiple windows + } else { + qWarning("Failed to create QRhi on the render thread; scenegraph is not functional"); + return; + } + } + if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) { + rhi->makeThreadLocalNativeContextCurrent(); + QSGDefaultRenderContext::InitParams rcParams; + rcParams.rhi = rhi; + rcParams.sampleCount = rhiSampleCount; + rcParams.openGLContext = nullptr; + rcParams.initialSurfacePixelSize = windowSize * qreal(dpr); + rcParams.maybeSurface = window; + sgrc->initialize(&rcParams); + } + QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); + if (rhi && !cd->swapchain) { + cd->rhi = rhi; + QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab + // 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; + if (alpha) + flags |= QRhiSwapChain::SurfaceHasPreMulAlpha; + cd->swapchain = rhi->newSwapChain(); + cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), + rhiSampleCount, + QRhiRenderBuffer::UsedWithSwapChainOnly); + cd->swapchain->setWindow(window); + cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); + qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s.", + rhiSampleCount, alpha ? "yes" : "no"); + cd->swapchain->setSampleCount(rhiSampleCount); + cd->swapchain->setFlags(flags); + cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor(); + cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain); + } +} + void QSGRenderThread::run() { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()"); @@ -723,11 +969,26 @@ void QSGRenderThread::run() QQuickProfiler::registerAnimationCallback(); while (active) { +#ifdef Q_OS_DARWIN + QMacAutoReleasePool frameReleasePool; +#endif if (window) { - if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(window)) - sgrc->initialize(gl); - syncAndRender(); + if (enableRhi) { + ensureRhi(); + if (rhi) + syncAndRender(); + } else { + if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(window)) { + QSGDefaultRenderContext::InitParams rcParams; + rcParams.sampleCount = qMax(1, gl->format().samples()); + rcParams.openGLContext = gl; + rcParams.initialSurfacePixelSize = windowSize * qreal(dpr); + rcParams.maybeSurface = window; + sgrc->initialize(&rcParams); + } + syncAndRender(); + } } processEvents(); @@ -741,7 +1002,7 @@ void QSGRenderThread::run() } } - Q_ASSERT_X(!gl, "QSGRenderThread::run()", "The OpenGL context should be cleaned up before exiting the render thread..."); + Q_ASSERT_X(!gl && !rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread..."); qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed"); @@ -911,12 +1172,48 @@ void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window) qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window; } +void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window) +{ + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); + delete wd->rpDescForSwapchain; + wd->rpDescForSwapchain = nullptr; + delete wd->swapchain; + wd->swapchain = nullptr; + delete wd->depthStencilForSwapchain; + wd->depthStencilForSwapchain = nullptr; + wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false; +} void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window) { qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window; + + // This is tricker than used to be. We want to detect having an empty + // surface size (which may be the case even when window->size() is + // non-empty, on some platforms with some graphics APIs!) as well as the + // case when the window just became "newly exposed" (e.g. after a + // minimize-restore on Windows, or when switching between fully obscured - + // not fully obscured on macOS) + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); + if (!window->isExposed()) + wd->hasRenderableSwapchain = false; + + bool skipThisExpose = false; + if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) { + wd->hasRenderableSwapchain = false; + skipThisExpose = true; + } + + if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain + && !wd->swapchain->surfacePixelSize().isEmpty()) + { + wd->hasRenderableSwapchain = true; + wd->swapchainJustBecameRenderable = true; + } + if (window->isExposed()) { - handleExposure(window); + if (!skipThisExpose) + handleExposure(window); } else { Window *w = windowFor(m_windows, window); if (w) @@ -968,30 +1265,43 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) // Start render thread if it is not running if (!w->thread->isRunning()) { - qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread"); - if (!w->thread->gl) { - w->thread->gl = new QOpenGLContext(); - if (qt_gl_global_share_context()) - w->thread->gl->setShareContext(qt_gl_global_share_context()); - w->thread->gl->setFormat(w->window->requestedFormat()); - w->thread->gl->setScreen(w->window->screen()); - if (!w->thread->gl->create()) { - const bool isEs = w->thread->gl->isOpenGLES(); - delete w->thread->gl; - w->thread->gl = nullptr; - handleContextCreationFailure(w->window, isEs); - return; + w->thread->enableRhi = QSGRhiSupport::instance()->isRhiEnabled(); + if (w->thread->enableRhi) { + if (!w->thread->rhi) { + QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); + w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window); + window->installEventFilter(this); } + } else { + if (!w->thread->gl) { + w->thread->gl = new QOpenGLContext(); + if (qt_gl_global_share_context()) + w->thread->gl->setShareContext(qt_gl_global_share_context()); + w->thread->gl->setFormat(w->window->requestedFormat()); + w->thread->gl->setScreen(w->window->screen()); + if (!w->thread->gl->create()) { + const bool isEs = w->thread->gl->isOpenGLES(); + delete w->thread->gl; + w->thread->gl = nullptr; + handleContextCreationFailure(w->window, isEs); + return; + } + + QQuickWindowPrivate::get(w->window)->fireOpenGLContextCreated(w->thread->gl); - QQuickWindowPrivate::get(w->window)->fireOpenGLContextCreated(w->thread->gl); + w->thread->gl->moveToThread(w->thread); + qCDebug(QSG_LOG_RENDERLOOP, "- OpenGL context created"); - w->thread->gl->moveToThread(w->thread); - qCDebug(QSG_LOG_RENDERLOOP, "- OpenGL context created"); + w->thread->offscreenSurface = new QOffscreenSurface(); + w->thread->offscreenSurface->setFormat(w->actualWindowFormat); + w->thread->offscreenSurface->create(); + } } - QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController; + QQuickAnimatorController *controller + = QQuickWindowPrivate::get(w->window)->animationController.get(); if (controller->thread() != w->thread) controller->moveToThread(w->thread); @@ -1033,6 +1343,30 @@ void QSGThreadedRenderLoop::handleObscurity(Window *w) startOrStopAnimationTimer(); } +bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { + QQuickWindow *window = qobject_cast<QQuickWindow *>(watched); + if (window) { + Window *w = windowFor(m_windows, window); + if (w) { + w->thread->mutex.lock(); + w->thread->postEvent(new WMReleaseSwapchainEvent(window)); + w->thread->waitCondition.wait(&w->thread->mutex); + w->thread->mutex.unlock(); + } + window->removeEventFilter(this); + } + } + break; + default: + break; + } + return QObject::eventFilter(watched, event); +} void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window) { @@ -1062,6 +1396,8 @@ void QSGThreadedRenderLoop::maybeUpdate(Window *w) return; QThread *current = QThread::currentThread(); + if (current == w->thread && w->thread->rhi && w->thread->rhi->isDeviceLost()) + return; if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) { qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()"; return; @@ -1125,23 +1461,14 @@ void QSGThreadedRenderLoop::releaseResources(Window *w, bool inDestructor) QQuickWindow *window = w->window; // The platform window might have been destroyed before - // hide/release/windowDestroyed is called, so we need to have a - // fallback surface to perform the cleanup of the scene graph - // and the OpenGL resources. - // QOffscreenSurface must be created on the GUI thread, so we - // create it here and pass it on to QSGRenderThread::invalidateGL() - QOffscreenSurface *fallback = nullptr; - if (!window->handle()) { - qCDebug(QSG_LOG_RENDERLOOP, "- using fallback surface"); - fallback = new QOffscreenSurface(); - fallback->setFormat(w->actualWindowFormat); - fallback->create(); - } + // hide/release/windowDestroyed is called, so we may need to have a + // fallback surface to perform the cleanup of the scene graph and the + // OpenGL resources. QOffscreenSurface must be created on the GUI + // thread so that is done for us already. qCDebug(QSG_LOG_RENDERLOOP, "- posting release request to render thread"); - w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, fallback)); + w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, window->handle() == nullptr)); w->thread->waitCondition.wait(&w->thread->mutex); - delete fallback; // Avoid a shutdown race condition. // If SG is invalidated and 'active' becomes false, the thread's run() |