Revision control

Copy as Markdown

// 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