Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qquick3dsimplequadrenderpass_p.h"
++
++ #include <QtQuick3D/private/qquick3dobject_p.h>
++
++ #include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrendererimplshaders_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrendercommands_p.h>
++
++ #include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
++
++ #include <ssg/qssgrendercontextcore.h>
++ #include <ssg/qssgrenderextensions.h>
++
++ #include <rhi/qrhi.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype SimpleQuadRenderer
++ \nativetype QQuick3DSimpleQuadRenderer
++ \inqmlmodule QtQuick3D.Helpers
++ \inherits RenderExtension
++ \since 6.11
++ \brief Renders a full-screen textured quad to the main render target.
++
++ SimpleQuadRenderer is a \l RenderExtension that blits a \l Texture across
++ the entire screen. It runs at the \c PostColor stage in the \c Main render
++ mode, which means it executes after the scene has been drawn but before any
++ post-processing effects, and it writes directly into the \l View3D's main
++ render target, overwriting whatever was drawn by the scene.
++
++ The most common use case is building a fully custom rendering pipeline:
++ set \l {View3D::renderFormat}{View3D.renderFormat} to disable Qt Quick 3D's
++ built-in passes with \c {View3D.DisableInternalPasses}, render the scene
++ content into one or more \l RenderPass targets, and then use
++ SimpleQuadRenderer to composite the result back onto the screen.
++
++ \note SimpleQuadRenderer is automatically registered as an extension with the
++ scene — it does not need to be added to \l {View3D::extensions}{View3D.extensions}
++ manually.
++
++ \qml
++ import QtQuick3D
++ import QtQuick3D.Helpers
++
++ View3D {
++ // Disable the built-in render passes so we can provide our own output
++ renderFormat: View3D.DisableInternalPasses
++
++ // A custom offscreen pass that renders scene content
++ RenderPass {
++ id: mainColorPass
++ commands: [
++ RenderablesFilter { renderableTypes: RenderablesFilter.Opaque | RenderablesFilter.Transparent }
++ ]
++ }
++
++ // Blit the result of the custom pass to the screen
++ SimpleQuadRenderer {
++ texture: Texture {
++ textureProvider: RenderOutputProvider {
++ renderPass: mainColorPass
++ }
++ }
++ }
++ }
++ \endqml
++
++ \sa RenderOutputProvider, RenderPass, View3D
++ */
++
++ /*!
++ \qmlproperty Texture SimpleQuadRenderer::texture
++ The texture to render across the full-screen quad. This can be any \l Texture,
++ including one whose \l {Texture::textureProvider}{textureProvider} is set to a
++ \l RenderOutputProvider to display output from a \l RenderPass or one of Qt Quick 3D's
++ built-in passes (such as the depth texture or AO texture).
++ */
++
++ class QSSGRenderSimpleQuadRenderer : public QSSGRenderExtension
++ {
++ static constexpr FlagT flags = FlagT(Flags::HasGraphicsResources) | FlagT(QSSGRenderGraphObjectUtils::InternalFlags::AutoRegisterExtension);
++ public:
++ QSSGRenderSimpleQuadRenderer()
++ : QSSGRenderExtension(QSSGRenderGraphObject::Type::RenderExtension, flags)
++ {
++ }
++
++ ~QSSGRenderSimpleQuadRenderer() override
++ {
++ if (srb)
++ srb->deleteLater();
++ }
++
++ virtual bool prepareData(QSSGFrameData &data) final
++ {
++ bool wasDirty = true;
++
++ if (image)
++ renderImage = data.contextInterface()->bufferManager()->loadRenderImage(image);
++ else
++ renderImage = QSSGRenderImageTexture();
++
++ wasDirty = (renderImage.m_texture != nullptr);
++
++ return wasDirty;
++ }
++
++ virtual void prepareRender(QSSGFrameData &data) final
++ {
++ if (renderImage.m_texture) {
++ const auto &rhiCtx = data.contextInterface()->rhiContext();
++ const auto &renderer = data.contextInterface()->renderer();
++ QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
++ renderer->rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
++ }
++ }
++
++ virtual void render(QSSGFrameData &data) final
++ {
++ if (renderImage.m_texture) {
++ const auto &rhiCtx = data.contextInterface()->rhiContext();
++ const auto &renderer = data.contextInterface()->renderer();
++ const auto &shaderCache = data.contextInterface()->shaderCache();
++
++ QSSGRhiGraphicsPipelineState ps = data.getPipelineState();
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("QtQuick3D Blit Pass"));
++
++ const auto tonemapMode = QSSGLayerRenderData::getCurrent(data)->layer.tonemapMode;
++ const auto &shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(ps.viewCount, tonemapMode);
++
++ QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge });
++ QSSGRhiShaderResourceBindingList bindings;
++ bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, renderImage.m_texture, sampler);
++ // The texture is often a render pass output that is recreated on resize, so
++ // the srb is owned here and rebuilt when the bindings change. Going through
++ // the shared srb cache would leave an entry behind for every texture that was
++ // ever bound, and since the bindings carry nothing unique to this renderer,
++ // releasing the previous entry could pull it from under another renderer
++ // showing the same texture.
++ if (!srb || bindings != srbBindings) {
++ // render() can be called more than once in a frame, so the previous srb
++ // may already be referenced by commands recorded for this frame, so defer
++ // deletion until the next frame.
++ if (srb)
++ srb->deleteLater();
++ srb = rhiCtx->rhi()->newShaderResourceBindings();
++ srb->setBindings(bindings.v, bindings.v + bindings.p);
++ srbBindings = bindings;
++ if (!srb->create()) {
++ qWarning("Failed to build srb for the simple quad renderer");
++ srb->deleteLater();
++ srb = nullptr;
++ cb->debugMarkEnd();
++ return;
++ }
++ }
++
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, shaderPipeline.get());
++ renderer->rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(), QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::PremulBlend);
++ cb->debugMarkEnd();
++ }
++ }
++ virtual void resetForFrame() final {}
++ virtual RenderMode mode() const final { return RenderMode::Main; }
++ virtual RenderStage stage() const final { return RenderStage::PostColor; }
++
++ QSSGRenderImage *image = nullptr;
++ QSSGRenderImageTexture renderImage;
++ QRhiShaderResourceBindings *srb = nullptr;
++ QSSGRhiShaderResourceBindingList srbBindings;
++ QSSGRhiShaderPipelinePtr quadShaderPipeline;
++ };
++
++
++ QQuick3DSimpleQuadRenderer::QQuick3DSimpleQuadRenderer() { }
++
++ QSSGRenderGraphObject *QQuick3DSimpleQuadRenderer::updateSpatialNode(QSSGRenderGraphObject *node)
++ {
++ if (!node)
++ node = new QSSGRenderSimpleQuadRenderer();
++
++ QSSGRenderSimpleQuadRenderer *blitPass = static_cast<QSSGRenderSimpleQuadRenderer *>(node);
++ if (m_source)
++ blitPass->image = m_source->getRenderImage();
++ else
++ blitPass->image = nullptr;
++
++ return node;
++ }
++
++ void QQuick3DSimpleQuadRenderer::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
++ {
++ if (change == QQuick3DObject::ItemSceneChange)
++ updateSceneManager(value.sceneManager);
++ }
++
++ QQuick3DTexture *QQuick3DSimpleQuadRenderer::texture() const
++ {
++ return m_source;
++ }
++
++ void QQuick3DSimpleQuadRenderer::setTexture(QQuick3DTexture *newSource)
++ {
++ if (m_source == newSource)
++ return;
++
++ QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DSimpleQuadRenderer::setTexture, newSource, m_source);
++
++ m_source = newSource;
++ emit textureChanged();
++
++ update();
++ }
++
++ void QQuick3DSimpleQuadRenderer::updateSceneManager(QQuick3DSceneManager *sceneManager)
++ {
++ if (sceneManager)
++ QQuick3DObjectPrivate::refSceneManager(m_source, *sceneManager);
++ else
++ QQuick3DObjectPrivate::derefSceneManager(m_source);
++ }
++
++ QT_END_NAMESPACE