diff options
author | Laszlo Agocs <[email protected]> | 2019-04-23 09:40:59 +0200 |
---|---|---|
committer | Laszlo Agocs <[email protected]> | 2019-07-04 10:44:26 +0200 |
commit | 341ab7708049b1a3f559b76f16393e688951a938 (patch) | |
tree | 0203da53d1adf5025fa85ecc38e0e37bce35c46f /src/quick/scenegraph/qsgthreadedrenderloop.cpp | |
parent | 1f01b44d20471a7f4a5029a4c0049e8296749fef (diff) |
Add the graphics api independent scenegraph port
Opt in via environment variables:
QSG_RHI=1 -> enable using QRhi instead of GL
QSG_RHI_BACKEND -> set to vulkan, metal, d3d11, gl to override the default
(the default is d3d11 on Windows, metal on Mac, gl elsewhere)
Or force a given rhi backend via the existing
QQuickWindow::setSceneGraphBackend().
Otherwise the default behavior is the same as before, the rhi code path
is never active by default.
-no-opengl builds are supported in the sense that they work and default
to the software backend. However, the rhi code path cannot currently be
used in such builds, even though QRhi from qtbase is fully functional
with Vulkan, D3D, or Metal even when qtbase was configured with
-no-opengl. This cannot be utilized by Quick atm due to OpenGL usage
being all over the place in the sources corresponding to the default
backend, and those host the rhi code path as well. This will be cleaned up
hopefully in Qt 6, with the removal all direct OpenGL usage.
Other env.vars.:
QSG_RHI_DEBUG_LAYER=1 -> enable D3D debug or Vulkan validation layer
(assuming the system is set up for this)
QSG_RHI_SHADEREFFECT_DEBUG=1 -> print stuff from ShaderEffect
QSG_SAMPLES=1,2,4,... -> MSAA sample count (but QSurfaceFormat works too)
QT_D3D_ADAPTER_INDEX=0,1,... -> D3D adapter index
QT_VK_PHYSICAL_DEVICE_INDEX=0,1,... -> Vulkan physical device index
QSG_RHI_UINT32_INDEX=1 -> always use uint index data (both
merged/unmerged, convert when needed - with some rhi backends this is
implicit)
QSG_RENDER_LOOP -> to override the render loop as usual. The default
with RHI is threaded for Metal, threaded for Vulkan on Windows, basic
for Vulkan on Linux and Android (to be checked later), while the existing
rules apply for OpenGL.
Not supported when running with QRhi:
- particles
- compressed atlases (though this is transparent to the apps)
- QSGRenderNode
- QQuickRenderControl
- QQuickFramebufferObject
- certain QQuickWindow functionality that depends directly on OpenGL
- anisotropic filtering for textures
- native text may lack some gamma correction
- QSGEngine applicability unclear
- some QML profiler logs may be incorrect or irrelevant
Change-Id: I7822e99ad79e342e4166275da6e9e66498d76521
Reviewed-by: Lars Knoll <[email protected]>
Diffstat (limited to 'src/quick/scenegraph/qsgthreadedrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop.cpp | 449 |
1 files changed, 356 insertions, 93 deletions
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 232ef77324..b0e2e3acbf 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> /* @@ -160,6 +162,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 +187,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 +203,12 @@ public: WMSyncEvent(QQuickWindow *c, bool inExpose, bool force) : WMWindowEvent(c, WM_RequestSync) , size(c->size()) + , dpr(c->effectiveDevicePixelRatio()) , syncInExpose(inExpose) , forceRenderPass(force) {} QSize size; + float dpr; bool syncInExpose; bool forceRenderPass; }; @@ -222,6 +230,12 @@ public: QRunnable *job; }; +class WMReleaseSwapchainEvent : public WMWindowEvent +{ +public: + WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { } +}; + class QSGRenderThreadEventQueue : public QQueue<QEvent *> { public: @@ -271,6 +285,9 @@ public: QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext) : wm(w) , gl(nullptr) + , enableRhi(false) + , rhi(nullptr) + , offscreenSurface(nullptr) , animatorDriver(nullptr) , pendingUpdate(0) , sleeping(false) @@ -290,6 +307,7 @@ public: ~QSGRenderThread() { delete sgrc; + delete offscreenSurface; } void invalidateOpenGL(QQuickWindow *window, bool inDestructor, QOffscreenSurface *backupSurface); @@ -298,8 +316,8 @@ public: 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() { @@ -328,7 +346,10 @@ public: QSGThreadedRenderLoop *wm; QOpenGLContext *gl; + bool enableRhi; + QRhi *rhi; QSGDefaultRenderContext *sgrc; + QOffscreenSurface *offscreenSurface; QAnimationDriver *animatorDriver; @@ -347,6 +368,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 +403,7 @@ bool QSGRenderThread::event(QEvent *e) stopEventProcessing = true; window = se->window; windowSize = se->size; + dpr = se->dpr; pendingUpdate |= SyncRequest; if (se->syncInExpose) { @@ -399,8 +423,8 @@ 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; + invalidateOpenGL(wme->window, wme->inDestructor, wme->needsFallback ? offscreenSurface : nullptr); + active = gl || rhi; Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateOpenGL()", "Thread's active state is not set to false when shutting down"); if (sleeping) stopEventProcessing = true; @@ -427,19 +451,24 @@ 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) { + 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 +482,12 @@ bool QSGRenderThread::event(QEvent *e) WMJobEvent *ce = static_cast<WMJobEvent *>(e); Q_ASSERT(ce->window == window); if (window) { - gl->makeCurrent(window); + if (rhi) { + // ### needs https://codereview.qt-project.org/c/qt/qtbase/+/265231 + //rhi->makeThreadLocalNativeContextCurrent(); + } else { + gl->makeCurrent(window); + } ce->job->run(); delete ce->job; ce->job = nullptr; @@ -462,6 +496,21 @@ bool QSGRenderThread::event(QEvent *e) return true; } + 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; + } + 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 @@ -479,7 +528,7 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateOpenGL()"); - if (!gl) + if (!gl && !rhi) return; if (!window) { @@ -491,7 +540,10 @@ 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)); + if (Q_UNLIKELY(!current)) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- cleanup without an OpenGL context"); } @@ -499,16 +551,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; } @@ -518,13 +573,25 @@ void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); if (inDestructor) delete dd->animationController; - if (current) + 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 +602,34 @@ 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); + if (gl) { + 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) { + QSGDefaultRenderContext::InitParams rcParams; + rcParams.sampleCount = qMax(1, gl->format().samples()); + rcParams.openGLContext = gl; + rcParams.initialSurfacePixelSize = windowSize * dpr; + rcParams.maybeSurface = window; + sgrc->initialize(&rcParams); + } + } + } else { + current = true; } if (current) { QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); @@ -576,14 +654,14 @@ 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::syncAndRender(QImage *grabImage) { bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled(); if (profileFrames) { @@ -600,14 +678,59 @@ void QSGRenderThread::syncAndRender() syncResultedInChanges = false; QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); - bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage; - bool syncRequested = pendingUpdate & SyncRequest; + bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage; + bool syncRequested = (pendingUpdate & SyncRequest) || grabImage; bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; pendingUpdate = 0; + 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) + qDebug("just became exposed"); + cd->swapchainJustBecameRenderable = false; + cd->depthStencilForSwapchain->setPixelSize(effectiveOutputSize); + + cd->depthStencilForSwapchain->build(); + cd->hasActiveSwapchain = cd->swapchain->buildOrResize(); + + cd->hasRenderableSwapchain = cd->hasActiveSwapchain; + if (!cd->hasActiveSwapchain) + qWarning("Failed to build or resize swapchain"); + else + qDebug() << "rhi swapchain size" << effectiveOutputSize; + } + + Q_ASSERT(rhi == cd->rhi); + QRhi::FrameOpResult frameResult = rhi->beginFrame(cd->swapchain); + if (frameResult != QRhi::FrameOpSuccess) { + if (frameResult == QRhi::FrameOpDeviceLost) + qWarning("Device lost"); + 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))); + return; + } + } + if (syncRequested) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync"); - sync(exposeRequested); + sync(exposeRequested, grabImage != nullptr); } #ifndef QSG_NO_RENDER_TIMING if (profileFrames) @@ -616,41 +739,71 @@ 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))); + if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0) { + if (gl) + current = gl->makeCurrent(window); + else + current = true; + } + // 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); + 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; + rhi->endFrame(cd->swapchain, flags); + } 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 +833,8 @@ void QSGRenderThread::syncAndRender() Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame, QQuickProfiler::SceneGraphRenderLoopSwap); + + QSGRhiProfileConnection::instance()->send(rhi); } @@ -723,10 +878,55 @@ void QSGRenderThread::run() QQuickProfiler::registerAnimationCallback(); while (active) { - if (window) { - if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(window)) - sgrc->initialize(gl); + if (enableRhi) { + 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"); + } + } + if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) { + QSGDefaultRenderContext::InitParams rcParams; + rcParams.rhi = rhi; + rcParams.sampleCount = rhiSampleCount; + rcParams.openGLContext = gl; + rcParams.initialSurfacePixelSize = windowSize * 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 + cd->swapchain = rhi->newSwapChain(); + cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), + rhiSampleCount, + QRhiRenderBuffer::UsedWithSwapChainOnly); + cd->swapchain->setWindow(window); + cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); + qDebug("MSAA sample count for the swapchain is %d", rhiSampleCount); + cd->swapchain->setSampleCount(rhiSampleCount); + cd->swapchain->setFlags(flags); + cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor(); + cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain); + } + } 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 * dpr; + rcParams.maybeSurface = window; + sgrc->initialize(&rcParams); + } + } syncAndRender(); } @@ -741,7 +941,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 +1111,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,27 +1204,39 @@ 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; @@ -1033,6 +1281,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) { @@ -1125,23 +1397,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() |