Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qssgrenderpass_p.h"
++ #include "qssgrhiquadrenderer_p.h"
++ #include "qssglayerrenderdata_p.h"
++ #include "qssgrendercontextcore.h"
++ #include "qssgdebugdrawsystem_p.h"
++ #include "extensionapi/qssgrenderextensions.h"
++ #include "qssgrenderhelpers_p.h"
++ #include "qssgrendercommands_p.h"
++ #include "graphobjects/qssgrenderskymaterial_p.h"
++
++ #include "../utils/qssgassert_p.h"
++
++ #include <QtQuick/private/qsgrenderer_p.h>
++ #include <qtquick3d_tracepoints_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ static const char defaultFragOutputs[][12] = {
++ "fragOutput",
++ "fragOutput1",
++ "fragOutput2",
++ "fragOutput3",
++ };
++
++ static QByteArrayView getDefaultOutputName(size_t index)
++ {
++ QSSG_ASSERT(std::size(defaultFragOutputs) > index, return defaultFragOutputs[0]);
++ return defaultFragOutputs[index];
++ }
++
++ static inline QMatrix4x4 correctMVPForScissor(QRectF viewportRect, QRect scissorRect, bool isYUp) {
++ const auto &scissorCenter = scissorRect.center();
++ const auto &viewCenter = viewportRect.center();
++ const float scaleX = viewportRect.width() / float(scissorRect.width());
++ const float scaleY = viewportRect.height() / float(scissorRect.height());
++ const float dx = 2 * (viewCenter.x() - scissorCenter.x()) / scissorRect.width();
++ const float dyRect = isYUp ? (scissorCenter.y() - viewCenter.y())
++ : (viewCenter.y() - scissorCenter.y());
++ const float dy = 2 * dyRect / scissorRect.height();
++
++ return QMatrix4x4(scaleX, 0.0f, 0.0f, dx,
++ 0.0f, scaleY, 0.0f, dy,
++ 0.0f, 0.0f, 1.0f, 0.0f,
++ 0.0f, 0.0f, 0.0f, 1.0f);
++ }
++
++ QSSGRenderPass::~QSSGRenderPass()
++ {
++
++ }
++
++
++ // MOTION VECTOR PASS
++
++ void MotionVectorMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ Q_UNUSED(renderer)
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ camera = data.renderedCameras[0];
++ QMatrix4x4 viewProjection(Qt::Uninitialized);
++ QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
++ cameraGlobalTransform = data.getGlobalTransform(*camera);
++ data.renderedCameras[0]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
++
++ const auto &layerPrepResult = data.layerPrepResult;
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++
++ const auto &renderedOpaques = data.getSortedOpaqueRenderableObjects(*camera);
++ const auto &renderedTransparent = data.getSortedTransparentRenderableObjects(*camera);
++
++ rhiMotionVectorTexture = data.getRenderResult(QSSGRenderResult::Key::MotionVectorTexture);
++ bool textureReady = rhiMotionVectorTexture &&
++ rhiPrepareMotionVectorTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiMotionVectorTexture);
++
++ if (!textureReady) {
++ rhiMotionVectorTexture = nullptr;
++ enabled = false;
++ return;
++ }
++
++ motionVectorMapManager = data.requestMotionVectorMapManager();
++ ps = data.getPipelineState();
++ ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled,
++ QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
++ ps.samples = 1;
++ ps.viewCount = data.layer.viewCount;
++
++ for (int i = 0; i < MaxBuckets; ++i)
++ motionVectorPassObjects[i].clear();
++ enabled = false;
++
++ for (const auto &handles : { &renderedOpaques, &renderedTransparent }) {
++ for (const auto &handle : *handles) {
++ if (handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
++ handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
++ QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(handle.obj));
++ if (handle.obj->renderableFlags.isMotionVectorParticipant()) {
++ if (renderable->modelContext.model.instancing() && renderable->modelContext.model.instanceCount() == 0)
++ continue;
++ bool skin = renderable->modelContext.model.usesBoneTexture();
++ bool instance = renderable->modelContext.model.instanceCount() > 0;
++ bool morph = renderable->modelContext.model.morphTargets.size() > 0;
++ int bucketIndex = (int(skin) << 2) | (int(instance) << 1) | int(morph);
++ motionVectorPassObjects[bucketIndex].push_back(handle);
++
++ QSSGRhiGraphicsPipelineState tempPs = ps;
++
++ rhiPrepareMotionVectorRenderable(rhiCtx.get(),
++ this,
++ data,
++ viewProjection,
++ *handle.obj,
++ rhiMotionVectorTexture->rpDesc,
++ &tempPs,
++ *motionVectorMapManager);
++ enabled = true;
++ }
++ }
++ }
++ }
++ }
++
++ void MotionVectorMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++ if (enabled) {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D motion vector map"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D motion vector map"));
++
++ if (Q_LIKELY(rhiMotionVectorTexture && rhiMotionVectorTexture->isValid())) {
++ cb->beginPass(rhiMotionVectorTexture->rt, QColor(0, 0, 0, 0), { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
++ QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiMotionVectorTexture->rt));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ rhiRenderMotionVector(rhiCtx.get(),
++ ps,
++ motionVectorPassObjects,
++ MaxBuckets);
++
++ cb->endPass();
++ QSSGRHICTX_STAT(rhiCtx, endRenderPass());
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("motion_vector_map"));
++ }
++ cb->debugMarkEnd();
++ }
++ }
++
++ void MotionVectorMapPass::resetForFrame()
++ {
++ QSSG_CHECK(motionVectorMapManager);
++ motionVectorMapManager->endFrame();
++ camera = nullptr;
++ ps = {};
++ rhiMotionVectorTexture = nullptr;
++ for (int i = 0; i < MaxBuckets; ++i)
++ motionVectorPassObjects[i].clear();
++ }
++
++ // SHADOW PASS
++
++ void ShadowMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ Q_UNUSED(renderer)
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ camera = data.renderedCameras[0];
++
++ QSSG_ASSERT(shadowPassObjects.isEmpty(), shadowPassObjects.clear());
++
++ data.getShadowCastingObjects(*camera, shadowPassObjects, castingObjectsBox, receivingObjectsBox);
++
++ globalLights = data.globalLights;
++ enabled = !shadowPassObjects.isEmpty() || !globalLights.isEmpty();
++
++ if (enabled) {
++ shadowMapManager = data.requestShadowMapManager();
++
++ ps = data.getPipelineState();
++ ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
++ // Try reducing self-shadowing and artifacts.
++ ps.depthBias = 2;
++ ps.slopeScaledDepthBias = 1.5f;
++
++ if (!debugCamera) {
++ debugCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderGraphObject::Type::OrthographicCamera);
++ }
++ }
++ }
++
++ void ShadowMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: Sorted opaque and transparent + depth, global lights (scoped lights not supported) and camera.
++
++ // DEPENDECY: None
++
++ // OUTPUT: Texture (Shadowmap Texture Atlas)
++
++ // CONDITION: Lights (shadowPassObjects)
++
++ if (enabled) {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D shadow map"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D shadow map"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ QSSG_CHECK(shadowMapManager);
++ rhiRenderShadowMap(rhiCtx.get(),
++ this,
++ ps,
++ *shadowMapManager,
++ *camera,
++ debugCamera.get(),
++ globalLights, // scoped lights are not relevant here
++ shadowPassObjects,
++ renderer,
++ castingObjectsBox,
++ receivingObjectsBox);
++
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
++ }
++ }
++
++ void ShadowMapPass::resetForFrame()
++ {
++ enabled = false;
++ camera = nullptr;
++ castingObjectsBox = {};
++ receivingObjectsBox = {};
++ ps = {};
++ shadowPassObjects.clear();
++ globalLights.clear();
++ }
++
++ // REFLECTIONMAP PASS
++
++ ReflectionMapPass::ReflectionMapPass()
++ {
++ // Read the environment variable to check if the old behavior of including the screen texture objects
++ // in the reflection pass should be kept for compatibility reasons.
++ // Besides being expensive, we shouldn't be using the main screen texture in the probes at all.
++ m_includeSTO = qEnvironmentVariableIntValue("QT_QUICK3D_REFLECTION_PASS_INCLUDE_SCREEN_TEXTURE_OBJECTS") != 0;
++ }
++
++ void ReflectionMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ Q_UNUSED(renderer);
++ Q_UNUSED(data);
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ ps = data.getPipelineState();
++ ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled,
++ QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled,
++ QSSGRhiGraphicsPipelineState::Flag::BlendEnabled };
++
++ QSSG_ASSERT(data.reflectionProbesView.size() != 0, return);
++
++ reflectionProbes = { data.reflectionProbesView.begin(), data.reflectionProbesView.end() };
++ reflectionMapManager = data.requestReflectionMapManager();
++
++ const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++ const auto &sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
++ QSSGRenderableObjectList emptyList{};
++ const auto &sortedScreenTextureObjects = m_includeSTO ? data.getSortedScreenTextureRenderableObjects(*camera) : emptyList;
++
++ QSSG_ASSERT(reflectionPassObjects.isEmpty(), reflectionPassObjects.clear());
++
++ // NOTE: We should consider keeping track of the reflection casting objects to avoid
++ // filtering this list on each prep.
++ for (const auto &handles : { &sortedOpaqueObjects, &sortedTransparentObjects, &sortedScreenTextureObjects }) {
++ for (const auto &handle : *handles) {
++ if (handle.obj->renderableFlags.testFlag(QSSGRenderableObjectFlag::CastsReflections))
++ reflectionPassObjects.push_back(handle);
++ }
++ }
++ }
++
++ void ReflectionMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: Reflection probes, sorted opaque and transparent
++
++ // DEPENDECY: None
++
++ // OUTPUT: Cube maps (1 per probe)
++
++ // NOTE: Full pass with a sky box pass
++
++ // CONDITION: Probes and sorted opaque and transparent
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ const auto *layerData = QSSGLayerRenderData::getCurrent(renderer);
++ QSSG_ASSERT(layerData, return);
++
++ QSSG_CHECK(reflectionMapManager);
++ if (!reflectionPassObjects.isEmpty() || (reflectionProbes.size() != 0)) {
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D reflection map"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D reflection map"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ rhiRenderReflectionMap(*renderer.contextInterface(), this, *layerData, &ps, *reflectionMapManager, reflectionProbes, reflectionPassObjects, renderer);
++
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("reflection_map"));
++ }
++ }
++
++ void ReflectionMapPass::resetForFrame()
++ {
++ ps = {};
++ reflectionProbes.clear();
++ reflectionPassObjects.clear();
++ }
++
++ // SKY MATERIAL PASS
++
++ void SkyMaterialPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ Q_UNUSED(renderer);
++ Q_UNUSED(data);
++ }
++
++ void SkyMaterialPass::renderPass(QSSGRenderer &renderer)
++ {
++ auto layerData = QSSGLayerRenderData::getCurrent(renderer);
++ QSSG_ASSERT(layerData, return);
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D Sky Material"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D Sky Material"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ layerData->resolveLayerIblTexture(this);
++
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("sky_material"));
++ }
++
++ void SkyMaterialPass::resetForFrame() { }
++
++ // ZPrePass
++ void ZPrePassPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: Item2Ds + depth write + depth prepass
++
++ // DEPENDECY: none
++
++ // OUTPUT: Depth buffer attchment for current target
++
++ // NOTE: Could we make the depth pass more complete and just do a blit here?
++ //
++ // 1. If we have a depth map, just do a blit and then update with the rest
++ // 2. If we don't have a depth map (and/or SSAO) consider using a lower lod level.
++
++ // CONDITION: Input + globally enabled or ?
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ ps = data.getPipelineState();
++
++ renderedDepthWriteObjects = data.getSortedRenderedDepthWriteObjects(*camera);
++ renderedOpaqueDepthPrepassObjects = data.getSortedrenderedOpaqueDepthPrepassObjects(*camera);
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare Z prepass"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D prepare Z prepass"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ active = rhiPrepareDepthPass(rhiCtx.get(), this, ps, rhiCtx->mainRenderPassDescriptor(), data,
++ renderedDepthWriteObjects, renderedOpaqueDepthPrepassObjects,
++ rhiCtx->mainPassSampleCount(), data.layer.viewCount);
++ data.setZPrePassPrepResult(active);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("prepare_z_prepass"));
++ }
++
++ void ZPrePassPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++
++ bool needsSetViewport = true;
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ if (active) {
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render Z prepass"));
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render Z prepass"));
++ rhiRenderDepthPass(rhiCtx.get(), ps, renderedDepthWriteObjects, renderedOpaqueDepthPrepassObjects, &needsSetViewport);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_z_prepass"));
++ }
++ }
++
++ void ZPrePassPass::resetForFrame()
++ {
++ renderedDepthWriteObjects.clear();
++ renderedOpaqueDepthPrepassObjects.clear();
++ ps = {};
++ active = false;
++ }
++
++ // SSAO PASS
++ void SSAOMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ // Assumption for now is that all passes are keept alive and only reset once a frame is done.
++ // I.e., holding data like this should be safe (If that's no longer the case we need to do ref counting
++ // for shared data).
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++
++ rhiAoTexture = data.getRenderResult(QSSGRenderResult::Key::AoTexture);
++ rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
++ QSSG_ASSERT_X(!data.renderedCameras.isEmpty(), "Preparing AO pass failed, missing camera", return);
++ camera = data.renderedCameras[0];
++ QSSG_ASSERT_X((rhiDepthTexture && rhiDepthTexture->isValid()), "Preparing AO pass failed, missing equired texture(s)", return);
++
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ ssaoShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSsaoShader(data.layer.viewCount);
++ aoSettings = { data.layer.aoStrength, data.layer.aoDistance, data.layer.aoSoftness, data.layer.aoBias, data.layer.aoSamplerate, data.layer.aoDither };
++
++ ps = data.getPipelineState();
++ const auto &layerPrepResult = data.layerPrepResult;
++ const bool ready = rhiAoTexture && rhiPrepareAoTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiAoTexture, data.layer.viewCount);
++
++ if (Q_UNLIKELY(!ready))
++ rhiAoTexture = nullptr;
++ }
++
++ void SSAOMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: Camera + depth map
++
++ // DEPENDECY: Depth map (zprepass)
++
++ // OUTPUT: AO Texture
++
++ // NOTE:
++
++ // CONDITION: SSAO enabled
++ QSSG_ASSERT(camera && rhiDepthTexture && rhiDepthTexture->isValid(), return);
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D SSAO map"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D SSAO map"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ if (Q_LIKELY(rhiAoTexture && rhiAoTexture->isValid())) {
++ rhiRenderAoTexture(rhiCtx.get(),
++ this,
++ renderer,
++ *ssaoShaderPipeline,
++ ps,
++ aoSettings,
++ *rhiAoTexture,
++ *rhiDepthTexture,
++ *camera);
++ }
++
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("ssao_map"));
++ }
++
++ void SSAOMapPass::resetForFrame()
++ {
++ rhiDepthTexture = nullptr;
++ rhiAoTexture = nullptr;
++ camera = nullptr;
++ ps = {};
++ aoSettings = {};
++ }
++
++ // DEPTH TEXTURE PASS
++ void DepthMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ const auto &layerPrepResult = data.layerPrepResult;
++ bool ready = false;
++ ps = data.getPipelineState();
++
++ if (m_multisampling) {
++ ps.samples = rhiCtx->mainPassSampleCount();
++ rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTextureMS);
++ } else {
++ ps.samples = 1;
++ rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
++ }
++
++ if (Q_LIKELY(rhiDepthTexture && rhiPrepareDepthTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiDepthTexture, data.layer.viewCount, ps.samples))) {
++ sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++ sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
++ // the depth texture is always non-MSAA, but is a 2D array with multiview
++ ready = rhiPrepareDepthPass(rhiCtx.get(), this, ps, rhiDepthTexture->rpDesc, data,
++ sortedOpaqueObjects, sortedTransparentObjects,
++ ps.samples, data.layer.viewCount);
++ }
++
++ if (Q_UNLIKELY(!ready))
++ rhiDepthTexture = nullptr;
++ }
++
++ void DepthMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: sorted objects (opaque + transparent) (maybe...)
++
++ // DEPENDECY: If this is only used for the AO case, that dictates if this should be done or not.
++
++ // OUTPUT: Texture
++
++ // NOTE: Why are we prepping opaque + transparent object if we're not using them? And why are we staying compatible with 5.15?
++ // Only used for AO? Merge into the AO pass?
++
++ // NOTES:
++ //
++ // 1: If requested, use this and blit it in the z-pre pass.
++ // 2. Why are we handling the transparent objects in the render prep (only)?
++
++ // CONDITION:
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D depth texture"));
++
++ if (Q_LIKELY(rhiDepthTexture && rhiDepthTexture->isValid())) {
++ bool needsSetViewport = true;
++ cb->beginPass(rhiDepthTexture->rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
++ QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiDepthTexture->rt));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ // NB! We do not pass sortedTransparentObjects in the 4th
++ // argument to stay compatible with the 5.15 code base,
++ // which also does not include semi-transparent objects in
++ // the depth texture. In addition, capturing after the
++ // opaque pass, not including transparent objects, is part
++ // of the contract for screen reading custom materials,
++ // both for depth and color.
++ rhiRenderDepthPass(rhiCtx.get(), ps, sortedOpaqueObjects, {}, &needsSetViewport);
++ cb->endPass();
++ QSSGRHICTX_STAT(rhiCtx, endRenderPass());
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("depth_texture"));
++ }
++
++ cb->debugMarkEnd();
++ }
++
++ void DepthMapPass::resetForFrame()
++ {
++ rhiDepthTexture = nullptr;
++ sortedOpaqueObjects.clear();
++ sortedTransparentObjects.clear();
++ ps = {};
++ }
++
++ // NORMAL TEXTURE PASS
++
++ void NormalPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QRhi *rhi = rhiCtx->rhi();
++ QSSG_ASSERT(rhi->isRecordingFrame(), return);
++ const auto &layerPrepResult = data.layerPrepResult;
++
++ // the normal texture is not multiview-dependent and it is always a 2D texture
++ // (so not an array with multiview either)
++
++ // the normal texture is always non-MSAA
++
++ ps = data.getPipelineState();
++ ps.samples = 1;
++ ps.viewCount = 1;
++
++ sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++
++ // transparent objects are not included in the normal texture pass
++
++ QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
++ shaderFeatures.set(QSSGShaderFeatures::Feature::NormalPass, true);
++
++ normalTexture = data.getRenderResult(QSSGRenderResult::Key::NormalTexture);
++
++ const QSize size = layerPrepResult.textureDimensions();
++ bool needsBuild = false;
++
++ if (!normalTexture->texture) {
++ QRhiTexture::Format format = QRhiTexture::RGBA16F;
++ if (!rhi->isTextureFormatSupported(format)) {
++ qWarning("No float formats, not great");
++ format = QRhiTexture::RGBA8;
++ }
++ normalTexture->texture = rhiCtx->rhi()->newTexture(format, size, 1, QRhiTexture::RenderTarget);
++ needsBuild = true;
++ normalTexture->texture->setName(QByteArrayLiteral("Normal texture"));
++ } else if (normalTexture->texture->pixelSize() != size) {
++ normalTexture->texture->setPixelSize(size);
++ needsBuild = true;
++ }
++
++ if (!normalTexture->depthStencil) {
++ normalTexture->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
++ needsBuild = true;
++ } else if (normalTexture->depthStencil->pixelSize() != size) {
++ normalTexture->depthStencil->setPixelSize(size);
++ needsBuild = true;
++ }
++
++ if (needsBuild) {
++ if (!normalTexture->texture->create()) {
++ qWarning("Failed to build normal texture (size %dx%d, format %d)",
++ size.width(), size.height(), int(normalTexture->texture->format()));
++ normalTexture->reset();
++ return;
++ }
++
++ if (!normalTexture->depthStencil->create()) {
++ qWarning("Failed to build depth-stencil buffer for normal texture (size %dx%d)",
++ size.width(), size.height());
++ normalTexture->reset();
++ return;
++ }
++
++ normalTexture->resetRenderTarget();
++
++ QRhiTextureRenderTargetDescription rtDesc;
++ QRhiColorAttachment colorAttachment(normalTexture->texture);
++ rtDesc.setColorAttachments({ colorAttachment });
++ rtDesc.setDepthStencilBuffer(normalTexture->depthStencil);
++
++ normalTexture->rt = rhi->newTextureRenderTarget(rtDesc);
++ normalTexture->rt->setName(QByteArrayLiteral("Normal texture RT"));
++ normalTexture->rpDesc = normalTexture->rt->newCompatibleRenderPassDescriptor();
++ normalTexture->rt->setRenderPassDescriptor(normalTexture->rpDesc);
++ if (!normalTexture->rt->create()) {
++ qWarning("Failed to build render target for normal texture");
++ normalTexture->reset();
++ return;
++ }
++ }
++
++ rhiPrepareNormalPass(rhiCtx.get(), this, ps, normalTexture->rpDesc, data, sortedOpaqueObjects);
++ }
++
++ void NormalPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D normal texture"));
++
++ if (Q_LIKELY(normalTexture && normalTexture->isValid())) {
++ bool needsSetViewport = true;
++ cb->beginPass(normalTexture->rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
++ QSSGRHICTX_STAT(rhiCtx, beginRenderPass(normalTexture->rt));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ rhiRenderNormalPass(rhiCtx.get(), ps, sortedOpaqueObjects, &needsSetViewport);
++
++ cb->endPass();
++ QSSGRHICTX_STAT(rhiCtx, endRenderPass());
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("normal_texture"));
++ }
++
++ cb->debugMarkEnd();
++ }
++
++ void NormalPass::resetForFrame()
++ {
++ normalTexture = nullptr;
++ depthBuffer = nullptr;
++ sortedOpaqueObjects.clear();
++ ps = {};
++ }
++
++ // SCREEN TEXTURE PASS
++
++ void ScreenMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ rhiScreenTexture = data.getRenderResult(QSSGRenderResult::Key::ScreenTexture);
++ auto &layer = data.layer;
++ const auto &layerPrepResult = data.layerPrepResult;
++ wantsMips = layerPrepResult.getFlags().requiresMipmapsForScreenTexture();
++ sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++ ps = data.getPipelineState();
++ ps.samples = 1; // screen texture is always non-MSAA
++ ps.viewCount = data.layer.viewCount; // but is a 2D texture array when multiview
++ if (ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled))
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, true);
++
++ if (layer.background == QSSGRenderLayer::Background::Color)
++ clearColor = QColor::fromRgbF(layer.clearColor.x(), layer.clearColor.y(), layer.clearColor.z());
++
++ if (rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
++ if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap) {
++ if (!skyboxCubeMapPass)
++ skyboxCubeMapPass = SkyboxCubeMapPass();
++
++ skyboxCubeMapPass->skipTonemapping = true;
++ skyboxCubeMapPass->renderPrep(renderer, data);
++
++ // The pass expects to output to the main render target, but we have
++ // our own texture here, possibly with a differing sample count, so
++ // override the relevant settings once renderPrep() is done.
++ skyboxCubeMapPass->ps.samples = ps.samples;
++
++ skyboxPass = std::nullopt;
++ } else if ((layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe)
++ || (layer.background == QSSGRenderLayer::Background::SkyMaterial && layer.skyMaterial)) {
++ if (!skyboxPass)
++ skyboxPass = SkyboxPass();
++
++ skyboxPass->skipTonemapping = true;
++ skyboxPass->renderPrep(renderer, data);
++
++ skyboxPass->ps.samples = ps.samples;
++
++ skyboxCubeMapPass = std::nullopt;
++ }
++ }
++
++ bool ready = false;
++ if (Q_LIKELY(rhiScreenTexture && rhiPrepareScreenTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), wantsMips, rhiScreenTexture, layer.viewCount))) {
++ ready = true;
++ if (skyboxCubeMapPass)
++ skyboxCubeMapPass->rpDesc = rhiScreenTexture->rpDesc;
++ if (skyboxPass)
++ skyboxPass->rpDesc = rhiScreenTexture->rpDesc;
++ // NB: not compatible with disabling LayerEnableDepthTest
++ // because there are effectively no "opaque" objects then.
++ // Disable Tonemapping for all materials in the screen pass texture
++ shaderFeatures = data.getShaderFeatures();
++ shaderFeatures.disableTonemapping();
++ const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++ for (const auto &handle : sortedOpaqueObjects) {
++ // Reflection cube maps are not available at this point, make sure they are turned off.
++ bool recRef = handle.obj->renderableFlags.receivesReflections();
++ handle.obj->renderableFlags.setReceivesReflections(false);
++ rhiPrepareRenderableForScreenMapPass(rhiCtx.get(), this, data, *handle.obj, rhiScreenTexture->rpDesc, &ps, shaderFeatures, 1, data.layer.viewCount);
++ handle.obj->renderableFlags.setReceivesReflections(recRef);
++ }
++ }
++
++ if (Q_UNLIKELY(!ready))
++ rhiScreenTexture = nullptr;
++ }
++
++ void ScreenMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ // INPUT: Sorted opaque objects + depth objects
++
++ // DEPENDECY: Depth pass (if enabled)
++
++ // OUTPUT: Texture (screen texture).
++
++ // NOTE: Used for refrection and effects (?)
++
++ // CONDITION:
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D screen texture"));
++
++ if (Q_LIKELY(rhiScreenTexture && rhiScreenTexture->isValid())) {
++ cb->beginPass(rhiScreenTexture->rt, clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
++ QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiScreenTexture->rt));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ bool needsSetViewport = true;
++ for (const auto &handle : std::as_const(sortedOpaqueObjects))
++ rhiRenderRenderable(rhiCtx.get(), ps, *handle.obj, &needsSetViewport);
++
++ if (skyboxCubeMapPass)
++ skyboxCubeMapPass->renderPass(renderer);
++ else if (skyboxPass)
++ skyboxPass->renderPass(renderer);
++
++ QRhiResourceUpdateBatch *rub = nullptr;
++ if (wantsMips) {
++ rub = rhiCtx->rhi()->nextResourceUpdateBatch();
++ rub->generateMips(rhiScreenTexture->texture);
++ }
++ cb->endPass(rub);
++ QSSGRHICTX_STAT(rhiCtx, endRenderPass());
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture"));
++ }
++
++ cb->debugMarkEnd();
++ }
++
++ void ScreenMapPass::resetForFrame()
++ {
++ rhiScreenTexture = nullptr;
++ if (skyboxPass)
++ skyboxPass->resetForFrame();
++ if (skyboxCubeMapPass)
++ skyboxCubeMapPass->resetForFrame();
++ ps = {};
++ wantsMips = false;
++ clearColor = Qt::transparent;
++ shaderFeatures = {};
++ sortedOpaqueObjects.clear();
++ }
++
++ void ScreenReflectionPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ rhiScreenTexture = data.getRenderResult(QSSGRenderResult::Key::ScreenTexture);
++ QSSG_ASSERT_X(rhiScreenTexture && rhiScreenTexture->isValid(), "Invalid screen texture!", return);
++
++ const auto &layer = data.layer;
++ const auto shaderFeatures = data.getShaderFeatures();
++ const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
++
++ QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
++ const int samples = rhiCtx->mainPassSampleCount();
++ const int viewCount = data.layer.viewCount;
++
++ // NOTE: We're piggybacking on the screen map pass for now, but we could do better.
++ ps = data.getPipelineState();
++ const bool depthTestEnabled = (data.screenMapPass.ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled));
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, depthTestEnabled);
++ const bool depthWriteEnabled = (data.screenMapPass.ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled));
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, depthWriteEnabled);
++ sortedScreenTextureObjects = data.getSortedScreenTextureRenderableObjects(*camera);
++ for (const auto &handle : std::as_const(sortedScreenTextureObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ const auto depthWriteMode = theObject->depthWriteMode;
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, theObject->renderableFlags.hasTransparency());
++ const bool curDepthWriteEnabled = !(depthWriteMode == QSSGDepthDrawMode::Never || depthWriteMode == QSSGDepthDrawMode::OpaquePrePass
++ || data.isZPrePassActive() || !layerEnableDepthTest);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
++ RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), this, data, *theObject, mainRpDesc, &ps, shaderFeatures, samples, viewCount);
++ }
++ }
++
++ void ScreenReflectionPass::renderPass(QSSGRenderer &renderer)
++ {
++ if (QSSG_GUARD(rhiScreenTexture && rhiScreenTexture->isValid())) {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ // 3. Screen texture depended objects
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render screen texture dependent"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render screen texture dependent"));
++ bool needsSetViewport = true;
++ for (const auto &handle : std::as_const(sortedScreenTextureObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
++ }
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture_dependent"));
++ Q_TRACE(QSSG_renderPass_exit);
++ }
++ }
++
++ void ScreenReflectionPass::resetForFrame()
++ {
++ sortedScreenTextureObjects.clear();
++ rhiScreenTexture = nullptr;
++ ps = {};
++ }
++
++ void OpaquePass::prep(const QSSGRenderContextInterface &ctx,
++ QSSGLayerRenderData &data,
++ QSSGPassKey passKey,
++ QSSGRhiGraphicsPipelineState &ps,
++ QSSGShaderFeatures shaderFeatures,
++ QRhiRenderPassDescriptor *rpDesc,
++ const QSSGRenderableObjectList &sortedOpaqueObjects)
++ {
++ const auto &rhiCtx = ctx.rhiContext();
++ QSSG_ASSERT(rpDesc && rhiCtx->rhi()->isRecordingFrame(), return);
++
++ const auto &layer = data.layer;
++ const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
++
++ for (const auto &handle : std::as_const(sortedOpaqueObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ const auto depthWriteMode = theObject->depthWriteMode;
++ const bool curDepthWriteEnabled = !(depthWriteMode == QSSGDepthDrawMode::Never ||
++ depthWriteMode == QSSGDepthDrawMode::OpaquePrePass ||
++ data.isZPrePassActive() || !layerEnableDepthTest);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
++ RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), passKey, data, *theObject, rpDesc, &ps, shaderFeatures, ps.samples, ps.viewCount);
++ }
++ }
++
++ void OpaquePass::render(const QSSGRenderContextInterface &ctx,
++ const QSSGRhiGraphicsPipelineState &ps,
++ const QSSGRenderableObjectList &sortedOpaqueObjects)
++ {
++ const auto &rhiCtx = ctx.rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ bool needsSetViewport = true;
++ for (const auto &handle : std::as_const(sortedOpaqueObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
++ }
++ }
++
++ void OpaquePass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++ ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
++
++ // opaque objects (or, this list is empty when LayerEnableDepthTest is disabled)
++ sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
++ shaderFeatures = data.getShaderFeatures();
++
++ QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
++ prep(*ctx, data, this, ps, shaderFeatures, mainRpDesc, sortedOpaqueObjects);
++ }
++
++ void OpaquePass::renderPass(QSSGRenderer &renderer)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render opaque"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render opaque"));
++ render(*ctx, ps, sortedOpaqueObjects);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("opaque_pass"));
++ Q_TRACE(QSSG_renderPass_exit);
++ }
++
++ void OpaquePass::resetForFrame()
++ {
++ sortedOpaqueObjects.clear();
++ ps = {};
++ shaderFeatures = {};
++ }
++
++ void TransparentPass::prep(const QSSGRenderContextInterface &ctx,
++ QSSGLayerRenderData &data,
++ QSSGPassKey passKey,
++ QSSGRhiGraphicsPipelineState &ps,
++ QSSGShaderFeatures shaderFeatures,
++ QRhiRenderPassDescriptor *rpDesc,
++ const QSSGRenderableObjectList &sortedTransparentObjects,
++ bool oit)
++ {
++ const auto &rhiCtx = ctx.rhiContext();
++ QSSG_ASSERT(rpDesc && rhiCtx->rhi()->isRecordingFrame(), return);
++
++ const bool zPrePassActive = data.isZPrePassActive();
++ for (const auto &handle : std::as_const(sortedTransparentObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ const auto depthWriteMode = theObject->depthWriteMode;
++ const bool curDepthWriteEnabled = (depthWriteMode == QSSGDepthDrawMode::Always && !zPrePassActive);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
++ if (!(theObject->renderableFlags.isCompletelyTransparent())) {
++ RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), passKey, data, *theObject, rpDesc, &ps, shaderFeatures,
++ ps.samples, ps.viewCount, nullptr, nullptr, QSSGRenderTextureCubeFaceNone, nullptr, oit);
++ }
++ }
++ }
++
++ void TransparentPass::render(const QSSGRenderContextInterface &ctx,
++ const QSSGRhiGraphicsPipelineState &ps,
++ const QSSGRenderableObjectList &sortedTransparentObjects)
++ {
++ const auto &rhiCtx = ctx.rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ // If scissorRect is set, Item2Ds will be drawn by a workaround of modifying
++ // viewport, not using actual 3D scissor test.
++ // It means non-opaque objects may be affected by this viewport setting.
++ bool needsSetViewport = true;
++ for (const auto &handle : std::as_const(sortedTransparentObjects)) {
++ QSSGRenderableObject *theObject = handle.obj;
++ if (!theObject->renderableFlags.isCompletelyTransparent())
++ RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
++ }
++ }
++
++ void TransparentPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
++
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++
++ // transparent objects (or, without LayerEnableDepthTest, all objects)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
++
++ shaderFeatures = data.getShaderFeatures();
++ sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
++
++ prep(*ctx, data, this, ps, shaderFeatures, mainRpDesc, sortedTransparentObjects);
++ }
++
++ void TransparentPass::renderPass(QSSGRenderer &renderer)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render alpha"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render alpha"));
++ render(*ctx, ps, sortedTransparentObjects);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_pass"));
++ Q_TRACE(QSSG_renderPass_exit);
++ }
++
++ void TransparentPass::resetForFrame()
++ {
++ sortedTransparentObjects.clear();
++ ps = {};
++ shaderFeatures = {};
++ }
++
++ static void skyMaterialTonemapFeatures(QSSGRenderLayer::TonemapMode mode, QSSGShaderFeatures &features, quint32 &key)
++ {
++ switch (mode) {
++ case QSSGRenderLayer::TonemapMode::Linear:
++ features.set(QSSGShaderFeatures::Feature::LinearTonemapping, true);
++ key = 1;
++ break;
++ case QSSGRenderLayer::TonemapMode::Aces:
++ features.set(QSSGShaderFeatures::Feature::AcesTonemapping, true);
++ key = 2;
++ break;
++ case QSSGRenderLayer::TonemapMode::HejlDawson:
++ features.set(QSSGShaderFeatures::Feature::HejlDawsonTonemapping, true);
++ key = 3;
++ break;
++ case QSSGRenderLayer::TonemapMode::Filmic:
++ features.set(QSSGShaderFeatures::Feature::FilmicTonemapping, true);
++ key = 4;
++ break;
++ case QSSGRenderLayer::TonemapMode::None:
++ case QSSGRenderLayer::TonemapMode::Custom:
++ key = 0; // passthrough (Custom tonemapping in the sky is not yet supported)
++ break;
++ }
++ }
++
++ void SkyboxPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ if (!skipPrep) {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
++ layer = &data.layer;
++ QSSG_ASSERT(layer, return);
++
++ rpDesc = rhiCtx->mainRenderPassDescriptor();
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++ ps.polygonMode = QRhiGraphicsPipeline::Fill;
++
++ // When there are effects, then it is up to the last pass of the
++ // last effect to perform tonemapping, neither the skybox nor the
++ // main render pass should alter the colors then.
++ skipTonemapping = layer->firstEffect != nullptr;
++
++ RenderHelpers::rhiPrepareSkyBox(*renderer.contextInterface(), this, *layer, data.renderedCameras, renderer);
++ skipPrep = true;
++ }
++ }
++
++ void SkyboxPass::renderPass(QSSGRenderer &renderer)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(layer, return);
++
++ QRhiShaderResourceBindings *srb = layer->skyBoxSrb;
++ if (!srb)
++ return;
++
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render skybox"));
++
++ // Note: We get the shader here, as the screen map pass might modify the state of
++ // the tonemap mode.
++
++ QSSGRenderLayer::TonemapMode tonemapMode = skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom) ? QSSGRenderLayer::TonemapMode::None : layer->tonemapMode;
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ auto shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(tonemapMode, layer->skyBoxIsRgbe8, layer->viewCount);
++ QSSG_CHECK(shaderPipeline);
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, shaderPipeline.get());
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_map"));
++ }
++
++ void SkyboxPass::resetForFrame()
++ {
++ ps = {};
++ layer = nullptr;
++ skipPrep = false;
++ }
++
++ // SKY MATERIAL BACKGROUND PASS (screen-space)
++
++ void SkyMaterialBackgroundPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ const auto &ctx = *renderer.contextInterface();
++ const auto &rhiCtx = ctx.rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
++ layer = &data.layer;
++ QSSG_ASSERT(layer && layer->skyMaterial, return);
++
++ ready = false;
++
++ rpDesc = rhiCtx->mainRenderPassDescriptor();
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++ ps.polygonMode = QRhiGraphicsPipeline::Fill;
++ // When there are effects, the last effect pass performs tonemapping, so the sky must
++ // output linear here (TonemapMode::None below).
++ skipTonemapping = layer->firstEffect != nullptr;
++
++ QRhi *rhi = rhiCtx->rhi();
++ QSSGRenderSkyMaterial &skyMaterial = *layer->skyMaterial;
++ const int viewCount = data.layer.viewCount;
++
++ const QSSGRenderLayer::TonemapMode tonemapMode =
++ skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom)
++ ? QSSGRenderLayer::TonemapMode::None
++ : layer->tonemapMode;
++ QSSGShaderFeatures tonemapFeatures;
++ quint32 tonemapKey = 0;
++ skyMaterialTonemapFeatures(tonemapMode, tonemapFeatures, tonemapKey);
++
++ pipeline = skyMaterial.ensureBackgroundPipeline(ctx, tonemapFeatures, tonemapKey, viewCount);
++ if (!pipeline) // shader/textures not ready yet — skip this frame (clear color shows)
++ return;
++
++ // Reconstruct the view direction inputs exactly like skybox.vert, per view:
++ // eye = orientation * (cameraRotation * (inverseProjection * ndcPos))
++ // viewRotation folds orientation * cameraRotation into one translation-free matrix.
++ // orientation (the probe orientation) is shared across views.
++ const QMatrix3x3 o = layer->lightProbeSettings.probeOrientation;
++ const QMatrix4x4 orientation(o(0, 0), o(0, 1), o(0, 2), 0.0f,
++ o(1, 0), o(1, 1), o(1, 2), 0.0f,
++ o(2, 0), o(2, 1), o(2, 2), 0.0f,
++ 0.0f, 0.0f, 0.0f, 1.0f);
++ QVarLengthArray<QMatrix4x4, 2> inverseProjections;
++ QVarLengthArray<QMatrix4x4, 2> viewRotations;
++ for (int v = 0; v < viewCount; ++v) {
++ const QSSGRenderCamera &camera = *data.renderedCameras[v];
++ inverseProjections.append(camera.projection.inverted());
++ QMatrix4x4 cameraRotation = data.getGlobalTransform(camera);
++ cameraRotation.setColumn(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f)); // drop translation
++ viewRotations.append(orientation * cameraRotation);
++ }
++ const float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
++ const float exposure = layer->lightProbeSettings.probeExposure;
++
++ skyMaterial.updateBackgroundUniforms(ctx, this, inverseProjections, viewRotations, adjustY, exposure);
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ backgroundSrb = rhiCtxD->srb(skyMaterial.backgroundBindings);
++
++ switch (skyMaterial.skyboxMode) {
++ case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceHalf:
++ scaleDivisor = 2;
++ break;
++ case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceQuarter:
++ scaleDivisor = 4;
++ break;
++ case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceFull:
++ case QSSGRenderSkyMaterial::SkyboxMode::Cubemap:
++ scaleDivisor = 1;
++ break;
++ }
++
++ renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), nullptr);
++
++ // Reduced-scale: render the (tonemapped) sky into a smaller offscreen target now, in
++ // the prepare phase, so renderPass only has to upscale-blit it into the main pass.
++ if (scaleDivisor > 1) {
++ const std::array<float, 4> vp = ps.viewport.viewport();
++ const QSize fullSize(int(vp.at(2)), int(vp.at(3)));
++ const QSize targetSize = (fullSize / qreal(scaleDivisor)).expandedTo(QSize(1, 1));
++ const bool multiView = viewCount >= 2;
++ const int wantArraySize = multiView ? viewCount : 0;
++
++ offscreenTexture = data.getRenderResult(QSSGRenderResult::Key::SkyMaterialBackgroundTexture);
++ QSSGRhiRenderableTexture *tex = offscreenTexture;
++ if (!tex->texture || tex->texture->pixelSize() != targetSize || tex->texture->arraySize() != wantArraySize) {
++ tex->reset();
++ tex->texture = multiView ? rhi->newTextureArray(QRhiTexture::RGBA16F, viewCount, targetSize, 1, QRhiTexture::RenderTarget)
++ : rhi->newTexture(QRhiTexture::RGBA16F, targetSize, 1, QRhiTexture::RenderTarget);
++ if (!tex->texture->create()) {
++ qWarning("Failed to build SkyMaterial screen-space background texture");
++ tex->reset();
++ offscreenTexture = nullptr;
++ return;
++ }
++ QRhiColorAttachment att(tex->texture);
++ att.setMultiViewCount(viewCount);
++ QRhiTextureRenderTargetDescription desc;
++ desc.setColorAttachments({ att });
++ tex->rt = rhi->newTextureRenderTarget(desc);
++ tex->rt->setName(QByteArrayLiteral("SkyMaterial screen-space background"));
++ tex->rpDesc = tex->rt->newCompatibleRenderPassDescriptor();
++ tex->rt->setRenderPassDescriptor(tex->rpDesc);
++ if (!tex->rt->create()) {
++ qWarning("Failed to build SkyMaterial screen-space background render target");
++ tex->reset();
++ offscreenTexture = nullptr;
++ return;
++ }
++ }
++
++ QSSGRhiGraphicsPipelineState offscreenPs;
++ offscreenPs.viewport = QRhiViewport(0, 0, float(targetSize.width()), float(targetSize.height()));
++ offscreenPs.samples = 1;
++ offscreenPs.viewCount = viewCount;
++ offscreenPs.polygonMode = QRhiGraphicsPipeline::Fill;
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(offscreenPs, pipeline.get());
++ renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx.get(), &offscreenPs, backgroundSrb,
++ offscreenTexture->rt, QSSGRhiQuadRenderer::RenderBehind);
++ }
++
++ ready = true;
++ }
++
++ void SkyMaterialBackgroundPass::renderPass(QSSGRenderer &renderer)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(layer, return);
++
++ if (!ready) // sky shader not ready this frame
++ return;
++
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render sky material background"));
++
++ if (scaleDivisor > 1) {
++ QSSG_ASSERT(offscreenTexture && offscreenTexture->isValid(), return);
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ auto upscaleShader = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(layer->viewCount,
++ QSSGRenderLayer::TonemapMode::None);
++ QSSG_CHECK(upscaleShader);
++
++ QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
++ QSSGRhiShaderResourceBindingList bindings;
++ bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, offscreenTexture->texture, sampler);
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
++
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, upscaleShader.get());
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc,
++ { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
++ } else {
++ QSSG_ASSERT(pipeline && backgroundSrb, return);
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline.get());
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, backgroundSrb, rpDesc,
++ { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
++ }
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("sky_material_background"));
++ }
++
++ void SkyMaterialBackgroundPass::resetForFrame()
++ {
++ ps = {};
++ layer = nullptr;
++ ready = false;
++ scaleDivisor = 1;
++ pipeline = nullptr;
++ backgroundSrb = nullptr;
++ offscreenTexture = nullptr;
++ }
++
++ void SkyboxCubeMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
++ layer = &data.layer;
++ QSSG_ASSERT(layer, return);
++
++ rpDesc = rhiCtx->mainRenderPassDescriptor();
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++ ps.polygonMode = QRhiGraphicsPipeline::Fill;
++
++ QSSGRenderLayer::TonemapMode tonemapMode = skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom) ? QSSGRenderLayer::TonemapMode::None : layer->tonemapMode;
++
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ skyBoxCubeShader = shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(tonemapMode, !data.layer.skyBoxIsSrgb, data.layer.viewCount);
++
++ RenderHelpers::rhiPrepareSkyBox(*renderer.contextInterface(), this, *layer, data.renderedCameras, renderer, uint(tonemapMode));
++ }
++
++ void SkyboxCubeMapPass::renderPass(QSSGRenderer &renderer)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(layer && skyBoxCubeShader, return);
++
++ QRhiShaderResourceBindings *srb = layer->skyBoxSrb;
++ QSSG_ASSERT(srb, return);
++
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render cubemap skybox"));
++
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, skyBoxCubeShader.get());
++ renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_cube"));
++ }
++
++ void SkyboxCubeMapPass::resetForFrame()
++ {
++ ps = {};
++ layer = nullptr;
++ }
++
++ void Item2DPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ const auto &layer = data.layer;
++
++ const auto &item2Ds = data.getRenderableItem2Ds();
++ prepdItem2DRenderers.reserve(size_t(item2Ds.size()));
++ // NOTE: This marks the start of the 2D sub-scene rendering as it might result in
++ // a nested 3D scene to be rendered and if we don't save the state here, we can
++ // end up with a mismatched state in the QtQuick3D renderer.
++ // See the end of this function for the corresponding end call (endSubLayerRender()).
++ renderer.beginSubLayerRender(data);
++ for (const auto *item2D: std::as_const(item2Ds)) {
++ // Find data for item
++ auto item2DData = data.getItem2DRenderer(*item2D);
++ const auto &mvps = data.getItem2DMvps(*item2D);
++ QSGRenderer *renderer2d = item2DData;
++
++ QSSG_ASSERT(renderer2d, continue);
++
++ // NOTE: We shouldn't get into this state...
++ if (renderer2d && renderer2d->currentRhi() != rhiCtx->rhi()) {
++ static bool contextWarningShown = false;
++ if (!contextWarningShown) {
++ qWarning () << "Scene with embedded 2D content can only be rendered in one window.";
++ contextWarningShown = true;
++ }
++ continue;
++ }
++
++ // Set the projection matrix
++
++ auto layerPrepResult = data.layerPrepResult;
++
++ QRhiRenderTarget *renderTarget = rhiCtx->renderTarget();
++ auto *rpd = renderTarget->renderPassDescriptor();
++ renderer2d->setDevicePixelRatio(renderTarget->devicePixelRatio());
++ const QRect deviceRect(QPoint(0, 0), renderTarget->pixelSize());
++ const int viewCount = data.layer.viewCount;
++ if (layer.scissorRect.isValid()) {
++ QRect effScissor = layer.scissorRect & layerPrepResult.getViewport().toRect();
++ QMatrix4x4 correctionMat = correctMVPForScissor(layerPrepResult.getViewport(),
++ effScissor,
++ rhiCtx->rhi()->isYUpInNDC());
++ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex) {
++ const QMatrix4x4 projectionMatrix = correctionMat * mvps[viewIndex];
++ renderer2d->setProjectionMatrix(projectionMatrix, viewIndex);
++ }
++ renderer2d->setViewportRect(effScissor);
++ } else {
++ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
++ renderer2d->setProjectionMatrix(mvps[viewIndex], viewIndex);
++ renderer2d->setViewportRect(RenderHelpers::correctViewportCoordinates(layerPrepResult.getViewport(), deviceRect));
++ }
++ renderer2d->setDeviceRect(deviceRect);
++ QSGRenderTarget sgRt(renderTarget, rpd, rhiCtx->commandBuffer());
++ sgRt.multiViewCount = data.layer.viewCount;
++ renderer2d->setRenderTarget(sgRt);
++ renderer2d->prepareSceneInline();
++ prepdItem2DRenderers.push_back(renderer2d);
++ }
++ renderer.endSubLayerRender(data);
++ }
++
++ void Item2DPass::renderPass(QSSGRenderer &renderer)
++ {
++ QSSG_ASSERT(prepdItem2DRenderers.size() > 0, return);
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render 2D sub-scene"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render 2D sub-scene"));
++ QSSGLayerRenderData *data = QSSGLayerRenderData::getCurrent(renderer);
++ renderer.beginSubLayerRender(*data);
++ for (QSGRenderer *renderer2d : std::as_const(prepdItem2DRenderers))
++ renderer2d->renderSceneInline();
++ renderer.endSubLayerRender(*data);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("2D_sub_scene"));
++ }
++
++ void Item2DPass::resetForFrame()
++ {
++ prepdItem2DRenderers.clear();
++ }
++
++ void InfiniteGridPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
++ layer = &data.layer;
++ QSSG_ASSERT(layer, return);
++
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ gridShader = shaderCache->getBuiltInRhiShaders().getRhiGridShader(data.layer.viewCount);
++
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ ps.polygonMode = QRhiGraphicsPipeline::Fill;
++
++ RenderHelpers::rhiPrepareGrid(rhiCtx.get(), this, *layer, data.renderedCameras, renderer);
++ }
++
++ void InfiniteGridPass::renderPass(QSSGRenderer &renderer)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(gridShader && rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render grid"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render grid"));
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, gridShader.get());
++ QRhiShaderResourceBindings *srb = layer->gridSrb;
++ QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor();
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest });
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_grid"));
++ }
++
++ void InfiniteGridPass::resetForFrame()
++ {
++ ps = {};
++ layer = nullptr;
++ }
++
++ void DebugDrawPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
++
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ debugObjectShader = shaderCache->getBuiltInRhiShaders().getRhiDebugObjectShader(data.layer.viewCount);
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = data.layer.viewCount;
++
++ // debug objects
++ const auto &debugDraw = renderer.contextInterface()->debugDrawSystem();
++ if (debugDraw && debugDraw->hasContent()) {
++ QRhi *rhi = rhiCtx->rhi();
++ QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
++ debugDraw->prepareGeometry(rhiCtx.get(), rub);
++ QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 });
++ if (!dcd.ubuf) {
++ dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 * data.renderedCameras.count());
++ dcd.ubuf->create();
++ }
++ char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
++ QMatrix4x4 viewProjection(Qt::Uninitialized);
++ QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
++ for (qsizetype viewIdx = 0; viewIdx < data.renderedCameras.count(); ++viewIdx) {
++ cameraGlobalTransform = data.getGlobalTransform(*data.renderedCameras[viewIdx]);
++ data.renderedCameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
++ viewProjection = rhi->clipSpaceCorrMatrix() * viewProjection;
++ memcpy(ubufData, viewProjection.constData() + viewIdx * 64, 64);
++ }
++ dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
++
++ QSSGRhiShaderResourceBindingList bindings;
++ bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage, dcd.ubuf);
++ dcd.srb = rhiCtxD->srb(bindings);
++
++ rhiCtx->commandBuffer()->resourceUpdate(rub);
++ }
++ }
++
++ void DebugDrawPass::renderPass(QSSGRenderer &renderer)
++ {
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(debugObjectShader && rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++
++ const auto &debugDraw = renderer.contextInterface()->debugDrawSystem();
++ if (debugDraw && debugDraw->hasContent()) {
++ cb->debugMarkBegin(QByteArrayLiteral("Quick 3D debug objects"));
++ Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick 3D debug objects"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, debugObjectShader.get());
++ QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 });
++ QRhiShaderResourceBindings *srb = dcd.srb;
++ QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor();
++ debugDraw->recordRenderDebugObjects(rhiCtx.get(), &ps, srb, rpDesc);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("debug_objects"));
++ }
++ }
++
++ void DebugDrawPass::resetForFrame()
++ {
++ ps = {};
++ }
++
++ void UserExtensionPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ Q_UNUSED(renderer);
++ auto &frameData = data.getFrameData();
++ for (const auto &p : std::as_const(extensions)) {
++ p->prepareRender(frameData);
++ if (p->mode() == QSSGRenderExtension::RenderMode::Standalone)
++ p->render(frameData);
++ }
++ }
++
++ void UserExtensionPass::renderPass(QSSGRenderer &renderer)
++ {
++ auto *data = QSSGLayerRenderData::getCurrent(renderer);
++ QSSG_ASSERT(data, return);
++ auto &frameData = data->getFrameData();
++ for (const auto &p : std::as_const(extensions)) {
++ if (p->mode() == QSSGRenderExtension::RenderMode::Main)
++ p->render(frameData);
++ }
++ }
++
++ void UserExtensionPass::resetForFrame()
++ {
++ for (const auto &p : std::as_const(extensions))
++ p->resetForFrame();
++
++ // TODO: We should track if we need to update this list.
++ extensions.clear();
++ }
++
++ static quint32 nextMultipleOf(quint32 value, quint32 multiple)
++ {
++ return multiple * ((value / multiple) + 1);
++ }
++
++ static quint32 ensureFreeNodes(quint32 value, quint32 multiple)
++ {
++ quint32 multipleOf = nextMultipleOf(value, multiple);
++ if (multipleOf - value < multiple)
++ multipleOf += multiple;
++ return multipleOf;
++ }
++
++ bool OITRenderPass::linkedListRequiresResize(QSize dim)
++ {
++ quint32 reported = reportedNodeCount;
++ quint32 current = currentNodeCount;
++ quint32 fullCount = dim.width() * dim.height();
++
++ // resize if reported is greater than current or if there are less than 50% of screen size nodes free
++ if (reported > current)
++ return true;
++ quint32 freeCount = current - reported;
++ if (freeCount < fullCount / 2)
++ return true;
++ // resize if we have more than 100% screen size free nodes
++ if (freeCount > fullCount)
++ return true;
++ return false;
++ }
++
++ void OITRenderPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++ auto *rhi = rhiCtx->rhi();
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = rhiCtx->mainPassViewCount();
++
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
++
++ shaderFeatures = data.getShaderFeatures();
++ sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
++
++ if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
++ ps.colorAttachmentCount = 2;
++
++ rhiAccumTexture = data.getRenderResult(QSSGRenderResult::Key::AccumTexture);
++ rhiRevealageTexture = data.getRenderResult(QSSGRenderResult::Key::RevealageTexture);
++ if (ps.samples > 1)
++ rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTextureMS);
++ else
++ rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
++ if (!rhiDepthTexture->isValid())
++ return;
++ auto &oitrt = data.getOitRenderContext();
++ if (!oitrt.oitRenderTarget || oitrt.oitRenderTarget->pixelSize() != data.layerPrepResult.textureDimensions()
++ || rhiDepthTexture->texture != oitrt.oitRenderTarget->description().depthTexture()
++ || ps.samples != oitrt.oitRenderTarget->sampleCount()) {
++ if (oitrt.oitRenderTarget) {
++ delete oitrt.oitRenderTarget;
++ delete oitrt.renderPassDescriptor;
++ delete rhiAccumTexture->texture;
++ delete rhiRevealageTexture->texture;
++ oitrt.oitRenderTarget = nullptr;
++ oitrt.renderPassDescriptor = nullptr;
++ rhiAccumTexture->texture = nullptr;
++ rhiRevealageTexture->texture = nullptr;
++ }
++ const QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget;
++ if (ps.viewCount >= 2) {
++ rhiAccumTexture->texture = rhi->newTextureArray(QRhiTexture::RGBA16F, ps.viewCount, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
++ rhiRevealageTexture->texture = rhi->newTextureArray(QRhiTexture::R16F, ps.viewCount, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
++ } else {
++ rhiAccumTexture->texture = rhi->newTexture(QRhiTexture::RGBA16F, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
++ rhiRevealageTexture->texture = rhi->newTexture(QRhiTexture::R16F, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
++ }
++ rhiAccumTexture->texture->create();
++ rhiRevealageTexture->texture->create();
++
++ QRhiTextureRenderTargetDescription desc;
++ desc.setColorAttachments({{rhiAccumTexture->texture}, {rhiRevealageTexture->texture}});
++ desc.setDepthTexture(rhiDepthTexture->texture);
++
++ if (oitrt.oitRenderTarget == nullptr) {
++ oitrt.oitRenderTarget = rhi->newTextureRenderTarget(desc, QRhiTextureRenderTarget::PreserveDepthStencilContents);
++ oitrt.renderPassDescriptor = oitrt.oitRenderTarget->newCompatibleRenderPassDescriptor();
++ oitrt.oitRenderTarget->setRenderPassDescriptor(oitrt.renderPassDescriptor);
++ oitrt.oitRenderTarget->create();
++
++ renderTarget = oitrt.oitRenderTarget;
++ }
++ }
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearMRTShader();
++
++ QSSGRhiShaderResourceBindingList bindings;
++ QVector4D clearData[2];
++ clearData[0] = QVector4D(0.0, 0.0, 0.0, 0.0);
++ clearData[1] = QVector4D(1.0, 1.0, 1.0, 1.0);
++
++ QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, clearPipeline.get(), nullptr, nullptr, 0 }));
++ QRhiBuffer *&ubuf = dcd.ubuf;
++ const int ubufSize = sizeof(clearData);
++ if (!ubuf) {
++ ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
++ ubuf->create();
++ }
++
++ QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
++ rub->updateDynamicBuffer(ubuf, 0, ubufSize, &clearData);
++ renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
++
++ bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
++
++ clearSrb = rhiCtxD->srb(bindings);
++
++ ps.targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
++ ps.targetBlend[0].srcColor = QRhiGraphicsPipeline::One;
++ ps.targetBlend[0].dstAlpha = QRhiGraphicsPipeline::One;
++ ps.targetBlend[0].dstColor = QRhiGraphicsPipeline::One;
++ ps.targetBlend[1].srcAlpha = QRhiGraphicsPipeline::Zero;
++ ps.targetBlend[1].srcColor = QRhiGraphicsPipeline::Zero;
++ ps.targetBlend[1].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
++ ps.targetBlend[1].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
++
++ TransparentPass::prep(*ctx, data, this, ps, shaderFeatures, oitrt.renderPassDescriptor, sortedTransparentObjects, true);
++ } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
++ oitRenderContext = &data.getOitRenderContext();
++ if (oitRenderContext->rub) {
++ rhiCtx->commandBuffer()->resourceUpdate(oitRenderContext->rub);
++ oitRenderContext->rub = nullptr;
++ }
++
++ // same as transparent pass
++ // transparent objects (or, without LayerEnableDepthTest, all objects)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
++ ps.targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
++ ps.targetBlend[0].srcColor = QRhiGraphicsPipeline::SrcAlpha;
++ ps.targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
++ ps.targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
++
++ shaderFeatures = data.getShaderFeatures();
++ sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
++
++ #ifdef QSSG_OIT_USE_BUFFERS
++ auto &oitCtx = data.getOitRenderContext();
++ rhiABuffer = oitCtx.aBuffer;
++ rhiAuxBuffer = oitCtx.auxBuffer;
++ rhiCounterBuffer = oitCtx.counterBuffer;
++ #else
++ rhiABufferImage = data.getRenderResult(QSSGRenderResult::Key::ABufferImage);
++ rhiAuxiliaryImage = data.getRenderResult(QSSGRenderResult::Key::AuxiliaryImage);
++ rhiCounterImage = data.getRenderResult(QSSGRenderResult::Key::CounterImage);
++ #endif
++ QSize dim = data.layerPrepResult.textureDimensions();
++ dim.setWidth(dim.width() * ps.samples);
++ dim.setHeight(dim.height() * ps.viewCount);
++ #ifdef QSSG_OIT_USE_BUFFERS
++ if (!rhiAuxBuffer || rhiAuxBuffer->size() != (dim.width() * dim.height() * 4u) || currentNodeCount == 0 || linkedListRequiresResize(dim))
++ #else
++ if (!rhiAuxiliaryImage->texture || rhiAuxiliaryImage->texture->pixelSize() != dim || currentNodeCount == 0 || linkedListRequiresResize(dim))
++ #endif
++ {
++ quint32 extraNodeCount = 0;
++ #ifdef QSSG_OIT_USE_BUFFERS
++ if (rhiAuxBuffer && rhiAuxBuffer->size() != (dim.width() * dim.height() * 4u)) {
++ if (rhiAuxBuffer->size() < (dim.width() * dim.height() * 4u))
++ extraNodeCount = ensureFreeNodes((dim.width() * dim.height() * 4u) - rhiAuxBuffer->size(), 32u * 1024u);
++ rhiAuxBuffer->destroy();
++ rhiAuxBuffer = nullptr;
++ }
++ #else
++ if (rhiABufferImage->texture) {
++ const auto s = rhiAuxiliaryImage->texture->pixelSize();
++ if (s.width() * s.height() < dim.width() * dim.height())
++ extraNodeCount = ensureFreeNodes(dim.width() * dim.height() - s.width() * s.height(), 32u * 1024u);
++ rhiABufferImage->texture->destroy();
++ rhiAuxiliaryImage->texture->destroy();
++ }
++ #endif
++
++ if (reportedNodeCount) {
++ // ensure there are at least 50% of the screen size nodes available
++ currentNodeCount = ensureFreeNodes(reportedNodeCount + extraNodeCount, dim.width() * dim.height() / 2);
++ } else {
++ quint32 size = RenderHelpers::rhiCalculateABufferSize(data.layerPrepResult.textureDimensions(), 4 * ps.samples * ps.viewCount);
++ currentNodeCount = ensureFreeNodes(size * size, 32u * 1024u);
++ }
++ data.layer.oitNodeCount = currentNodeCount;
++
++ #ifdef QSSG_OIT_USE_BUFFERS
++ if (rhiABuffer && currentNodeCount * 16 != rhiABuffer->size()) {
++ rhiABuffer->destroy();
++ rhiABuffer = nullptr;
++ }
++ if (!rhiABuffer) {
++ rhiABuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, currentNodeCount * 16);
++ rhiABuffer->create();
++ }
++ if (!rhiAuxBuffer) {
++ rhiAuxBuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, dim.width() * dim.height() * 4);
++ rhiAuxBuffer->create();
++ }
++ if (!rhiCounterBuffer) {
++ rhiCounterBuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 4);
++ rhiCounterBuffer->create();
++ oitCtx.counterBuffer = rhiCounterBuffer;
++ }
++ oitCtx.aBuffer = rhiABuffer;
++ oitCtx.auxBuffer = rhiAuxBuffer;
++ #else
++ quint32 sizeWithLayers = RenderHelpers::rhiCalculateABufferSize(currentNodeCount);
++ const QRhiTexture::Flags textureFlags = QRhiTexture::UsedWithLoadStore;
++ rhiABufferImage->texture = rhi->newTexture(QRhiTexture::RGBA32UI, QSize(sizeWithLayers, sizeWithLayers), 1, textureFlags);
++ rhiABufferImage->texture->create();
++ rhiAuxiliaryImage->texture = rhi->newTexture(QRhiTexture::R32UI, dim, 1, textureFlags);
++ rhiAuxiliaryImage->texture->create();
++ if (!rhiCounterImage->texture) {
++ rhiCounterImage->texture = rhi->newTexture(QRhiTexture::R32UI, QSize(1, 1), 1, textureFlags | QRhiTexture::UsedAsTransferSource);
++ rhiCounterImage->texture->create();
++
++ auto &oitrt = data.getOitRenderContext();
++ readbackImage = rhi->newTexture(QRhiTexture::R32UI, QSize(1, 1), 1, QRhiTexture::UsedAsTransferSource);
++ readbackImage->create();
++ oitrt.copyTexture = readbackImage;
++ }
++ #endif
++ }
++ QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++ #ifdef QSSG_OIT_USE_BUFFERS
++ clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearBufferShader();
++ #else
++ clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearImageShader();
++ #endif
++ QSSGRhiShaderResourceBindingList bindings;
++ quint32 clearImageData[8] = {0};
++
++ QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, clearPipeline.get(), nullptr, nullptr, 0 }));
++ QRhiBuffer *&ubuf = dcd.ubuf;
++ const int ubufSize = sizeof(clearImageData);
++ if (!ubuf) {
++ ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
++ ubuf->create();
++ }
++
++ clearImageData[4] = data.layerPrepResult.textureDimensions().width();
++ clearImageData[5] = data.layerPrepResult.textureDimensions().height();
++ clearImageData[6] = ps.samples;
++ clearImageData[7] = ps.viewCount;
++
++ rub->updateDynamicBuffer(ubuf, 0, ubufSize, clearImageData);
++ quint32 zero = 0;
++ #ifdef QSSG_OIT_USE_BUFFERS
++ rub->uploadStaticBuffer(rhiCounterBuffer, 0, 4, &zero);
++ #else
++ rub->uploadTexture(rhiCounterImage->texture, {{0, 0, {&zero, 4}}});
++ #endif
++
++ bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
++ #ifdef QSSG_OIT_USE_BUFFERS
++ bindings.addStorageBufferStore(1, QRhiShaderResourceBinding::FragmentStage, rhiAuxBuffer);
++ #else
++ bindings.addImageStore(1, QRhiShaderResourceBinding::FragmentStage, rhiAuxiliaryImage->texture, 0);
++ #endif
++
++ clearSrb = rhiCtxD->srb(bindings);
++
++ renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
++
++ TransparentPass::prep(*ctx, data, this, ps, shaderFeatures, rhiCtx->mainRenderPassDescriptor(), sortedTransparentObjects, true);
++ }
++ }
++
++ void OITRenderPass::renderPass(QSSGRenderer &renderer)
++ {
++ auto *ctx = renderer.contextInterface();
++ const auto &rhiCtx = ctx->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
++ if (Q_LIKELY(renderTarget)) {
++ cb->beginPass(renderTarget, Qt::black, {});
++
++ QRhiShaderResourceBindings *srb = clearSrb;
++ QSSG_ASSERT(srb, return);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, clearPipeline.get());
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, renderTarget->renderPassDescriptor(), {});
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render order-independent alpha"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render order-independent alpha"));
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, true);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
++ TransparentPass::render(*ctx, ps, sortedTransparentObjects);
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_order_independent_pass"));
++ Q_TRACE(QSSG_renderPass_exit);
++
++ cb->endPass();
++ }
++ } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D render alpha"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++ Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render alpha"));
++
++ QRhiShaderResourceBindings *srb = clearSrb;
++ QSSG_ASSERT(srb, return);
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, clearPipeline.get());
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(), {});
++
++ TransparentPass::render(*ctx, ps, sortedTransparentObjects);
++
++ if (!oitRenderContext->pendingResult) {
++ QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
++ QRhiReadbackResult *result = nullptr;
++ if (oitRenderContext->completedResults.size()) {
++ result = oitRenderContext->completedResults.takeLast();
++ result->pixelSize = {};
++ result->data = {};
++ result->format = QRhiTexture::UnknownFormat;
++ } else {
++ result = new QRhiReadbackResult();
++ }
++ auto *oitrt = oitRenderContext;
++ const auto completedFunc = [this, oitrt, result](){
++ if (result) {
++ const quint32 *d = reinterpret_cast<const quint32 *>(result->data.constData());
++ quint32 nodeCount = *d;
++ if (nodeCount)
++ this->reportedNodeCount = ensureFreeNodes(nodeCount, 32u * 1024u);
++ oitrt->completedResults.append(result);
++ QSSG_ASSERT(oitrt->pendingResult == result, return;);
++ oitrt->pendingResult = nullptr;
++ }
++ };
++ result->completed = completedFunc;
++ #ifdef QSSG_OIT_USE_BUFFERS
++ rub->readBackBuffer(rhiCounterBuffer, 0, 4, result);
++ #else
++ rub->copyTexture(readbackImage, rhiCounterImage->texture);
++ QRhiReadbackDescription rbdesc;
++ rbdesc.setTexture(readbackImage);
++ rub->readBackTexture(rbdesc, result);
++ #endif
++ oitRenderContext->rub = rub;
++ oitRenderContext->pendingResult = result;
++ }
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_order_independent_pass"));
++ Q_TRACE(QSSG_renderPass_exit);
++ }
++ }
++
++ QSSGRenderPass::Type OITRenderPass::passType() const
++ {
++ if (method == QSSGRenderLayer::OITMethod::WeightedBlended)
++ return Type::Standalone;
++ return Type::Main;
++ }
++
++
++ void OITRenderPass::resetForFrame()
++ {
++ sortedTransparentObjects.clear();
++ ps = {};
++ shaderFeatures = {};
++ rhiAccumTexture = nullptr;
++ rhiRevealageTexture = nullptr;
++ rhiDepthTexture = nullptr;
++ rhiCounterImage = nullptr;
++ oitRenderContext = nullptr;
++ }
++
++ void OITCompositePass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ using namespace RenderHelpers;
++
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ const auto &shaderCache = renderer.contextInterface()->shaderCache();
++
++ ps = data.getPipelineState();
++ ps.samples = rhiCtx->mainPassSampleCount();
++ ps.viewCount = rhiCtx->mainPassViewCount();
++
++ if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
++ rhiAccumTexture = data.getRenderResult(QSSGRenderResult::Key::AccumTexture);
++ rhiRevealageTexture = data.getRenderResult(QSSGRenderResult::Key::RevealageTexture);
++ compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false);
++ } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
++ #ifdef QSSG_OIT_USE_BUFFERS
++ rhiABuffer = data.getOitRenderContext().aBuffer;
++ rhiAuxBuffer = data.getOitRenderContext().auxBuffer;
++ compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false, true);
++ #else
++ rhiABufferImage = data.getRenderResult(QSSGRenderResult::Key::ABufferImage);
++ rhiAuxiliaryImage = data.getRenderResult(QSSGRenderResult::Key::AuxiliaryImage);
++ compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false);
++ #endif
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 }));
++ QRhiBuffer *&ubuf = dcd.ubuf;
++ const int ubufSize = 6 * sizeof(quint32);
++ if (!ubuf) {
++ ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
++ ubuf->create();
++ }
++ #ifdef QSSG_OIT_USE_BUFFERS
++ const quint32 values[6] = { 0,
++ quint32(data.layer.oitNodeCount),
++ quint32(ps.samples),
++ 0,
++ quint32(data.layerPrepResult.textureDimensions().width()),
++ quint32(data.layerPrepResult.textureDimensions().height())
++ };
++ #else
++ const quint32 sizeWithLayers = rhiABufferImage->texture->pixelSize().width();
++ const quint32 values[6] = { sizeWithLayers,
++ sizeWithLayers * sizeWithLayers,
++ quint32(ps.samples),
++ 0,
++ quint32(data.layerPrepResult.textureDimensions().width()),
++ quint32(data.layerPrepResult.textureDimensions().height())
++ };
++ #endif
++ QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
++ rub->updateDynamicBuffer(ubuf, 0, ubufSize, values);
++ renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
++ }
++ }
++
++ void OITCompositePass::renderPass(QSSGRenderer &renderer)
++ {
++ using namespace RenderHelpers;
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++
++ if (!rhiAccumTexture->texture || !rhiRevealageTexture->texture)
++ return;
++
++ if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
++ QSSGRhiShaderResourceBindingList bindings;
++
++ QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest,
++ QRhiSampler::Nearest,
++ QRhiSampler::None,
++ QRhiSampler::ClampToEdge,
++ QRhiSampler::ClampToEdge,
++ QRhiSampler::ClampToEdge });
++ bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiAccumTexture->texture, sampler);
++ bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, rhiRevealageTexture->texture, sampler);
++
++ compositeSrb = rhiCtxD->srb(bindings);
++
++ QRhiShaderResourceBindings *srb = compositeSrb;
++ QSSG_ASSERT(srb, return);
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D revealage"));
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, compositeShaderPipeline.get());
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(),
++ { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::PremulBlend});
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("revealage"));
++ cb->debugMarkEnd();
++ } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
++ QSSGRhiShaderResourceBindingList bindings;
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
++ QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 }));
++ QRhiBuffer *&ubuf = dcd.ubuf;
++
++ bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
++ #ifdef QSSG_OIT_USE_BUFFERS
++ bindings.addStorageBufferLoad(1, QRhiShaderResourceBinding::FragmentStage, rhiABuffer);
++ bindings.addStorageBufferLoad(2, QRhiShaderResourceBinding::FragmentStage, rhiAuxBuffer);
++ #else
++ bindings.addImageLoad(1, QRhiShaderResourceBinding::FragmentStage, rhiABufferImage->texture, 0);
++ bindings.addImageLoad(2, QRhiShaderResourceBinding::FragmentStage, rhiAuxiliaryImage->texture, 0);
++ #endif
++
++ compositeSrb = rhiCtxD->srb(bindings);
++
++ QRhiShaderResourceBindings *srb = compositeSrb;
++ QSSG_ASSERT(srb, return);
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D oit-composite"));
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, compositeShaderPipeline.get());
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
++ renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(),
++ { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::PremulBlend});
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("oit-composite"));
++ }
++ }
++
++ void OITCompositePass::resetForFrame()
++ {
++ ps = {};
++ shaderFeatures = {};
++ rhiAccumTexture = nullptr;
++ rhiRevealageTexture = nullptr;
++ }
++
++ Q_STATIC_LOGGING_CATEGORY(lcUserRenderPass, "qt.quick3d.rhi.userrenderpass")
++
++ void UserRenderPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
++ {
++ QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++
++ for (auto *passNode : std::as_const(userPasses)) {
++ QSSG_ASSERT(passNode != nullptr, continue);
++ prepareTopLevelPass(renderer, data, passNode);
++ }
++ }
++
++ void UserRenderPass::renderPass(QSSGRenderer &renderer)
++ {
++
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++ QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
++ QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
++
++ for (UserPassData &passData : userPassData) {
++
++ const auto &ps = passData.ps;
++ const auto &renderables = passData.renderables;
++ const auto &clearColor = passData.clearColor;
++ const auto &depthStencilClearValue = passData.depthStencilClearValue;
++ const auto &renderableTexture = passData.renderableTexture;
++
++ cb->debugMarkBegin(QByteArrayLiteral("Quick3D UserRenderPass"));
++
++ if (Q_LIKELY(renderableTexture && renderableTexture->isValid())) {
++ cb->beginPass(renderableTexture->getRenderTarget().get(), clearColor, depthStencilClearValue, nullptr, rhiCtx->commonPassFlags());
++ if (passData.skyboxCubeMapPass) {
++ passData.skyboxCubeMapPass->renderPass(renderer);
++ } else if (passData.skyboxPass) {
++ passData.skyboxPass->renderPass(renderer);
++ } else if (passData.item2DPass) {
++ passData.item2DPass->renderPass(renderer);
++ } else {
++ // Regular User Passes
++ bool needsSetViewport = true;
++ if (passData.index >= 0) {
++ for (const auto &handle : std::as_const(renderables))
++ RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *handle.obj, &needsSetViewport, QSSGRenderTextureCubeFaceNone, qsizetype(passData.index));
++ }
++ }
++ QRhiResourceUpdateBatch *rub = nullptr;
++
++ // Sub Passes
++ for (auto &subPassData : passData.subPassData) {
++ const auto &subPs = subPassData.ps;
++ const auto &subRenderables = subPassData.renderables;
++
++ if (subPassData.skyboxCubeMapPass) {
++ subPassData.skyboxCubeMapPass->renderPass(renderer);
++ } else if (subPassData.skyboxPass) {
++ subPassData.skyboxPass->renderPass(renderer);
++ } else if (subPassData.item2DPass) {
++ subPassData.item2DPass->renderPass(renderer);
++ } else {
++ bool needsSetViewport = true;
++ if (subPassData.index >= 0) {
++ for (const auto &handle : std::as_const(subRenderables))
++ RenderHelpers::rhiRenderRenderable(rhiCtx.get(), subPs, *handle.obj, &needsSetViewport, QSSGRenderTextureCubeFaceNone, qsizetype(subPassData.index));
++ }
++ }
++ }
++
++ cb->endPass(rub);
++ }
++
++ cb->debugMarkEnd();
++ }
++ }
++
++ void UserRenderPass::resetForFrame()
++ {
++ qCDebug(lcUserRenderPass) << "resetForFrame in UserRenderPass";
++ userPasses.clear();
++
++ userPassData.clear();
++ }
++
++ void UserRenderPass::preparePassImpl(QSSGRenderer &renderer,
++ QSSGLayerRenderData &data,
++ QSSGRenderUserPass *passNode,
++ std::vector<UserPassData> &outData,
++ QSSGRhiRenderableTextureV2Ptr renderableTexture)
++ {
++ // NOTE: Only top-level passes should create their own renderable texture by passing in a null renderable texture.
++ // Ideally we this should be more explict...
++ const bool isTopLevelPass = (renderableTexture == nullptr);
++
++ QSSGRenderCamera *camera = data.renderedCameras[0];
++ const auto &rhiCtx = renderer.contextInterface()->rhiContext();
++
++ const QSize targetSize = data.layerPrepResult.textureDimensions();
++
++ static const auto needsRebuild = [](QRhiTexture *texture, const QSize &size, QRhiTexture::Format format) {
++ return !texture || texture->pixelSize() != size || texture->format() != format;
++ };
++
++ qCDebug(lcUserRenderPass, "renderPrep in UserRenderPass");
++
++ const QSSGResourceId currentPassId = QSSGRenderGraphObjectUtils::getResourceId(*passNode);
++ if (visitedPasses.find(currentPassId) != visitedPasses.end()) {
++ qWarning("UserRenderPass: Circular dependency detected in SubRenderPass chain. Ignoring pass.");
++ return;
++ }
++
++ // Check max depth
++ if (visitedPasses.size() >= MAX_SUBPASS_DEPTH) {
++ qWarning("UserRenderPass: Maximum SubRenderPass nesting depth (%zu) exceeded. Ignoring pass.", MAX_SUBPASS_DEPTH);
++ return;
++ }
++
++ visitedPasses.insert(currentPassId);
++
++ UserPassData currentPassData;
++ currentPassData.clearColor = passNode->clearColor;
++ currentPassData.depthStencilClearValue = passNode->depthStencilClearValue;
++ if (isTopLevelPass)
++ renderableTexture = currentPassData.renderableTexture = data.requestUserRenderPassManager()->getOrCreateRenderableTexture(*passNode);
++ else
++ currentPassData.renderableTexture = renderableTexture;
++
++ QSSGRenderableObjectList &renderables = currentPassData.renderables;
++ QSSGRhiGraphicsPipelineState &ps = currentPassData.ps;
++ const auto &renderTarget = currentPassData.renderableTexture;
++
++ // Initial pipeline state from layer data
++ ps = data.getPipelineState();
++
++ bool renderablesFiltered = false;
++
++ bool needsDepthStencilRenderBuffer = false;
++ QSSGAllocateTexturePtr depthTextureAllocCommand;
++
++ QVarLengthArray<QSSGColorAttachment *, 16> colorAttachments;
++ QVarLengthArray<QSSGResourceId, 4> subPassIds;
++
++ // Process commands in passNode
++ for (const QSSGCommand *theCommand : std::as_const(passNode->commands)) {
++ QSSG_ASSERT(theCommand != nullptr, continue);
++
++ qCDebug(lcUserRenderPass) << "Exec. command: >" << theCommand->typeAsString() << "--" << theCommand->debugString();
++
++ switch (theCommand->m_type) {
++ case CommandType::ColorAttachment:
++ {
++ const QSSGColorAttachment *colorAttachCmd = static_cast<const QSSGColorAttachment *>(theCommand);
++ colorAttachments.push_back(const_cast<QSSGColorAttachment *>(colorAttachCmd));
++ }
++ break;
++ case CommandType::DepthTextureAttachment:
++ {
++ QSSG_ASSERT(depthTextureAllocCommand == nullptr, break);
++ const QSSGDepthTextureAttachment *depthAttachCmd = static_cast<const QSSGDepthTextureAttachment *>(theCommand);
++ needsDepthStencilRenderBuffer = false;
++ depthTextureAllocCommand = depthAttachCmd->m_textureCmd;
++ }
++ break;
++ case CommandType::AddShaderDefine:
++ {
++ const auto *defineCmd = static_cast<const QSSGAddShaderDefine *>(theCommand);
++ const auto &defineName = defineCmd->m_name;
++ if (defineName.size() > 0) {
++ QByteArray value = QByteArray::number(defineCmd->m_value);
++ currentPassData.shaderDefines.push_back({ defineName, value });
++ }
++ }
++ break;
++ case CommandType::RenderablesFilter:
++ {
++ auto filterCommand = static_cast<const QSSGRenderablesFilterCommand *>(theCommand);
++
++ // Use the filter to select which renderables to include
++ // renderableTypes can be: None (0x0), Opaque (0x1), Transparent (0x2), or both
++ enum RenderableType : quint8 {
++ None = 0x0,
++ Opaque = 0x1,
++ Transparent = 0x2,
++ };
++
++ if (filterCommand->renderableTypes & RenderableType::Opaque) // Opaque
++ renderables = data.getSortedOpaqueRenderableObjects(*camera, 0, filterCommand->layerMask);
++ if (filterCommand->renderableTypes & RenderableType::Transparent) // Transparent
++ renderables += data.getSortedTransparentRenderableObjects(*camera, 0, filterCommand->layerMask);
++
++ // NOTE: If renderableTypes is None (0x0), no renderables are added
++ // NOTE: If no filter is run, all opaque objects are rendered.
++ renderablesFiltered = true;
++ }
++ break;
++
++ case CommandType::PipelineStateOverride:
++ {
++ auto pipelineCommand = static_cast<const QSSGPipelineStateOverrideCommand *>(theCommand);
++ if (pipelineCommand->m_depthTestEnabled)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, *pipelineCommand->m_depthTestEnabled);
++ if (pipelineCommand->m_depthWriteEnabled)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, *pipelineCommand->m_depthWriteEnabled);
++ if (pipelineCommand->m_blendEnabled)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, *pipelineCommand->m_blendEnabled);
++ if (pipelineCommand->m_usesStencilReference)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef, *pipelineCommand->m_usesStencilReference);
++ if (pipelineCommand->m_usesScissor)
++ ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor, *pipelineCommand->m_usesScissor);
++ if (pipelineCommand->m_depthFunction)
++ ps.depthFunc = *pipelineCommand->m_depthFunction;
++ if (pipelineCommand->m_cullMode) {
++ ps.cullMode = *pipelineCommand->m_cullMode;
++ ps.userSetCullMode = true;
++ }
++ if (pipelineCommand->m_polygonMode)
++ ps.polygonMode = *pipelineCommand->m_polygonMode;
++ if (pipelineCommand->m_stencilOpFrontState)
++ ps.stencilOpFrontState = *pipelineCommand->m_stencilOpFrontState;
++ if (pipelineCommand->m_stencilWriteMask)
++ ps.stencilWriteMask = *pipelineCommand->m_stencilWriteMask;
++ if (pipelineCommand->m_stencilReference)
++ ps.stencilRef = *pipelineCommand->m_stencilReference;
++ if (pipelineCommand->m_viewport)
++ ps.viewport = *pipelineCommand->m_viewport;
++ if (pipelineCommand->m_scissor)
++ ps.scissor = *pipelineCommand->m_scissor;
++ if (pipelineCommand->m_targetBlend0)
++ ps.targetBlend[0] = *pipelineCommand->m_targetBlend0;
++ if (pipelineCommand->m_targetBlend1)
++ ps.targetBlend[1] = *pipelineCommand->m_targetBlend1;
++ if (pipelineCommand->m_targetBlend2)
++ ps.targetBlend[2] = *pipelineCommand->m_targetBlend2;
++ if (pipelineCommand->m_targetBlend3)
++ ps.targetBlend[3] = *pipelineCommand->m_targetBlend3;
++ if (pipelineCommand->m_targetBlend4)
++ ps.targetBlend[4] = *pipelineCommand->m_targetBlend4;
++ if (pipelineCommand->m_targetBlend5)
++ ps.targetBlend[5] = *pipelineCommand->m_targetBlend5;
++ if (pipelineCommand->m_targetBlend6)
++ ps.targetBlend[6] = *pipelineCommand->m_targetBlend6;
++ if (pipelineCommand->m_targetBlend7)
++ ps.targetBlend[7] = *pipelineCommand->m_targetBlend7;
++ break;
++ }
++ case CommandType::DepthStencilAttachment:
++ //auto depthStencilCommand = static_cast<const QSSGDepthStencilAttachment *>(theCommand);
++ QSSG_ASSERT(depthTextureAllocCommand == nullptr, break);
++ needsDepthStencilRenderBuffer = true;
++ break;
++ case CommandType::SubRenderPass:
++ {
++ auto subPassCommand = static_cast<const QSSGSubRenderPass *>(theCommand);
++ if (subPassCommand && subPassCommand->m_userPassId != QSSGResourceId::Invalid)
++ subPassIds.append(subPassCommand->m_userPassId);
++ break;
++ }
++ default:
++ qWarning() << "Effect command" << theCommand->typeAsString() << "not implemented";
++ break;
++ }
++ }
++
++ if (colorAttachments.size() > 4) {
++ colorAttachments.resize(4);
++ qWarning() << "UserRenderPass supports up to 4 color attachments only.";
++ }
++
++ // m_passNode contains state for this UserRenderPass
++
++ if (isTopLevelPass) {
++ // 1) Setup the render target
++ bool needsBuild = !renderTarget->isValid();
++
++ const qsizetype oldAttachmentCount = renderTarget->colorAttachmentCount();
++ // A pass without ColorAttachment commands gets one implicit default
++ // attachment, so compare against the effective count.
++ const qsizetype wantedAttachmentCount = qMax<qsizetype>(colorAttachments.size(), 1);
++ // If the number of attachments has changed, we need to rebuild.
++ if (oldAttachmentCount != wantedAttachmentCount)
++ needsBuild = true;
++
++ // Even if the render target is valid we need to check if the textures are still compatible.
++ if (!needsBuild) {
++ // Render target flags (e.g. PreserveColorContents) change the Metal load/store actions
++ // baked into the QRhiRenderPassDescriptor, so a flag change requires a full rebuild.
++ if (renderTarget->getRenderTarget() && renderTarget->getRenderTarget()->flags() != passNode->renderTargetFlags)
++ needsBuild = true;
++ }
++
++ if (!needsBuild) {
++ // Color attachments (oldAttachmentCount == wantedAttachmentCount here;
++ // an attachment without a command is the implicit default).
++ for (int i = 0; i != oldAttachmentCount; ++i) {
++ QRhiTexture::Format expectedFormat = QRhiTexture::RGBA8;
++ if (i < colorAttachments.size()) {
++ const auto &colorAttachment = colorAttachments.at(i);
++ QSSG_ASSERT(colorAttachment != nullptr, continue);
++ expectedFormat = QSSGBufferManager::toRhiFormat(colorAttachment->format());
++ }
++ const auto &texture = renderTarget->getColorTexture(i);
++ needsBuild = needsBuild || needsRebuild(&(*texture->texture()), targetSize, expectedFormat);
++ }
++
++ // depthStencilBuffer
++ if (needsDepthStencilRenderBuffer != (renderTarget->getDepthStencil() != nullptr))
++ needsBuild = true;
++
++ // depthTexture
++ if (depthTextureAllocCommand) {
++ const auto format = QSSGBufferManager::toRhiFormat(depthTextureAllocCommand->format());
++ const auto &depthTextureWrapper = renderTarget->getDepthTexture();
++ needsBuild = depthTextureWrapper == nullptr;
++ needsBuild = needsBuild || needsRebuild(&(*depthTextureWrapper->texture()), targetSize, format);
++ } else {
++ if (renderTarget->getDepthTexture() != nullptr)
++ needsBuild = true;
++ }
++
++ // Flags are baked into the render pass descriptor.
++ if (!needsBuild) {
++ const auto &rt = renderTarget->getRenderTarget();
++ if (rt && rt->flags() != passNode->renderTargetFlags)
++ needsBuild = true;
++ }
++ }
++
++ if (needsBuild) {
++ renderTarget->reset();
++
++ QRhiTextureRenderTargetDescription rtDesc;
++
++ // If no attachments are specified, create one (the implicit default).
++ const qsizetype colorAttachmentCount = wantedAttachmentCount;
++
++ bool createSucceeded = true;
++ bool colorAllocatorsNeedsUpdate = false;
++
++ {
++ // Used to set the color attachments in rtDesc below (don't let the raw ptrs leave this scope).
++ QVarLengthArray<QRhiTexture *, 4> textures;
++ for (qsizetype i = 0; i < colorAttachmentCount && createSucceeded ; ++i) {
++ if (i >= colorAttachments.size()) {
++ // No ColorAttachment command; create the implicit default attachment.
++ auto *tex = rhiCtx->rhi()->newTexture(QRhiTexture::RGBA8, targetSize, ps.samples, QRhiTexture::RenderTarget);
++ tex->setName(QByteArrayLiteral("Default color attachment"));
++ createSucceeded = createSucceeded && tex->create();
++ textures.push_back(tex);
++ continue;
++ }
++ const auto &colorAttCmd = colorAttachments.at(i);
++ const auto &name = colorAttCmd->m_name;
++ const auto format = QSSGBufferManager::toRhiFormat(colorAttCmd->format());
++ const auto &allocateTexCmd = colorAttCmd->m_textureCmd;
++ if (allocateTexCmd->texture() && !needsRebuild(&(*allocateTexCmd->texture()->texture()), targetSize, format)) {
++ // NOTE: The QRhiTexture is tracked by the QSSGUserRenderPassManager, even if we create a new
++ // shared pointer wrapper here, it will ask the manager before destroying it.
++ textures.push_back(allocateTexCmd->texture()->texture().get());
++ } else {
++ auto *tex = rhiCtx->rhi()->newTexture(format, targetSize, ps.samples, QRhiTexture::RenderTarget);
++ tex->setName(name);
++ createSucceeded = createSucceeded && tex->create();
++ textures.push_back(tex);
++ // We created a new texture, so we need to update the allocator.
++ // NOTE: Since the render target type will take ownership of the texture, we need to
++ // request the textures once the render target description is set!
++ colorAllocatorsNeedsUpdate = true;
++ }
++ }
++
++ rtDesc.setColorAttachments(textures.cbegin(), textures.cend());
++ }
++
++ if (needsDepthStencilRenderBuffer) {
++ auto renderBuffer = rhiCtx->rhi()->newRenderBuffer(QRhiRenderBuffer::DepthStencil, targetSize, ps.samples);
++ if (renderBuffer->create())
++ rtDesc.setDepthStencilBuffer(renderBuffer);
++ } else if (depthTextureAllocCommand) {
++ const auto format = QSSGBufferManager::toRhiFormat(depthTextureAllocCommand->format());
++ if (depthTextureAllocCommand->texture() && !needsRebuild(&(*depthTextureAllocCommand->texture()->texture()), targetSize, format)) {
++ rtDesc.setDepthTexture(depthTextureAllocCommand->texture()->texture().get());
++ } else {
++ // Create new depth texture
++ QRhiTexture *depthTex = rhiCtx->rhi()->newTexture(format, targetSize, ps.samples, QRhiTexture::RenderTarget);
++ if (depthTex->create())
++ rtDesc.setDepthTexture(depthTex);
++ }
++ }
++
++ if (createSucceeded) {
++ // Set description takes ownership of rtDesc and the textures inside it (Color + Depth).
++ renderTarget->setDescription(rhiCtx->rhi(), std::move(rtDesc), passNode->renderTargetFlags);
++
++ // Now we can update the allocators for the color attachments that were created here.
++ // The implicit default attachment has no command, so only command-backed
++ // attachments are updated.
++ if (colorAllocatorsNeedsUpdate) {
++ for (qsizetype i = 0; i < colorAttachments.size(); ++i) {
++ const auto &colorAttCmd = colorAttachments.at(i);
++ const auto &allocateTexCmd = colorAttCmd->m_textureCmd;
++ allocateTexCmd->setTexture(renderTarget->getColorTexture(i));
++ }
++ }
++
++ if (depthTextureAllocCommand)
++ depthTextureAllocCommand->setTexture(renderTarget->getDepthTexture());
++
++ } else {
++ renderTarget->resetRenderTarget();
++ qWarning() << "Failed to create textures for UserRenderPass";
++ }
++ }
++
++ }
++
++ Q_ASSERT(renderTarget->isValid());
++
++ ps.colorAttachmentCount = int(renderTarget->colorAttachmentCount());
++
++ // Subpasses
++ for (const auto &subPassId : std::as_const(subPassIds)) {
++ QSSGRenderUserPass *userPassNode = QSSGRenderGraphObjectUtils::getResource<QSSGRenderUserPass>(subPassId);
++ QSSG_ASSERT(userPassNode && userPassNode->type == QSSGRenderGraphObject::Type::RenderPass, continue);
++ prepareSubPass(renderer, data, userPassNode, currentPassData.subPassData, currentPassData.renderableTexture);
++ }
++
++ if (passNode->passMode == QSSGRenderUserPass::PassModes::UserPass) {
++ // If no filter is specified, render all opaque objects
++ if (!renderablesFiltered && renderables.isEmpty())
++ renderables = data.getSortedOpaqueRenderableObjects(*camera);
++
++ if (passNode->materialMode == QSSGRenderUserPass::MaterialModes::AugmentMaterial) {
++
++ QSSGUserShaderAugmentation shaderAugmentation = passNode->shaderAugmentation;
++
++ QSSG_ASSERT(shaderAugmentation.outputs.size() == 0, shaderAugmentation.outputs.clear());
++ shaderAugmentation.defines = std::move(currentPassData.shaderDefines);
++
++ for (int i = 0, end = colorAttachments.size(); i < end; ++i) {
++ const auto &colorAttCmd = colorAttachments.at(i);
++ const auto &name = colorAttCmd->m_name;
++ if (name.size() > 0)
++ shaderAugmentation.outputs.push_back(name);
++ else
++ shaderAugmentation.outputs.push_back(getDefaultOutputName(size_t(i)));
++ }
++
++ QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
++ shaderFeatures.disableTonemapping();
++
++ currentPassData.index = RenderHelpers::rhiPrepareAugmentedUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), shaderAugmentation, data, renderables, shaderFeatures);
++ } else if (passNode->materialMode == QSSGRenderUserPass::MaterialModes::OverrideMaterial) {
++ // Every renderable will use the override material
++ QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
++ shaderFeatures.disableTonemapping();
++
++ currentPassData.index = RenderHelpers::rhiPrepareOverrideMaterialUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), passNode->overrideMaterial, data, renderables, shaderFeatures);
++
++ } else {
++ // Use original material of the renderables
++ QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
++ shaderFeatures.disableTonemapping();
++ currentPassData.index = RenderHelpers::rhiPrepareOriginalMaterialUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), data, renderables, shaderFeatures);
++ }
++ outData.push_back(currentPassData);
++ } else {
++ // Wrapped Built-in Passes
++ if (passNode->passMode == QSSGRenderUserPass::PassModes::SkyboxPass) {
++ if (rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
++ if (data.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && data.layer.skyBoxCubeMap) {
++ if (!currentPassData.skyboxCubeMapPass)
++ currentPassData.skyboxCubeMapPass = SkyboxCubeMapPass();
++
++ currentPassData.skyboxCubeMapPass->skipTonemapping = true;
++ currentPassData.skyboxCubeMapPass->renderPrep(renderer, data);
++ currentPassData.skyboxCubeMapPass->ps.samples = ps.samples;
++ currentPassData.skyboxCubeMapPass->rpDesc = renderTarget->getRenderPassDescriptor().get();
++
++ currentPassData.skyboxPass = std::nullopt;
++ } else if ((data.layer.background == QSSGRenderLayer::Background::SkyBox && data.layer.lightProbe)
++ || (data.layer.background == QSSGRenderLayer::Background::SkyMaterial && data.layer.skyMaterial)) {
++ if (!currentPassData.skyboxPass)
++ currentPassData.skyboxPass = SkyboxPass();
++
++ currentPassData.skyboxPass->skipTonemapping = true;
++ currentPassData.skyboxPass->renderPrep(renderer, data);
++ currentPassData.skyboxPass->ps.samples = ps.samples;
++ currentPassData.skyboxPass->rpDesc = renderTarget->getRenderPassDescriptor().get();
++
++ currentPassData.skyboxCubeMapPass = std::nullopt;
++ }
++ outData.push_back(currentPassData);
++ }
++ } else if (passNode->passMode == QSSGRenderUserPass::PassModes::Item2DPass) {
++ if (!currentPassData.item2DPass)
++ currentPassData.item2DPass = Item2DPass();
++ const bool hasItem2Ds = (data.item2DsView.size() > 0);
++ if (hasItem2Ds) {
++ //backup render target
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(renderer.contextInterface()->rhiContext().get());
++ QRhiRenderTarget *prevRenderTarget = rhiCtx->renderTarget();
++ rhiCtxD->setRenderTarget(renderTarget->getRenderTarget().get());
++ currentPassData.item2DPass->renderPrep(renderer, data);
++ //restore render target
++ rhiCtxD->setRenderTarget(prevRenderTarget);
++ outData.push_back(currentPassData);
++ }
++ }
++ }
++ }
++
++ QT_END_NAMESPACE