diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/resources/qresourcemanager_p.h | 13 | ||||
-rw-r--r-- | src/extras/defaults/qt3dwindow.cpp | 5 | ||||
-rw-r--r-- | src/plugins/renderers/opengl/renderer/renderer.cpp | 10 | ||||
-rw-r--r-- | src/plugins/renderers/opengl/renderer/renderer_p.h | 9 | ||||
-rw-r--r-- | src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp | 109 | ||||
-rw-r--r-- | src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h | 13 | ||||
-rw-r--r-- | src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp | 3 | ||||
-rw-r--r-- | src/plugins/renderers/rhi/renderer/renderer.cpp | 153 | ||||
-rw-r--r-- | src/plugins/renderers/rhi/renderer/renderer_p.h | 15 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3ditem.cpp | 2 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer.cpp | 547 | ||||
-rw-r--r-- | src/quick3d/imports/scene3d/scene3drenderer_p.h | 85 | ||||
-rw-r--r-- | src/render/backend/abstractrenderer_p.h | 15 | ||||
-rw-r--r-- | src/render/frontend/qrenderapi.h | 1 | ||||
-rw-r--r-- | src/render/frontend/qrenderaspect.cpp | 23 | ||||
-rw-r--r-- | src/render/frontend/qrenderaspect_p.h | 5 |
16 files changed, 706 insertions, 302 deletions
diff --git a/src/core/resources/qresourcemanager_p.h b/src/core/resources/qresourcemanager_p.h index 308036fd9..82e8ad17f 100644 --- a/src/core/resources/qresourcemanager_p.h +++ b/src/core/resources/qresourcemanager_p.h @@ -441,6 +441,19 @@ public: Allocator::releaseResource(handle); } + // Releases all resources referenced by a key + // Resources allocated manually with just a handle aren't releases + void releaseAllResources() + { + typename LockingPolicy<QResourceManager>::WriteLocker lock(this); + // Make a copy as releaseResource removes the entry in m_activeHanldes + const std::vector<Handle> activeHandles = Allocator::activeHandles(); + for (const Handle &h : activeHandles) + Allocator::releaseResource(h); + // Clear Key to Handle Map + m_keyToHandleMap.clear(); + } + protected: QHash<KeyType, Handle > m_keyToHandleMap; diff --git a/src/extras/defaults/qt3dwindow.cpp b/src/extras/defaults/qt3dwindow.cpp index 34264a89f..ffa8d3a4f 100644 --- a/src/extras/defaults/qt3dwindow.cpp +++ b/src/extras/defaults/qt3dwindow.cpp @@ -262,7 +262,11 @@ void setupWindowSurface(QWindow *window, Qt3DRender::API api) noexcept api = Qt3DRender::API::DirectX; } else if (userRequestedApi == QByteArrayLiteral("null")) { api = Qt3DRender::API::Null; + } else if (userRequestedApi == QByteArrayLiteral("auto")) { + api = Qt3DRender::API::RHI; } + } else { + api = Qt3DRender::API::RHI; } // We have to set the environment so that the backend is able to read it. @@ -294,6 +298,7 @@ void setupWindowSurface(QWindow *window, Qt3DRender::API api) noexcept break; } #endif + case Qt3DRender::API::RHI: default: break; } diff --git a/src/plugins/renderers/opengl/renderer/renderer.cpp b/src/plugins/renderers/opengl/renderer/renderer.cpp index b90b08ac1..407d0d25c 100644 --- a/src/plugins/renderers/opengl/renderer/renderer.cpp +++ b/src/plugins/renderers/opengl/renderer/renderer.cpp @@ -315,6 +315,16 @@ void Renderer::dumpInfo() const qDebug() << *textureImageManager; } +void Renderer::setRenderDriver(AbstractRenderer::RenderDriver driver) +{ + m_driver = driver; +} + +AbstractRenderer::RenderDriver Renderer::renderDriver() const +{ + return m_driver; +} + qint64 Renderer::time() const { return m_time; diff --git a/src/plugins/renderers/opengl/renderer/renderer_p.h b/src/plugins/renderers/opengl/renderer/renderer_p.h index 37be7642b..1d12e5039 100644 --- a/src/plugins/renderers/opengl/renderer/renderer_p.h +++ b/src/plugins/renderers/opengl/renderer/renderer_p.h @@ -172,6 +172,9 @@ public: void dumpInfo() const override; API api() const override { return Qt3DRender::API::OpenGL; } + void setRenderDriver(RenderDriver driver) override; + RenderDriver renderDriver() const override; + qint64 time() const override; void setTime(qint64 time) override; void setJobsInLastFrame(int jobsInLastFrame) override; @@ -258,8 +261,11 @@ public: bool requiresVAOAttributeUpdate(Geometry *geometry, const RenderCommand *command) const; - // For Scene2D rendering + // For Scene3D/Scene2D rendering void setOpenGLContext(QOpenGLContext *context) override; + void setRHIContext(QRhi *) override {}; + void setDefaultRHIRenderTarget(QRhiRenderTarget *) override {}; + void setRHICommandBuffer(QRhiCommandBuffer *) override {}; bool accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture, QMutex **lock, @@ -410,6 +416,7 @@ private: QMetaObject::Connection m_contextConnection; RendererCache<RenderCommand> m_cache; bool m_shouldSwapBuffers; + RenderDriver m_driver = RenderDriver::Qt3D; std::vector<FrameGraphNode *> m_frameGraphLeaves; QScreen *m_screen = nullptr; diff --git a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp index 276d71348..17119e504 100644 --- a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext.cpp @@ -499,7 +499,8 @@ static QShader::Stage rhiShaderStage(QShaderProgram::ShaderType type) noexcept } // anonymous SubmissionContext::SubmissionContext() - : m_ownCurrent(true), + : m_ownsRhiCtx(false), + m_drivenExternally(false), m_id(nextFreeContextId()), m_surface(nullptr), m_renderTargetFormat(QAbstractTexture::NoFormat), @@ -509,7 +510,9 @@ SubmissionContext::SubmissionContext() m_initialized(false), m_rhi(nullptr), m_currentSwapChain(nullptr), - m_currentRenderPassDescriptor(nullptr) + m_currentRenderPassDescriptor(nullptr), + m_defaultRenderTarget(nullptr), + m_defaultCommandBuffer(nullptr) #ifndef QT_NO_OPENGL , m_fallbackSurface(nullptr) @@ -534,9 +537,15 @@ SubmissionContext::~SubmissionContext() void SubmissionContext::initialize() { m_initialized = true; - // m_textureContext.initialize(this); - Qt3DRender::API requestedApi = Qt3DRender::API::OpenGL; + // If the RHI instance was already set (Scene3D) + // no point in continuing below; + if (m_rhi) + return; + + m_ownsRhiCtx = true; + + Qt3DRender::API requestedApi = Qt3DRender::API::RHI; const auto userRequestedApi = qgetenv("QT3D_RHI_DEFAULT_API").toLower(); if (!userRequestedApi.isEmpty()) { if (userRequestedApi == QByteArrayLiteral("opengl")) { @@ -552,6 +561,19 @@ void SubmissionContext::initialize() } } + // If nothing specified, deduce best backend API based on platform + if (requestedApi == Qt3DRender::API::RHI) { +#if defined(Q_OS_WIN) + requestedApi = Qt3DRender::API::DirectX; +#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) + requestedApi = Qt3DRender::API::Metal; +#elif QT_CONFIG(opengl) + requestedApi = Qt3DRender::API::OpenGL; +#else + requestedApi = Qt3DRender::API::Vulkan; +#endif + } + QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers; #if QT_CONFIG(vulkan) @@ -601,31 +623,15 @@ void SubmissionContext::initialize() Q_ASSERT(m_rhi != nullptr); } -void SubmissionContext::resolveRenderTargetFormat() +void SubmissionContext::setDrivenExternally(bool drivenExternally) { - RHI_UNIMPLEMENTED; + Q_ASSERT(!m_initialized); + m_drivenExternally = drivenExternally; +} - //* const QSurfaceFormat format = m_gl->format(); - //* const uint a = (format.alphaBufferSize() == -1) ? 0 : format.alphaBufferSize(); - //* const uint r = format.redBufferSize(); - //* const uint g = format.greenBufferSize(); - //* const uint b = format.blueBufferSize(); - //* - //* #define RGBA_BITS(r,g,b,a) (r | (g << 6) | (b << 12) | (a << 18)) - //* - //* const uint bits = RGBA_BITS(r,g,b,a); - //* switch (bits) { - //* case RGBA_BITS(8,8,8,8): - //* m_renderTargetFormat = QAbstractTexture::RGBA8_UNorm; - //* break; - //* case RGBA_BITS(8,8,8,0): - //* m_renderTargetFormat = QAbstractTexture::RGB8_UNorm; - //* break; - //* case RGBA_BITS(5,6,5,0): - //* m_renderTargetFormat = QAbstractTexture::R5G6B5; - //* break; - //* } - //* #undef RGBA_BITS +bool SubmissionContext::drivenExternally() const +{ + return m_drivenExternally; } bool SubmissionContext::beginDrawing(QSurface *surface) @@ -634,11 +640,15 @@ bool SubmissionContext::beginDrawing(QSurface *surface) m_surface = surface; - // TODO: cache surface format somewhere rather than doing this every time render surface changes - resolveRenderTargetFormat(); - Q_ASSERT(isInitialized()); + // In the Scene3D case it does not make sense to create SwapChains as we + // can only record commands that will be used against QtQuick default's + // swap chain. Also when rendering through Scene3D, QtQuick takes care of + // beginning the frame + if (m_drivenExternally) + return true; + // Check if we have a swapchain for the Window, if not create one SwapChainInfo *swapChainInfo = swapChainForSurface(surface); QRhiSwapChain *swapChain = swapChainInfo->swapChain; @@ -653,16 +663,17 @@ bool SubmissionContext::beginDrawing(QSurface *surface) m_currentSwapChain = swapChain; m_currentRenderPassDescriptor = swapChainInfo->renderPassDescriptor; - // Begin Frame const auto success = m_rhi->beginFrame(m_currentSwapChain); - return success == QRhi::FrameOpSuccess; } void SubmissionContext::endDrawing(bool swapBuffers) { Q_UNUSED(swapBuffers); - m_rhi->endFrame(m_currentSwapChain, {}); + const bool shouldEndFrame = !m_drivenExternally; + // When rendering through Scene3D, QtQuick takes care of ending the frame + if (shouldEndFrame) + m_rhi->endFrame(m_currentSwapChain, {}); } QImage SubmissionContext::readFramebuffer(const QRect &rect) @@ -836,7 +847,9 @@ void SubmissionContext::releaseResources() it = m_swapChains.erase(it); } - delete m_rhi; + // Only destroy RHI context if we created it + if (m_ownsRhiCtx) + delete m_rhi; m_rhi = nullptr; #ifndef QT_NO_OPENGL @@ -852,6 +865,24 @@ void SubmissionContext::releaseResources() //* } } +// Called when Scene3D is used +void SubmissionContext::setRHIContext(QRhi *ctx) +{ + m_rhi = ctx; +} + +// Scene3D +void SubmissionContext::setDefaultRenderTarget(QRhiRenderTarget *target) +{ + m_defaultRenderTarget = target; +} + +// Scene3D +void SubmissionContext::setCommandBuffer(QRhiCommandBuffer *commandBuffer) +{ + m_defaultCommandBuffer = commandBuffer; +} + void SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments) { @@ -1149,6 +1180,11 @@ SubmissionContext::SwapChainInfo *SubmissionContext::swapChainForSurface(QSurfac QRhiCommandBuffer *SubmissionContext::currentFrameCommandBuffer() const { + // When rendering with Scene3D, we have to use the Command Buffer provided by QtQuick + // When Qt3D renders on its own, we use our own Command Buffer which we can + // retrieve from the current Swap Chain + if (m_defaultCommandBuffer) + return m_defaultCommandBuffer; return m_currentSwapChain->currentFrameCommandBuffer(); } @@ -1157,6 +1193,11 @@ QRhiRenderTarget *SubmissionContext::currentFrameRenderTarget() const return m_currentSwapChain->currentFrameRenderTarget(); } +QRhiRenderTarget *SubmissionContext::defaultRenderTarget() const +{ + return m_defaultRenderTarget; +} + QRhiSwapChain *SubmissionContext::currentSwapChain() const { return m_currentSwapChain; diff --git a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h index 2d55ae382..e164564a4 100644 --- a/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h +++ b/src/plugins/renderers/rhi/graphicshelpers/submissioncontext_p.h @@ -124,10 +124,16 @@ public: int id() const; // unique, small integer ID of this context void setRenderer(Renderer *renderer) { m_renderer = renderer; } + void setDrivenExternally(bool drivenExternally); + bool drivenExternally() const; + bool beginDrawing(QSurface *surface); void endDrawing(bool swapBuffers); void releaseResources(); void setOpenGLContext(QOpenGLContext *ctx); + void setRHIContext(QRhi *ctx); + void setDefaultRenderTarget(QRhiRenderTarget *target); + void setCommandBuffer(QRhiCommandBuffer *commandBuffer); bool isInitialized() const { return m_initialized; } const GraphicsApiFilterData *contextInfo() const; @@ -186,6 +192,7 @@ public: QRhi *rhi() const { return m_rhi; } QRhiCommandBuffer *currentFrameCommandBuffer() const; QRhiRenderTarget *currentFrameRenderTarget() const; + QRhiRenderTarget *defaultRenderTarget() const; QRhiRenderPassDescriptor *currentRenderPassDescriptor() const; QRhiSwapChain *currentSwapChain() const; QSurfaceFormat format() const noexcept; @@ -194,7 +201,6 @@ private: // FBO void bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments); void activateDrawBuffers(const AttachmentPack &attachments); - void resolveRenderTargetFormat(); // Buffers HRHIBuffer createRHIBufferFor(Buffer *buffer); void uploadDataToRHIBuffer(Buffer *buffer, RHIBuffer *b); @@ -204,7 +210,8 @@ private: // States void applyState(const StateVariant &state, QRhiGraphicsPipeline *graphicsPipeline); - bool m_ownCurrent; + bool m_ownsRhiCtx; + bool m_drivenExternally; const unsigned int m_id; QSurface *m_surface; QSize m_surfaceSize; @@ -227,6 +234,8 @@ private: QHash<QSurface *, SwapChainInfo> m_swapChains; QRhiSwapChain *m_currentSwapChain; QRhiRenderPassDescriptor *m_currentRenderPassDescriptor; + QRhiRenderTarget *m_defaultRenderTarget; + QRhiCommandBuffer *m_defaultCommandBuffer; #ifndef QT_NO_OPENGL QOffscreenSurface *m_fallbackSurface; diff --git a/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp b/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp index 385fa24b4..387464fe4 100644 --- a/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp +++ b/src/plugins/renderers/rhi/managers/rhiresourcemanagers.cpp @@ -68,6 +68,9 @@ RHIResourceManagers::~RHIResourceManagers() void RHIResourceManagers::releaseAllResources() { auto releaseAll = [](auto *manager) noexcept { + // Release all resources reference by a key + manager->releaseAllResources(); + // Release remaining resources which were allocated manually but had no key const auto handles = manager->activeHandles(); for (const auto &handle : handles) { manager->release(handle); diff --git a/src/plugins/renderers/rhi/renderer/renderer.cpp b/src/plugins/renderers/rhi/renderer/renderer.cpp index 096994da1..a194c94ff 100644 --- a/src/plugins/renderers/rhi/renderer/renderer.cpp +++ b/src/plugins/renderers/rhi/renderer/renderer.cpp @@ -247,6 +247,7 @@ Renderer::Renderer() m_exposed(0), m_lastFrameCorrect(0), m_glContext(nullptr), + m_rhiContext(nullptr), m_time(0), m_settings(nullptr), m_updateShaderDataTransformJob(Render::UpdateShaderDataTransformJobPtr::create()), @@ -265,7 +266,6 @@ Renderer::Renderer() [this](Qt3DCore::QAspectManager *m) { sendShaderChangesToFrontend(m); }, JobTypes::DirtyShaderGathering)), m_ownedContext(false), - m_offscreenHelper(nullptr), m_RHIResourceManagers(nullptr), m_commandExecuter(new Qt3DRender::Debug::CommandExecuter(this)), m_shouldSwapBuffers(true) @@ -316,7 +316,24 @@ void Renderer::dumpInfo() const API Renderer::api() const { - return API::OpenGL; + return API::RHI; +} + +// When Scene3D is driving rendering: +// - Don't create SwapChains +// - Use the defaultRenderTarget if no renderTarget specified instead of the +// swapChain +// - Use the provided commandBuffer +void Renderer::setRenderDriver(AbstractRenderer::RenderDriver driver) +{ + // This must be called before initialize which creates the submission context + Q_ASSERT(!m_submissionContext); + m_driver = driver; +} + +AbstractRenderer::RenderDriver Renderer::renderDriver() const +{ + return m_driver; } qint64 Renderer::time() const @@ -397,6 +414,30 @@ void Renderer::setOpenGLContext(QOpenGLContext *context) m_glContext = context; } +void Renderer::setRHIContext(QRhi *ctx) +{ + m_rhiContext = ctx; +} + +// Called by Scene3D +void Renderer::setDefaultRHIRenderTarget(QRhiRenderTarget *defaultTarget) +{ + Q_ASSERT(m_submissionContext); + m_submissionContext->setDefaultRenderTarget(defaultTarget); + + // When the defaultTarget changes, we have to recreate all QRhiGraphicsPipelines + // to ensure they match new DefaultRenderTargetLayout. This is potentially expensive + RHIGraphicsPipelineManager *pipelineManager = m_RHIResourceManagers->rhiGraphicsPipelineManager(); + pipelineManager->releaseAllResources(); +} + +// Called by Scene3D +void Renderer::setRHICommandBuffer(QRhiCommandBuffer *commandBuffer) +{ + Q_ASSERT(m_submissionContext); + m_submissionContext->setCommandBuffer(commandBuffer); +} + void Renderer::setScreen(QScreen *scr) { m_screen = scr; @@ -450,6 +491,11 @@ void Renderer::initialize() m_submissionContext.reset(new SubmissionContext); m_submissionContext->setRenderer(this); + if (m_driver == AbstractRenderer::Scene3D) { + m_submissionContext->setRHIContext(m_rhiContext); + m_submissionContext->setDrivenExternally(true); + } + // RHI initialization { qCDebug(Backend) << Q_FUNC_INFO << "Requesting renderer initialize"; @@ -534,53 +580,6 @@ void Renderer::releaseGraphicsResources() // check that we haven't already cleaned up before going any further. if (!m_submissionContext) return; - - // Try to temporarily make the context current so we can free up any resources - QMutexLocker locker(&m_offscreenSurfaceMutex); - QOffscreenSurface *offscreenSurface = m_offscreenHelper->offscreenSurface(); - if (!offscreenSurface) { - qCWarning(Backend) << "Failed to make context current: OpenGL resources will not be destroyed"; - // We still need to delete the submission context - m_submissionContext.reset(nullptr); - return; - } - - //* QOpenGLContext *context = m_submissionContext->openGLContext(); - //* Q_ASSERT(context); - //* - //* if (context->thread() == QThread::currentThread() && context->makeCurrent(offscreenSurface)) - //{ - //* - //* // Clean up the graphics context and any resources - //* const std::vector<HRHITexture> activeTexturesHandles = - //m_RHIResourceManagers->rhiTextureManager()->activeHandles(); - //* for (const HRHITexture &textureHandle : activeTexturesHandles) { - //* RHITexture *tex = m_RHIResourceManagers->rhiTextureManager()->data(textureHandle); - //* tex->destroy(); - //* } - //* - //* // Do the same thing with buffers - //* const std::vector<HRHIBuffer> activeBuffers = - //m_RHIResourceManagers->rhiBufferManager()->activeHandles(); - //* for (const HRHIBuffer &bufferHandle : activeBuffers) { - //* RHIBuffer *buffer = m_RHIResourceManagers->rhiBufferManager()->data(bufferHandle); - //* buffer->destroy(m_submissionContext.data()); - //* } - //* - //* // Do the same thing with shaders - //* const std::vector<RHIShader *> shaders = - //m_RHIResourceManagers->rhiShaderManager()->takeActiveResources(); - //* qDeleteAll(shaders); - //* - //* - //* context->doneCurrent(); - //* } else { - //* qCWarning(Backend) << "Failed to make context current: OpenGL resources will not be destroyed"; - //* } - //* - //* if (m_ownedContext) - //* delete context; - m_submissionContext.reset(nullptr); qCDebug(Backend) << Q_FUNC_INFO << "Renderer properly shutdown"; @@ -807,16 +806,6 @@ QVariant Renderer::executeCommand(const QStringList &args) return m_commandExecuter->executeCommand(args); } -/*! - \internal - Called in the context of the aspect thread from QRenderAspect::onRegistered -*/ -void Renderer::setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) -{ - QMutexLocker locker(&m_offscreenSurfaceMutex); - m_offscreenHelper = helper; -} - QSurfaceFormat Renderer::format() { return m_submissionContext->format(); @@ -908,8 +897,6 @@ void Renderer::updateGraphicsPipeline(RenderCommand &cmd, RenderView *rv, if (auto& pipelines = m_rvToPipelines[rv]; !Qt3DCore::contains(pipelines, graphicsPipeline)) pipelines.push_back(graphicsPipeline); - // TO DO: Record active pipelines in the RenderView - // Record RHIGraphicsPipeline into command for later use cmd.pipeline = graphicsPipeline; @@ -933,10 +920,18 @@ void Renderer::buildGraphicsPipelines(RHIGraphicsPipeline *graphicsPipeline, // Note: we can rebuild add/remove things from the QRhiShaderResourceBindings after having // created the pipeline and rebuild it. Changes should be picked up automatically - const SubmissionContext::SwapChainInfo *swapchain = - m_submissionContext->swapChainForSurface(rv->surface()); - if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor) - return; + + // If a defaultRenderTarget was set (Scene3D) we don't bother retrieving the swapchain + // as we have to use the one provided by Scene3D + QRhiSwapChain *rhiSwapChain = nullptr; + if (!m_submissionContext->defaultRenderTarget()) { + const SubmissionContext::SwapChainInfo *swapchain = m_submissionContext->swapChainForSurface(rv->surface()); + if (!swapchain || !swapchain->swapChain || !swapchain->renderPassDescriptor) { + qCWarning(Backend) << "Can't create pipeline, incomplete SwapChain and no default Render Target"; + return; + } + rhiSwapChain = swapchain->swapChain; + } auto onFailure = [&] { qCWarning(Backend) << "Failed to build pipeline"; @@ -1019,7 +1014,7 @@ void Renderer::buildGraphicsPipelines(RHIGraphicsPipeline *graphicsPipeline, m_submissionContext->applyStateSet(renderState, pipeline); // Setup potential texture render target - const bool renderTargetIsSet = setupRenderTarget(rv, graphicsPipeline, swapchain->swapChain); + const bool renderTargetIsSet = setupRenderTarget(rv, graphicsPipeline, rhiSwapChain); if (!renderTargetIsSet) return onFailure(); @@ -1133,7 +1128,9 @@ void Renderer::createRenderTarget(RenderView *rv, RHIRenderTarget *target) target->depthStencilBuffer = ds; } -bool Renderer::setupRenderTarget(RenderView *rv, RHIGraphicsPipeline *graphicsPipeline, QRhiSwapChain *swapchain) +bool Renderer::setupRenderTarget(RenderView *rv, + RHIGraphicsPipeline *graphicsPipeline, + QRhiSwapChain *swapchain) { QRhiGraphicsPipeline *rhiPipeline = graphicsPipeline->pipeline(); @@ -1163,13 +1160,18 @@ bool Renderer::setupRenderTarget(RenderView *rv, RHIGraphicsPipeline *graphicsPi rhiRenderTargetManager->nodeIdForRHIRenderTarget.insert(rhiTarget, renderTargetId); } } - rhiPipeline->setRenderPassDescriptor(rhiTarget->renderPassDescriptor); rhiPipeline->setSampleCount(rhiTarget->renderTarget->sampleCount()); - + return true; + } else if (m_submissionContext->defaultRenderTarget()) { + // Use default RenderTarget if set Default FBO set by Scene3D + QRhiRenderTarget *defaultTarget = m_submissionContext->defaultRenderTarget();; + rhiPipeline->setRenderPassDescriptor(defaultTarget->renderPassDescriptor()); + rhiPipeline->setSampleCount(defaultTarget->sampleCount()); return true; } else { - // Render to the default framebuffer + Q_ASSERT(swapchain); + // Render to the default framebuffer on our swapchain rhiPipeline->setRenderPassDescriptor(swapchain->renderPassDescriptor()); rhiPipeline->setSampleCount(swapchain->sampleCount()); return true; @@ -1871,6 +1873,8 @@ Renderer::submitRenderViews(const std::vector<RHIPassInfo> &rhiPassesInfo) const bool surfaceHasChanged = surface != previousSurface; if (surfaceHasChanged && previousSurface) { + // TO DO: Warn that this likely won't work with Scene3D + // TODO what should be the swapBuffers condition for RHI ? // lastRenderTarget == swapChain->renderTarget or something like that ? const bool swapBuffers = surfaceLock.isSurfaceValid() && m_shouldSwapBuffers; @@ -1879,6 +1883,8 @@ Renderer::submitRenderViews(const std::vector<RHIPassInfo> &rhiPassesInfo) } if (surfaceHasChanged) { + // TO DO: Warn that this likely won't work with Scene3D + // If we can't make the context current on the surface, skip to the // next RenderView. We won't get the full frame but we may get something if (!m_submissionContext->beginDrawing(surface)) { @@ -2455,11 +2461,12 @@ bool Renderer::executeCommandsSubmission(const RHIPassInfo &passInfo) auto &renderTargetManager = *managers.rhiRenderTargetManager(); auto *renderTarget = renderTargetManager.lookupResource(passInfo.renderTargetId); - if (renderTarget) { + if (renderTarget) rhiRenderTarget = renderTarget->renderTarget; - } else { + else if (m_submissionContext->defaultRenderTarget()) + rhiRenderTarget = m_submissionContext->defaultRenderTarget(); + else rhiRenderTarget = m_submissionContext->currentSwapChain()->currentFrameRenderTarget(); - } } // TO DO: should be moved elsewhere diff --git a/src/plugins/renderers/rhi/renderer/renderer_p.h b/src/plugins/renderers/rhi/renderer/renderer_p.h index 190fed178..5d89670b0 100644 --- a/src/plugins/renderers/rhi/renderer/renderer_p.h +++ b/src/plugins/renderers/rhi/renderer/renderer_p.h @@ -168,6 +168,9 @@ public: void dumpInfo() const override; API api() const override; + void setRenderDriver(AbstractRenderer::RenderDriver driver) override; + AbstractRenderer::RenderDriver renderDriver() const override; + qint64 time() const override; void setTime(qint64 time) override; void setJobsInLastFrame(int jobsInLastFrame) override; @@ -265,8 +268,11 @@ public: std::vector<RHIPassInfo> prepareCommandsSubmission(const std::vector<RenderView *> &renderViews); bool executeCommandsSubmission(const RHIPassInfo &passInfo); - // For Scene2D rendering + // For Scene3D/Scene2D rendering void setOpenGLContext(QOpenGLContext *context) override; + void setRHIContext(QRhi *ctx) override; + void setDefaultRHIRenderTarget(QRhiRenderTarget *defaultTarget) override; + void setRHICommandBuffer(QRhiCommandBuffer *commandBuffer) override; bool accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture, QMutex **lock, bool readonly) override; QSharedPointer<RenderBackendResourceAccessor> resourceAccessor() const override; @@ -282,8 +288,8 @@ public: void enqueueRenderView(RenderView *renderView, int submitOrder); bool waitUntilReadyToSubmit(); - QVariant executeCommand(const QStringList &args) override; - void setOffscreenSurfaceHelper(OffscreenSurfaceHelper *helper) override; + QVariant executeCommand(const QStringList &args); + void setOffscreenSurfaceHelper(OffscreenSurfaceHelper *) override {}; QSurfaceFormat format() override; struct ViewSubmissionResultData @@ -345,6 +351,7 @@ private: QAtomicInt m_lastFrameCorrect; QOpenGLContext *m_glContext; + QRhi *m_rhiContext; qint64 m_time; @@ -387,7 +394,6 @@ private: bool m_ownedContext; - OffscreenSurfaceHelper *m_offscreenHelper; RHIResourceManagers *m_RHIResourceManagers; QMutex m_offscreenSurfaceMutex; @@ -405,6 +411,7 @@ private: QScreen *m_screen = nullptr; QSharedPointer<ResourceAccessor> m_scene2DResourceAccessor; QHash<RenderView *, std::vector<RHIGraphicsPipeline *>> m_rvToPipelines; + RenderDriver m_driver = RenderDriver::Qt3D; bool m_hasSwapChain = false; diff --git a/src/quick3d/imports/scene3d/scene3ditem.cpp b/src/quick3d/imports/scene3d/scene3ditem.cpp index 7b89ce91b..1932cd155 100644 --- a/src/quick3d/imports/scene3d/scene3ditem.cpp +++ b/src/quick3d/imports/scene3d/scene3ditem.cpp @@ -747,7 +747,7 @@ QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNode const bool hasScene3DViews = !m_views.empty(); Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(node); - // When usin Scene3DViews or Scene3D in Underlay mode + // When using Scene3DViews or Scene3D in Underlay mode // we shouldn't be managing a Scene3DSGNode if (!usesFBO || hasScene3DViews) { if (fboNode != nullptr) { diff --git a/src/quick3d/imports/scene3d/scene3drenderer.cpp b/src/quick3d/imports/scene3d/scene3drenderer.cpp index e8ffdea60..e75dd9689 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer.cpp +++ b/src/quick3d/imports/scene3d/scene3drenderer.cpp @@ -45,6 +45,7 @@ #include <qopenglcontext.h> #include <qopenglframebufferobject.h> #include <QtQuick/qquickwindow.h> +#include <private/qrhi_p.h> #include <Qt3DRender/private/qrenderaspect_p.h> #include <Qt3DRender/private/abstractrenderer_p.h> @@ -150,15 +151,9 @@ Scene3DRenderer::Scene3DRenderer() , m_item(nullptr) , m_aspectEngine(nullptr) , m_renderAspect(nullptr) - , m_multisampledFBO(nullptr) - , m_finalFBO(nullptr) - , m_texture(nullptr) , m_node(nullptr) , m_window(nullptr) - , m_multisample(false) // this value is not used, will be synced from the Scene3DItem instead - , m_lastMultisample(false) , m_needsShutdown(true) - , m_forceRecreate(false) , m_shouldRender(false) , m_dirtyViews(false) , m_skipFrame(false) @@ -180,20 +175,34 @@ void Scene3DRenderer::init(Scene3DItem *item, Qt3DCore::QAspectEngine *aspectEng Q_CHECK_PTR(m_item->window()); m_window = m_item->window(); - QObject::connect(m_item->window(), &QQuickWindow::beforeSynchronizing, this, &Scene3DRenderer::beforeSynchronize, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::beforeRenderPassRecording, this, &Scene3DRenderer::render, Qt::DirectConnection); - QObject::connect(m_item->window(), &QQuickWindow::sceneGraphInvalidated, this, &Scene3DRenderer::onSceneGraphInvalidated, Qt::DirectConnection); + + // Detect which Rendering backend Qt3D is using + Qt3DRender::QRenderAspectPrivate *aspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect)); + Qt3DRender::Render::AbstractRenderer *renderer = aspectPriv->m_renderer; + + const bool isRHI = renderer->api() == API::RHI; + if (isRHI) + m_quickRenderer = new Scene3DRenderer::RHIRenderer; + else + m_quickRenderer = new Scene3DRenderer::GLRenderer; + m_quickRenderer->initialize(this, renderer); + + QObject::connect(m_item->window(), &QQuickWindow::beforeSynchronizing, + this, [this] () { m_quickRenderer->beforeSynchronize(this); }, Qt::DirectConnection); + QObject::connect(m_item->window(), &QQuickWindow::beforeRendering, this, + [this] () { m_quickRenderer->beforeRendering(this); }, Qt::DirectConnection); + QObject::connect(m_item->window(), &QQuickWindow::beforeRenderPassRecording, this, + [this] () { m_quickRenderer->beforeRenderPassRecording(this); }, Qt::DirectConnection); + QObject::connect(m_item->window(), &QQuickWindow::sceneGraphInvalidated, this, + &Scene3DRenderer::onSceneGraphInvalidated, Qt::DirectConnection); // So that we can schedule the cleanup - QObject::connect(m_item, &QQuickItem::windowChanged, this, &Scene3DRenderer::onWindowChanged, Qt::QueuedConnection); + QObject::connect(m_item, &QQuickItem::windowChanged, this, + &Scene3DRenderer::onWindowChanged, Qt::QueuedConnection); // Main thread -> updates the rendering window QObject::connect(m_item, &QQuickItem::windowChanged, this, [this] (QQuickWindow *w) { QMutexLocker l(&m_windowMutex); m_window = w; }); - - Q_ASSERT(QOpenGLContext::currentContext()); - ContextSaver saver; - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderInitialize(saver.context()); scheduleRootEntityChange(); } @@ -202,25 +211,6 @@ Scene3DRenderer::~Scene3DRenderer() qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread(); } - -QOpenGLFramebufferObject *Scene3DRenderer::createMultisampledFramebufferObject(const QSize &size) -{ - QOpenGLFramebufferObjectFormat format; - format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - int samples = QSurfaceFormat::defaultFormat().samples(); - if (samples == -1) - samples = 4; - format.setSamples(samples); - return new QOpenGLFramebufferObject(size, format); -} - -QOpenGLFramebufferObject *Scene3DRenderer::createFramebufferObject(const QSize &size) -{ - QOpenGLFramebufferObjectFormat format; - format.setAttachment(QOpenGLFramebufferObject::Depth); - return new QOpenGLFramebufferObject(size, format); -} - void Scene3DRenderer::scheduleRootEntityChange() { QMetaObject::invokeMethod(m_item, "applyRootEntityChange", Qt::QueuedConnection); @@ -245,15 +235,13 @@ void Scene3DRenderer::shutdown() engineD->exitSimulationLoop(); } - // Shutdown the Renderer Aspect while the OpenGL context - // is still valid - if (m_renderAspect) { - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->renderShutdown(); - m_renderAspect = nullptr; - } + m_quickRenderer->shutdown(this); + + m_renderAspect = nullptr; m_aspectEngine = nullptr; - m_finalFBO.reset(); - m_multisampledFBO.reset(); + + delete m_quickRenderer; + m_quickRenderer = nullptr; } // QtQuick render thread (which may also be the gui/main thread with QQuickWidget / RenderControl) @@ -277,102 +265,6 @@ void Scene3DRenderer::onWindowChanged(QQuickWindow *w) } } -// Render Thread, GUI locked -void Scene3DRenderer::beforeSynchronize() -{ - if (m_item && m_window) { - - // Only render if we are sure aspectManager->processFrame was called prior - // We could otherwise enter a deadlock state - if (!m_allowRendering.tryAcquire(std::max(m_allowRendering.available(), 1))) - return; - - // In the case of OnDemand rendering, we still need to get to this - // point to ensure we have processed jobs for all aspects. - // We also still need to call render() to allow proceeding with the - // next frame. However it won't be performing any 3d rendering at all - // so we do it here and return early. This prevents a costly QtQuick - // SceneGraph update for nothing - if (m_skipFrame) { - m_skipFrame = false; - ContextSaver saver; - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->render(false); - return; - } - - m_shouldRender = true; - - // Check size / multisampling - m_multisample = m_item->multisample(); - const QSize boundingRectSize = m_item->boundingRect().size().toSize(); - const QSize currentSize = boundingRectSize * m_window->effectiveDevicePixelRatio(); - const bool sizeHasChanged = currentSize != m_lastSize; - const bool multisampleHasChanged = m_multisample != m_lastMultisample; - const bool forceRecreate = sizeHasChanged || multisampleHasChanged; - // Store the current size as a comparison - // point for the next frame - m_lastSize = currentSize; - m_lastMultisample = m_multisample; - - if (sizeHasChanged) { - static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); - setItemAreaAndDevicePixelRatio.invoke(m_item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), - Q_ARG(qreal, m_window->effectiveDevicePixelRatio())); - } - - // Rebuild FBO if size/multisampling has changed - const bool usesFBO = m_compositingMode == Scene3DItem::FBO; - if (usesFBO) { - // Rebuild FBO and textures if never created or a resize has occurred - if ((m_multisampledFBO.isNull() || forceRecreate) && m_multisample) { - m_multisampledFBO.reset(createMultisampledFramebufferObject(m_lastSize)); - if (m_multisampledFBO->format().samples() == 0 || !QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) { - m_multisample = false; - m_multisampledFBO.reset(nullptr); - } - } - - const bool generateNewTexture = m_finalFBO.isNull() || forceRecreate; - if (generateNewTexture) { - m_finalFBO.reset(createFramebufferObject(m_lastSize)); - m_textureId = m_finalFBO->texture(); - m_texture.reset(m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, m_textureId, - 0, m_finalFBO->size(), QQuickWindow::TextureHasAlphaChannel)); - } - - // We can render either the Scene3D or the Scene3DView but not both - // at the same time - Q_ASSERT((m_node == nullptr || m_views.empty()) || - (m_node != nullptr && m_views.empty()) || - (m_node == nullptr && !m_views.empty())); - - // Set texture on node - if (m_node && (!m_node->texture() || generateNewTexture)) - m_node->setTexture(m_texture.data()); - - // Set textures on Scene3DView - if (m_dirtyViews || generateNewTexture) { - for (Scene3DView *view : qAsConst(m_views)) - if (!view->texture() || generateNewTexture) - view->setTexture(m_texture.data()); - m_dirtyViews = false; - } - } - - if (m_aspectEngine->rootEntity() != m_item->entity()) - scheduleRootEntityChange(); - - // Mark SGNodes as dirty so that QQuick will trigger some rendering - if (m_node) - m_node->markDirty(QSGNode::DirtyMaterial); - - for (Scene3DView *view : qAsConst(m_views)) - view->markSGNodeDirty(); - - m_item->update(); - } -} - void Scene3DRenderer::allowRender() { m_allowRendering.release(1); @@ -400,19 +292,160 @@ void Scene3DRenderer::setSGNode(Scene3DSGNode *node) m_node = node; } -// Render Thread, Main Thread is unlocked at this point -void Scene3DRenderer::render() +QOpenGLFramebufferObject *Scene3DRenderer::GLRenderer::createMultisampledFramebufferObject(const QSize &size) { - QMutexLocker l(&m_windowMutex); - // Lock to ensure the window doesn't change while we are rendering - if (!m_window || !m_shouldRender) + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + int samples = QSurfaceFormat::defaultFormat().samples(); + if (samples == -1) + samples = 4; + format.setSamples(samples); + return new QOpenGLFramebufferObject(size, format); +} + +QOpenGLFramebufferObject *Scene3DRenderer::GLRenderer::createFramebufferObject(const QSize &size) +{ + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::Depth); + return new QOpenGLFramebufferObject(size, format); +} + +void Scene3DRenderer::GLRenderer::initialize(Scene3DRenderer *scene3DRenderer, + Qt3DRender::Render::AbstractRenderer *renderer) +{ + Q_UNUSED(scene3DRenderer); + Q_ASSERT(QOpenGLContext::currentContext()); + m_renderer = renderer; + ContextSaver saver; + Q_ASSERT(renderer->api() == API::OpenGL); + + m_renderer->setRenderDriver(Qt3DRender::Render::AbstractRenderer::Scene3D); + m_renderer->setOpenGLContext(saver.context()); + m_renderer->initialize(); +} + +void Scene3DRenderer::GLRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) +{ + // Check size / multisampling + Scene3DItem *item = scene3DRenderer->m_item; + QQuickWindow *window = scene3DRenderer->m_window; + + if (!item || !window) + return; + + // Only render if we are sure aspectManager->processFrame was called prior + // We could otherwise enter a deadlock state + if (!scene3DRenderer->m_allowRendering.tryAcquire(std::max(scene3DRenderer->m_allowRendering.available(), 1))) return; - m_shouldRender = false; + // In the case of OnDemand rendering, we still need to get to this + // point to ensure we have processed jobs for all aspects. + // We also still need to call render() to allow proceeding with the + // next frame. However it won't be performing any 3d rendering at all + // so we do it here and return early. This prevents a costly QtQuick + // SceneGraph update for nothing + if (scene3DRenderer->m_skipFrame) { + scene3DRenderer->m_skipFrame = false; + ContextSaver saver; + m_renderer->render(false); + return; + } + + scene3DRenderer->m_shouldRender = true; + + m_multisample = item->multisample(); + const QSize boundingRectSize = item->boundingRect().size().toSize(); + const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); + const bool sizeHasChanged = currentSize != m_lastSize; + const bool multisampleHasChanged = m_multisample != m_lastMultisample; + const bool forceRecreate = sizeHasChanged || multisampleHasChanged; + // Store the current size as a comparison + // point for the next frame + m_lastSize = currentSize; + m_lastMultisample = m_multisample; + + if (sizeHasChanged) { + static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); + setItemAreaAndDevicePixelRatio.invoke(item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), + Q_ARG(qreal, window->effectiveDevicePixelRatio())); + } + + // Rebuild FBO if size/multisampling has changed + const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; + auto node = scene3DRenderer->m_node; + if (usesFBO) { + // Rebuild FBO and textures if never created or a resize has occurred + if ((m_multisampledFBO.isNull() || forceRecreate) && m_multisample) { + m_multisampledFBO.reset(createMultisampledFramebufferObject(m_lastSize)); + if (m_multisampledFBO->format().samples() == 0 || !QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) { + m_multisample = false; + m_multisampledFBO.reset(nullptr); + } + } + + const bool generateNewTexture = m_finalFBO.isNull() || forceRecreate; + if (generateNewTexture) { + m_finalFBO.reset(createFramebufferObject(m_lastSize)); + m_textureId = m_finalFBO->texture(); + m_texture.reset(window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, m_textureId, + 0, m_finalFBO->size(), QQuickWindow::TextureHasAlphaChannel)); + } + + // We can render either the Scene3D or the Scene3DView but not both + // at the same time + const auto &views = scene3DRenderer->m_views; + Q_ASSERT((node == nullptr || views.empty()) || + (node != nullptr && views.empty()) || + (node == nullptr && !views.empty())); + + // Set texture on node + if (node && (!node->texture() || generateNewTexture)) + node->setTexture(m_texture.data()); + + // Set textures on Scene3DView + if (scene3DRenderer->m_dirtyViews || generateNewTexture) { + for (Scene3DView *view : qAsConst(views)) + if (!view->texture() || generateNewTexture) + view->setTexture(m_texture.data()); + scene3DRenderer->m_dirtyViews = false; + } + } + + if (scene3DRenderer->m_aspectEngine->rootEntity() != item->entity()) + scene3DRenderer->scheduleRootEntityChange(); + + // Mark SGNodes as dirty so that QQuick will trigger some rendering + if (node) + node->markDirty(QSGNode::DirtyMaterial); + + for (Scene3DView *view : qAsConst(scene3DRenderer->m_views)) + view->markSGNodeDirty(); + + item->update(); +} + +void Scene3DRenderer::GLRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) +{ + Q_UNUSED(scene3DRenderer); +} + +void Scene3DRenderer::GLRenderer::beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) +{ + // When this is fired, beforeRendering was fired and the RenderPass for QtQuick was + // started -> meaning the window was cleared but actual draw commands have yet to be + // recorded. + // When using the GL Renderer and FBO, that's the state we want to be in. + + QMutexLocker l(&scene3DRenderer->m_windowMutex); + // Lock to ensure the window doesn't change while we are rendering + if (!scene3DRenderer->m_window || !scene3DRenderer->m_shouldRender) + return; + // Reset flag for next frame + scene3DRenderer->m_shouldRender = false; ContextSaver saver; // Create and bind FBO if using the FBO compositing mode - const bool usesFBO = m_compositingMode == Scene3DItem::FBO; + const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; if (usesFBO) { // Bind FBO if (m_multisample) //Only try to use MSAA when available @@ -423,7 +456,7 @@ void Scene3DRenderer::render() // Render Qt3D Scene // Qt3D takes care of resetting the GL state to default values - static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(m_renderAspect))->render(usesFBO); + m_renderer->render(usesFBO); // We may have called doneCurrent() so restore the context if the rendering surface was changed // Note: keep in mind that the ContextSave also restores the surface when destroyed @@ -448,11 +481,231 @@ void Scene3DRenderer::render() // Only show the node once Qt3D has rendered to it // Avoids showing garbage on the first frame - if (m_node) - m_node->show(); + if (scene3DRenderer->m_node) + scene3DRenderer->m_node->show(); } } +void Scene3DRenderer::GLRenderer::shutdown(Scene3DRenderer *sceneRenderer) +{ + // Shutdown the Renderer Aspect while the OpenGL context + // is still valid + if (sceneRenderer->m_renderAspect) + m_renderer->shutdown(); + + m_finalFBO.reset(); + m_multisampledFBO.reset(); +} + +void Scene3DRenderer::RHIRenderer::initialize(Scene3DRenderer *scene3DRenderer, + Qt3DRender::Render::AbstractRenderer *renderer) +{ + QQuickWindow *window = scene3DRenderer->m_window; + Q_ASSERT(window); + Q_ASSERT(renderer->api() == API::RHI); + + // Retrieve RHI context + QSGRendererInterface *rif = window->rendererInterface(); + const bool isRhi = QSGRendererInterface::isApiRhiBased(rif->graphicsApi()); + Q_ASSERT(isRhi); + if (isRhi) { + m_rhi = static_cast<QRhi *>(rif->getResource(window, QSGRendererInterface::RhiResource)); + if (!m_rhi) + qFatal("No QRhi from QQuickWindow, this cannot happen"); + m_renderer = renderer; + m_renderer->setRenderDriver(Qt3DRender::Render::AbstractRenderer::Scene3D); + m_renderer->setRHIContext(m_rhi); + m_renderer->initialize(); + } +} + +void Scene3DRenderer::RHIRenderer::beforeSynchronize(Scene3DRenderer *scene3DRenderer) +{ + // Check size / multisampling + Scene3DItem *item = scene3DRenderer->m_item; + QQuickWindow *window = scene3DRenderer->m_window; + + if (!item || !window) + return; + + // Only render if we are sure aspectManager->processFrame was called prior + // We could otherwise enter a deadlock state + if (!scene3DRenderer->m_allowRendering.tryAcquire(std::max(scene3DRenderer->m_allowRendering.available(), 1))) + return; + + // In the case of OnDemand rendering, we still need to get to this + // point to ensure we have processed jobs for all aspects. + // We also still need to call render() to allow proceeding with the + // next frame. However it won't be performing any 3d rendering at all + // so we do it here and return early. This prevents a costly QtQuick + // SceneGraph update for nothing + if (scene3DRenderer->m_skipFrame) { + scene3DRenderer->m_skipFrame = false; + m_renderer->render(false); + return; + } + + scene3DRenderer->m_shouldRender = true; + + const QSize boundingRectSize = item->boundingRect().size().toSize(); + const QSize currentSize = boundingRectSize * window->effectiveDevicePixelRatio(); + const bool sizeHasChanged = currentSize != m_lastSize; + const bool multisampleHasChanged = item->multisample() != m_lastMultisample; + // Store the current size and multisample as a comparison point for the next frame + m_lastMultisample = item->multisample(); + m_lastSize = currentSize; + + if (sizeHasChanged) { + static const QMetaMethod setItemAreaAndDevicePixelRatio = setItemAreaAndDevicePixelRatioMethod(); + setItemAreaAndDevicePixelRatio.invoke(item, Qt::QueuedConnection, Q_ARG(QSize, boundingRectSize), + Q_ARG(qreal, window->effectiveDevicePixelRatio())); + } + + const bool forceRecreate = sizeHasChanged || multisampleHasChanged; + const bool usesFBO = scene3DRenderer->m_compositingMode == Scene3DItem::FBO; + // Not sure how we could support underlay rendering properly given Qt3D RHI will render into its own + // RHI RenderPasses prior to QtQuick and beginning a new RenderPass clears the screen + Q_ASSERT(usesFBO); + auto node = scene3DRenderer->m_node; + const bool generateNewTexture = m_texture.isNull() || forceRecreate; + const int samples = item->multisample() ? 4 : 1; + + if (generateNewTexture) { + releaseRHIResources(); + + // Depth / Stencil + m_rhiDepthRenderBuffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_lastSize, samples); + m_rhiDepthRenderBuffer->create(); + + // Color Attachment or Color Resolutin texture (if using MSAA) + m_rhiTexture = m_rhi->newTexture(QRhiTexture::RGBA8, m_lastSize, 1, QRhiTexture::RenderTarget|QRhiTexture::UsedAsTransferSource); + m_rhiTexture->create(); + + // Color Attachment description + QRhiTextureRenderTargetDescription renderTargetDesc; + renderTargetDesc.setDepthStencilBuffer(m_rhiDepthRenderBuffer); + if (samples > 1) { + m_rhiMSAARenderBuffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, m_lastSize, samples, {}, m_rhiTexture->format()); + m_rhiMSAARenderBuffer->create(); + QRhiColorAttachment att; + att.setRenderBuffer(m_rhiMSAARenderBuffer); + att.setResolveTexture(m_rhiTexture); + renderTargetDesc.setColorAttachments({ att }); + } else { + renderTargetDesc.setColorAttachments({ m_rhiTexture }); + } + + m_rhiRenderTarget = m_rhi->newTextureRenderTarget(renderTargetDesc); + m_rhiRenderTargetPassDescriptor = m_rhiRenderTarget->newCompatibleRenderPassDescriptor(); + m_rhiRenderTarget->setRenderPassDescriptor(m_rhiRenderTargetPassDescriptor); + m_rhiRenderTarget->create(); + + // Create QSGTexture from QRhiTexture + m_texture.reset(window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, + m_rhiTexture->nativeTexture().object, + 0, m_lastSize, QQuickWindow::TextureHasAlphaChannel)); + + // Set the Default RenderTarget to use on the RHI Renderer + // Note: this will release all pipelines using previousRenderTarget + m_renderer->setDefaultRHIRenderTarget(m_rhiRenderTarget); + } + + // Set texture on node + if (node && (!node->texture() || generateNewTexture)) + node->setTexture(m_texture.data()); + + // Mark SGNodes as dirty so that QQuick will trigger some rendering + if (node) + node->markDirty(QSGNode::DirtyMaterial); + + if (scene3DRenderer->m_aspectEngine->rootEntity() != item->entity()) + scene3DRenderer->scheduleRootEntityChange(); + + item->update(); +} + +void Scene3DRenderer::RHIRenderer::beforeRendering(Scene3DRenderer *scene3DRenderer) +{ + // This is fired before QtQuick starts its RenderPass but after a RHI Command Buffer + // has been created and after the RHI Frame has begun + + // This means the swap chain has been set up and that we only need to record commands + + // On the Qt3D side this also means we shouldn't be calling m_rhi->beginFrame/endFrame + // and we should only be rendering using the provided swap chain + + // Therefore, if a QRenderSurfaceSelector is used in the FrameGraph to render to another surface, + // this will not be supported with Scene3D/RHI + + + QMutexLocker l(&scene3DRenderer->m_windowMutex); + // Lock to ensure the window doesn't change while we are rendering + if (!scene3DRenderer->m_window || !scene3DRenderer->m_shouldRender) + return; + // Reset flag for next frame + scene3DRenderer->m_shouldRender = false; + + // TO DO: We need to check what we do about 3DViews and Composition Mode as those are not + // directly applicable with RHI + + // Retrieve Command Buffer used by QtQuick + QRhiCommandBuffer *cb = nullptr; + QSGRendererInterface *rif = scene3DRenderer->m_window ->rendererInterface(); + QRhiSwapChain *swapchain = static_cast<QRhiSwapChain *>(rif->getResource(scene3DRenderer->m_window, QSGRendererInterface::RhiSwapchainResource)); + if (swapchain) { + cb = swapchain->currentFrameCommandBuffer(); + } else { + cb = static_cast<QRhiCommandBuffer *>(rif->getResource(scene3DRenderer->m_window, QSGRendererInterface::RhiRedirectCommandBuffer)); + } + + Q_ASSERT(cb); + // Tell Qt3D renderer to use provided command buffer + m_renderer->setRHICommandBuffer(cb); + m_renderer->render(false); + + // Only show the node once Qt3D has rendered to it + // Avoids showing garbage on the first frame + if (scene3DRenderer->m_node) + scene3DRenderer->m_node->show(); +} + +void Scene3DRenderer::RHIRenderer::beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) +{ + Q_UNUSED(scene3DRenderer); +} + +void Scene3DRenderer::RHIRenderer::shutdown(Scene3DRenderer *scene3DRenderer) +{ + if (scene3DRenderer->m_renderAspect) + m_renderer->shutdown(); + + releaseRHIResources(); +} + +void Scene3DRenderer::RHIRenderer::releaseRHIResources() +{ + delete m_rhiRenderTarget; + m_rhiRenderTarget = nullptr; + + delete m_rhiTexture; + m_rhiTexture = nullptr; + + delete m_rhiDepthRenderBuffer; + m_rhiDepthRenderBuffer = nullptr; + + delete m_rhiMSAARenderBuffer; + m_rhiMSAARenderBuffer = nullptr; + + delete m_rhiColorRenderBuffer; + m_rhiColorRenderBuffer = nullptr; + + delete m_rhiRenderTargetPassDescriptor; + m_rhiRenderTargetPassDescriptor = nullptr; +} + +Scene3DRenderer::QuickRenderer::QuickRenderer() {} +Scene3DRenderer::QuickRenderer::~QuickRenderer() {} + } // namespace Qt3DRender QT_END_NAMESPACE diff --git a/src/quick3d/imports/scene3d/scene3drenderer_p.h b/src/quick3d/imports/scene3d/scene3drenderer_p.h index 5c57f71e3..0f20bb3eb 100644 --- a/src/quick3d/imports/scene3d/scene3drenderer_p.h +++ b/src/quick3d/imports/scene3d/scene3drenderer_p.h @@ -59,6 +59,11 @@ QT_BEGIN_NAMESPACE +class QRhi; +class QRhiTextureRenderTarget; +class QRhiRenderBuffer; +class QRhiTexture; +class QRhiRenderPassDescriptor; class QQuickWindow; class QSGTexture; class QOpenGLFramebufferObject; @@ -74,6 +79,10 @@ class Scene3DCleaner; class Scene3DSGNode; class Scene3DViews; +namespace Render { +class AbstractRenderer; +} + class Scene3DRenderer : public QObject { Q_OBJECT @@ -94,31 +103,83 @@ public: return m_renderAspect; } public Q_SLOTS: - void render(); void shutdown(); void onSceneGraphInvalidated(); void onWindowChanged(QQuickWindow *w); private: - QOpenGLFramebufferObject *createMultisampledFramebufferObject(const QSize &size); - QOpenGLFramebufferObject *createFramebufferObject(const QSize &size); - void beforeSynchronize(); void scheduleRootEntityChange(); + class QuickRenderer + { + public: + // ctor/dtor cannot be inlined because of QScopedPointer with forward + // declared class + QuickRenderer(); + virtual ~QuickRenderer(); + virtual void initialize(Scene3DRenderer *scene3DRenderer, + Qt3DRender::Render::AbstractRenderer *renderer) = 0; + virtual void beforeSynchronize(Scene3DRenderer *scene3DRenderer) = 0; + virtual void beforeRendering(Scene3DRenderer *scene3DRenderer) = 0; + virtual void beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) = 0; + virtual void shutdown(Scene3DRenderer *sceneRenderer) = 0; + + protected: + bool m_lastMultisample = false; + QSize m_lastSize; + QScopedPointer<QSGTexture> m_texture; + Qt3DRender::Render::AbstractRenderer *m_renderer = nullptr; + }; + + class GLRenderer : public QuickRenderer + { + public: + QOpenGLFramebufferObject *createMultisampledFramebufferObject(const QSize &size); + QOpenGLFramebufferObject *createFramebufferObject(const QSize &size); + + void initialize(Scene3DRenderer *scene3DRenderer, + Qt3DRender::Render::AbstractRenderer *renderer) override; + void beforeSynchronize(Scene3DRenderer *scene3DRenderer) override; + void beforeRendering(Scene3DRenderer *scene3DRenderer) override; + void beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) override; + void shutdown(Scene3DRenderer *sceneRenderer) override; + + private: + QScopedPointer<QOpenGLFramebufferObject> m_multisampledFBO; + QScopedPointer<QOpenGLFramebufferObject> m_finalFBO; + bool m_multisample = false; + quint32 m_textureId = 0; + }; + + class RHIRenderer : public QuickRenderer + { + public: + void initialize(Scene3DRenderer *scene3DRenderer, + Qt3DRender::Render::AbstractRenderer *renderer) override; + void beforeSynchronize(Scene3DRenderer *scene3DRenderer) override; + void beforeRendering(Scene3DRenderer *scene3DRenderer) override; + void beforeRenderPassRecording(Scene3DRenderer *scene3DRenderer) override; + void shutdown(Scene3DRenderer *sceneRenderer) override; + + private: + void releaseRHIResources(); + + QRhiTexture *m_rhiTexture = nullptr; + QRhiRenderBuffer *m_rhiColorRenderBuffer = nullptr; + QRhiRenderBuffer *m_rhiMSAARenderBuffer = nullptr; + QRhiRenderBuffer *m_rhiDepthRenderBuffer = nullptr; + QRhiTextureRenderTarget *m_rhiRenderTarget = nullptr; + QRhiRenderPassDescriptor *m_rhiRenderTargetPassDescriptor = nullptr; + QRhi *m_rhi = nullptr; + }; + Scene3DItem *m_item; // Will be released by the QQuickWindow/QML Engine Qt3DCore::QAspectEngine *m_aspectEngine; // Will be released by the Scene3DItem QRenderAspect *m_renderAspect; // Will be released by the aspectEngine - QScopedPointer<QOpenGLFramebufferObject> m_multisampledFBO; - QScopedPointer<QOpenGLFramebufferObject> m_finalFBO; - QScopedPointer<QSGTexture> m_texture; Scene3DSGNode *m_node; // Will be released by the QtQuick SceneGraph QQuickWindow *m_window; QMutex m_windowMutex; - QSize m_lastSize; - bool m_multisample; - bool m_lastMultisample; bool m_needsShutdown; - bool m_forceRecreate; bool m_shouldRender; bool m_dirtyViews; bool m_skipFrame; @@ -126,7 +187,7 @@ private: Scene3DItem::CompositingMode m_compositingMode; QList<Scene3DView *> m_views; bool m_resetRequested = false; - quint32 m_textureId; + QuickRenderer *m_quickRenderer = nullptr; friend class Scene3DItem; }; diff --git a/src/render/backend/abstractrenderer_p.h b/src/render/backend/abstractrenderer_p.h index 6f849128a..2eec1bc1b 100644 --- a/src/render/backend/abstractrenderer_p.h +++ b/src/render/backend/abstractrenderer_p.h @@ -69,6 +69,9 @@ class QScreen; class QOpenGLTexture; class QMouseEvent; class QKeyEvent; +class QRhi; +class QRhiRenderTarget; +class QRhiCommandBuffer; namespace Qt3DCore { class QAbstractFrameAdvanceService; @@ -118,9 +121,16 @@ public: }; Q_DECLARE_FLAGS(BackendNodeDirtySet, BackendNodeDirtyFlag) + enum RenderDriver { + Qt3D, + Scene3D, + }; + virtual void dumpInfo() const = 0; virtual API api() const = 0; + virtual void setRenderDriver(RenderDriver driver) = 0; + virtual RenderDriver renderDriver() const = 0; virtual qint64 time() const = 0; virtual void setTime(qint64 time) = 0; @@ -172,8 +182,11 @@ public: virtual QVariant executeCommand(const QStringList &args) = 0; - // For QtQuick rendering (Scene2D) + // For QtQuick rendering (Scene3D/2D) virtual void setOpenGLContext(QOpenGLContext *ctx) = 0; + virtual void setRHIContext(QRhi *ctx) = 0; + virtual void setDefaultRHIRenderTarget(QRhiRenderTarget *defaultTarget) = 0; + virtual void setRHICommandBuffer(QRhiCommandBuffer *commandBuffer) = 0; virtual void setScreen(QScreen *) {} virtual QScreen *screen() const { return nullptr; } virtual bool accessOpenGLTexture(Qt3DCore::QNodeId nodeId, QOpenGLTexture **texture, QMutex **lock, bool readonly) = 0; diff --git a/src/render/frontend/qrenderapi.h b/src/render/frontend/qrenderapi.h index fc046642a..df1a4a692 100644 --- a/src/render/frontend/qrenderapi.h +++ b/src/render/frontend/qrenderapi.h @@ -51,6 +51,7 @@ enum class API { Vulkan, DirectX, Metal, + RHI, Null }; diff --git a/src/render/frontend/qrenderaspect.cpp b/src/render/frontend/qrenderaspect.cpp index bd6d7d831..3c5c2c053 100644 --- a/src/render/frontend/qrenderaspect.cpp +++ b/src/render/frontend/qrenderaspect.cpp @@ -630,29 +630,6 @@ QRenderAspect::~QRenderAspect() { } -// Called by Scene3DRenderer only -void QRenderAspectPrivate::renderInitialize(QOpenGLContext *context) -{ - if (m_renderer->api() == API::OpenGL) - m_renderer->setOpenGLContext(context); - m_renderer->initialize(); -} - -/*! \internal */ -void QRenderAspectPrivate::render(bool swapBuffers) -{ - m_renderer->render(swapBuffers); -} - -/* - * \internal - * Only called when rendering with QtQuick 2 and a Scene3D item - */ -void QRenderAspectPrivate::renderShutdown() -{ - m_renderer->shutdown(); -} - std::vector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time) { using namespace Render; diff --git a/src/render/frontend/qrenderaspect_p.h b/src/render/frontend/qrenderaspect_p.h index 71dcf0468..b1b7bedc3 100644 --- a/src/render/frontend/qrenderaspect_p.h +++ b/src/render/frontend/qrenderaspect_p.h @@ -71,7 +71,7 @@ QT_BEGIN_NAMESPACE class QSurface; class QScreen; - +class QRhi; namespace Qt3DRender { class QSceneImporter; @@ -113,9 +113,6 @@ public: void unregisterBackendTypes(); void loadSceneParsers(); void loadRenderPlugin(const QString &pluginName); - void renderInitialize(QOpenGLContext *context); - void render(bool swapBuffers = true); - void renderShutdown(); void registerBackendType(const QMetaObject &, const Qt3DCore::QBackendNodeMapperPtr &functor); std::vector<Qt3DCore::QAspectJobPtr> createGeometryRendererJobs() const; std::vector<Qt3DCore::QAspectJobPtr> createPreRendererJobs() const; |