Revision control

Copy as Markdown

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssgdebugdrawsystem_p.h"
#include "qssgrenderhelpers_p.h"
#include "qssgrenderer_p.h"
#include "qssglayerrenderdata_p.h"
#include <QtQuick3DRuntimeRender/private/qssgrenderskymaterial_p.h>
#include "qssgrhiparticles_p.h"
#include "qssgrhiquadrenderer_p.h"
#include "../qssgrendercontextcore.h"
#include "../qssgrhicustommaterialsystem_p.h"
#include "../resourcemanager/qssgrenderbuffermanager_p.h"
#include "../qssgrenderdefaultmaterialshadergenerator_p.h"
#include "rendererimpl/qssgshadowmaphelpers_p.h"
#include <QtQuick3DUtils/private/qssgassert_p.h>
#include <QtGui/qquaternion.h>
#include <QtCore/qbitarray.h>
#include <cmath>
QT_BEGIN_NAMESPACE
static constexpr float QSSG_PI = float(M_PI);
static constexpr float QSSG_HALFPI = float(M_PI_2);
static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL =
QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps,
QSSGSubsetRenderable &subsetRenderable,
const QSSGShaderFeatures &featureSet,
const QSSGUserShaderAugmentation &shaderAugmentation = {})
{
auto &renderer(subsetRenderable.renderer);
const auto &shaderPipeline = QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(*renderer, subsetRenderable, featureSet, shaderAugmentation);
if (shaderPipeline) {
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
// NOTE: It's not just custom materials that can have custom property textures,
// augmented default materials can have them too.
shaderPipeline->resetExtraTextures();
}
return shaderPipeline;
}
static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps,
QSSGParticlesRenderable &particleRenderable,
const QSSGShaderFeatures &featureSet)
{
auto &renderer(particleRenderable.renderer);
const auto &shaderPipeline = QSSGParticleRenderer::getShaderPipelineParticles(*renderer, particleRenderable, featureSet);
if (shaderPipeline)
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
return shaderPipeline;
}
static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline,
QSSGRhiContext *rhiCtx,
const QSSGLayerRenderData &inData,
char *ubufData,
QSSGRhiGraphicsPipelineState *ps,
QSSGSubsetRenderable &subsetRenderable,
const QSSGRenderCameraList &cameras,
const QVector2D *depthAdjust,
const QMatrix4x4 *alteredModelViewProjection,
const QSSGRenderGraphObject *materialOverride = nullptr)
{
const auto &renderer(subsetRenderable.renderer);
const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
QSSGRenderMvpArray alteredMvpList;
if (alteredModelViewProjection)
alteredMvpList[0] = *alteredModelViewProjection;
const auto &modelNode = subsetRenderable.modelContext.model;
QRhiTexture *lightmapTexture = inData.getLightmapTexture(subsetRenderable.modelContext);
const auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(modelNode);
const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : inData.getGlobalTransform(modelNode));
QSSGMaterialShaderGenerator::setRhiMaterialProperties(*renderer->contextInterface(),
shaderPipeline,
ubufData,
ps,
materialOverride ? *materialOverride : subsetRenderable.material,
subsetRenderable.shaderDescription,
inData.getDefaultMaterialPropertyTable(),
cameras,
alteredModelViewProjection ? alteredMvpList : subsetRenderable.modelContext.modelViewProjections,
subsetRenderable.modelContext.normalMatrix,
modelMatrix,
clipSpaceCorrMatrix,
localInstanceTransform,
globalInstanceTransform,
toDataView(modelNode.morphWeights),
subsetRenderable.firstImage,
subsetRenderable.opacity,
inData,
subsetRenderable.lights,
subsetRenderable.reflectionProbe,
subsetRenderable.renderableFlags.receivesShadows(),
subsetRenderable.renderableFlags.receivesReflections(),
depthAdjust,
lightmapTexture);
}
static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
{
bool invertible = false;
QMatrix4x4 inv = projection.inverted(&invertible);
Q_ASSERT(invertible);
// The frustum points will be in this orientation
//
// bottom top
// 4__________5 7__________6
// \ / \ /
// \ / \ /
// \____/ \____/
// 0 1 3 2
return { inv.map(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)), inv.map(QVector3D(+1, +1, -1)),
inv.map(QVector3D(-1, +1, -1)), inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
inv.map(QVector3D(+1, +1, +1)), inv.map(QVector3D(-1, +1, +1)) };
}
static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
{
QSSGBoxPoints pts;
for (int i = 0; i < 4; ++i) {
const QVector3D forward = frustumPoints[i + 4] - frustumPoints[i];
pts[i] = frustumPoints[i] + forward * t0;
pts[i + 4] = frustumPoints[i] + forward * t1;
}
return pts;
}
static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix,
const QMatrix4x4 &lightMatrixInverted,
const QVector3D &lightPivot,
const QVector3D &lightForward,
const QVector3D &lightUp,
const float shadowMapResolution,
const float pcfRadius,
const QSSGBoxPoints &frustumPoints,
float frustumStartT,
float frustumEndT,
float frustumRadius,
bool lockShadowmapTexels,
const QSSGBounds3 &castingBox,
const QSSGBounds3 &receivingBox,
QSSGDebugDrawSystem *debugDrawSystem,
const bool drawCascades,
const bool drawSceneCascadeIntersection)
{
if (!castingBox.isFinite() || castingBox.isEmpty() || !receivingBox.isFinite() || receivingBox.isEmpty())
return nullptr; // Return early, no casting or receiving objects means no shadows
Q_ASSERT(frustumStartT <= frustumEndT);
Q_ASSERT(frustumStartT >= 0.f);
Q_ASSERT(frustumEndT <= 1.0f);
auto transformPoints = [&](const QSSGBoxPoints &points) {
QSSGBoxPoints result;
for (int i = 0; i < int(points.size()); ++i) {
result[i] = lightMatrix.map(points[i]);
}
return result;
};
QSSGBoxPoints frustumPointsSliced = sliceFrustum(frustumPoints, frustumStartT, frustumEndT);
if (drawCascades)
ShadowmapHelpers::addDebugFrustum(frustumPointsSliced, QColorConstants::Black, debugDrawSystem);
const QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPointsSliced,
receivingBox.toQSSGBoxPoints(),
drawSceneCascadeIntersection ? debugDrawSystem : nullptr,
QColorConstants::DarkGray);
if (receivingSliced.isEmpty())
return nullptr;
QSSGBounds3 receivingFrustumSlicedLightSpace;
for (const QVector3D &point : receivingSliced)
receivingFrustumSlicedLightSpace.include(lightMatrix.map(point));
// Slice casting box by frustumBounds' left, right, up, down planes
const QList<QVector3D> castingPointsLightSpace = ShadowmapHelpers::intersectBoxByBox(receivingFrustumSlicedLightSpace,
transformPoints(castingBox.toQSSGBoxPointsNoEmptyCheck()));
if (castingPointsLightSpace.isEmpty())
return nullptr;
// Create box containing casting and receiving from light space:
QSSGBounds3 castReceiveBounds;
for (const QVector3D &p : castingPointsLightSpace) {
castReceiveBounds.include(p);
}
for (const QVector3D &p : receivingFrustumSlicedLightSpace.toQSSGBoxPointsNoEmptyCheck()) {
float zMax = qMax(p.z(), castReceiveBounds.maximum.z());
float zMin = qMin(p.z(), castReceiveBounds.minimum.z());
castReceiveBounds.maximum.setZ(zMax);
castReceiveBounds.minimum.setZ(zMin);
}
// Expand to fit pcf radius
castReceiveBounds.fatten(pcfRadius);
QVector3D boundsCenterWorld = lightMatrixInverted.map(castReceiveBounds.center());
QVector3D boundsDims = castReceiveBounds.dimensions();
boundsDims.setZ(boundsDims.z() * 1.01f); // Expand slightly in z direction to avoid pancaking precision errors
if (lockShadowmapTexels) {
// Calculate center position aligned to texel size to avoid shimmering
const float diam = (pcfRadius + frustumRadius) * 2.0f;
const float texelsPerUnit = diam / shadowMapResolution;
QVector3D centerLight = lightMatrix.map(boundsCenterWorld);
float x = centerLight.x();
float y = centerLight.y();
float z = centerLight.z();
centerLight = QVector3D(int(x / texelsPerUnit), int(y / texelsPerUnit), int(z / texelsPerUnit)) * texelsPerUnit;
boundsCenterWorld = lightMatrixInverted.map(centerLight);
boundsDims.setX(diam);
boundsDims.setY(diam);
} else {
// We expand the shadowmap to cover the bounds with one extra texel on all sides
const float texelExpandFactor = shadowMapResolution / (shadowMapResolution - 2);
boundsDims.setX(boundsDims.x() * texelExpandFactor);
boundsDims.setY(boundsDims.y() * texelExpandFactor);
}
QRectF theViewport(0.0f, 0.0f, boundsDims.x(), boundsDims.y());
auto camera = std::make_unique<QSSGRenderCamera>(QSSGRenderGraphObject::Type::OrthographicCamera);
camera->clipPlanes = QSSGRenderCamera::ClipPlanes{-0.5f * boundsDims.z(), 0.5f * boundsDims.z()};
camera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
camera->parent = nullptr;
camera->localTransform = QSSGRenderNode::calculateTransformMatrix(boundsCenterWorld,
QSSGRenderNode::initScale,
lightPivot,
QQuaternion::fromDirection(lightForward, lightUp));
QSSGRenderCamera::calculateProjectionInternal(*camera, theViewport);
return camera;
}
static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamerasForShadowMap(const QSSGLayerRenderData &data,
const QSSGRenderCamera &inCamera,
const QSSGRenderLight *inLight,
const int shadowMapResolution,
const float pcfRadius,
const float clipNear,
const float clipFar,
const QSSGBounds3 &castingObjectsBox,
const QSSGBounds3 &receivingObjectsBox,
bool lockShadowmapTexels,
QSSGDebugDrawSystem *debugDrawSystem,
bool drawCascades,
bool drawSceneCascadeIntersection)
{
Q_ASSERT(inLight->type == QSSGRenderLight::Type::DirectionalLight);
QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> result;
if (clipNear >= clipFar || qFuzzyCompare(clipNear, clipFar))
return result;
const QMatrix4x4 lightGlobalTransform = data.getGlobalTransform(*inLight);
const QVector3D lightDir = inLight->getDirection(lightGlobalTransform);
const QVector3D lightPivot = inLight->pivot;
const QVector3D forward = lightDir.normalized();
const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
: QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
QMatrix4x4 lightMatrix;
lightMatrix.setRow(0, QVector4D(right, 0.0f));
lightMatrix.setRow(1, QVector4D(up, 0.0f));
lightMatrix.setRow(2, QVector4D(forward, 0.0f));
lightMatrix.setRow(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
QMatrix4x4 lightMatrixInverted = lightMatrix.inverted();
const float farScale = (clipFar - clipNear) / (inCamera.clipPlanes.clipFar() - inCamera.clipPlanes.clipNear());
const QMatrix4x4 cameraGlobalTransform = data.getGlobalTransform(inCamera);
QMatrix4x4 viewProjection(Qt::Uninitialized);
inCamera.calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
const QSSGBoxPoints frustum = computeFrustumBounds(viewProjection);
const QSSGBoxPoints frustumUntransformed = lockShadowmapTexels ? computeFrustumBounds(inCamera.projection) : QSSGBoxPoints();
// We calculate the radius of the cascade without rotation or translation so we always get
// the same floating point value.
const auto calcFrustumRadius = [&](float t0, float t1) -> float {
const QSSGBoxPoints pts = sliceFrustum(frustumUntransformed, t0 * farScale, t1 * farScale);
QVector3D center = QVector3D(0.f, 0.f, 0.f);
for (QVector3D point : pts) {
center += point;
}
center = center * 0.125f;
float radiusSquared = 0;
for (QVector3D point : pts) {
radiusSquared = qMax(radiusSquared, (point - center).lengthSquared());
}
return std::sqrt(radiusSquared);
};
const auto computeSplitRanges = [inLight](const QVarLengthArray<float, 3> &splits) -> QVarLengthArray<QPair<float, float>, 4> {
QVarLengthArray<QPair<float, float>, 4> ranges;
const float csmBlendRatio = inLight->m_csmBlendRatio;
float t0 = 0.f;
for (qsizetype i = 0; i < splits.length(); i++) {
const float tI = qBound(qMin(t0 + 0.01f, 1.0f), splits[i], 1.0f);
ranges.emplace_back(t0, qMin(1.0f, tI + csmBlendRatio));
t0 = tI;
}
ranges.emplace_back(t0, 1.0f);
return ranges;
};
const auto computeFrustums = [&](const QVarLengthArray<float, 3> &splits) {
for (const auto &range : computeSplitRanges(splits)) {
const float frustumRadius = lockShadowmapTexels ? calcFrustumRadius(range.first, range.second) : 0.0f;
auto camera = computeShadowCameraFromFrustum(lightMatrix,
lightMatrixInverted,
lightPivot,
forward,
up,
shadowMapResolution,
pcfRadius,
frustum,
range.first * farScale,
range.second * farScale,
frustumRadius,
lockShadowmapTexels,
castingObjectsBox,
receivingObjectsBox,
debugDrawSystem,
drawCascades,
drawSceneCascadeIntersection);
result.emplace_back(std::move(camera));
}
};
switch (inLight->m_csmNumSplits) {
case 0: {
computeFrustums({});
break;
}
case 1: {
computeFrustums({ inLight->m_csmSplit1 });
break;
}
case 2: {
computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2 });
break;
}
case 3: {
computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2, inLight->m_csmSplit3 });
break;
}
default:
Q_UNREACHABLE();
break;
}
return result;
}
static void setupCubeReflectionCameras(const QSSGLayerRenderData &inData, const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
{
Q_ASSERT(inProbe != nullptr);
// setup light matrix
quint32 mapRes = 1 << inProbe->reflectionMapRes;
QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
static const QQuaternion rotOfs[6] {
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
};
auto inProbeGlobalTranform = inData.getGlobalTransform(*inProbe);
const QVector3D inProbePos = QSSGRenderNode::getGlobalPos(inProbeGlobalTranform);
const QVector3D inProbePivot = inProbe->pivot;
for (int i = 0; i < 6; ++i) {
inCameras[i].parent = nullptr;
inCameras[i].clipPlanes = {1.0f, qMax<float>(2.0f, 10000.0f)};
inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inProbePos, QSSGRenderNode::initScale, inProbePivot, rotOfs[i]);
QSSGRenderCamera::calculateProjectionInternal(inCameras[i], theViewport);
}
}
static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx,
QSSGRhiShaderPipeline *shaderPipeline,
QSSGRenderableImage *renderableImage,
QSSGRhiShaderResourceBindingList &bindings,
bool isCustomMaterialMeshSubset = false)
{
static const auto imageAffectsAlpha = [](QSSGRenderableImage::Type mapType) {
return mapType == QSSGRenderableImage::Type::BaseColor ||
mapType == QSSGRenderableImage::Type::Diffuse ||
mapType == QSSGRenderableImage::Type::Translucency ||
mapType == QSSGRenderableImage::Type::Opacity;
};
while (renderableImage) {
const auto mapType = renderableImage->m_mapType;
if (imageAffectsAlpha(mapType)) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(mapType);
const int samplerHint = int(mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
});
bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
} // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
}
renderableImage = renderableImage->m_nextImage;
}
// For custom Materials we can't know which maps affect alpha, so map all
if (isCustomMaterialMeshSubset) {
QVector<QShaderDescription::InOutVariable> samplerVars =
shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
const auto combinedSamplers = shaderPipeline->vertexStage()->shader().description().combinedImageSamplers();
for (const QShaderDescription::InOutVariable &var : combinedSamplers) {
auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
[&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
if (it == samplerVars.cend())
samplerVars.append(var);
}
int maxSamplerBinding = -1;
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
// Will need to set unused image-samplers to something dummy
// because the shader code contains all custom property textures,
// and not providing a binding for all of them is invalid with some
// graphics APIs (and will need a real texture because setting a
// null handle or similar is not permitted with some of them so the
// srb does not accept null QRhiTextures either; but first let's
// figure out what bindings are unused in this frame)
QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
if (maxSamplerBinding >= 0) {
// custom property textures
int customTexCount = shaderPipeline->extraTextureCount();
for (int i = 0; i < customTexCount; ++i) {
const QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
if (samplerBinding >= 0) {
samplerBindingsSpecified.setBit(samplerBinding);
QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
bindings.addTexture(samplerBinding,
RENDERER_VISIBILITY_ALL,
t.texture,
sampler);
}
}
}
// use a dummy texture for the unused samplers in the shader
QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars)) {
if (!samplerBindingsSpecified.testBit(var.binding)) {
QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
bindings.addTexture(var.binding, RENDERER_VISIBILITY_ALL, t, dummySampler);
}
}
}
}
static void setupCubeShadowCameras(const QSSGLayerRenderData &inData,
const QSSGRenderLight *inLight,
float shadowMapFar,
QSSGRenderCamera inCameras[6])
{
Q_ASSERT(inLight != nullptr);
Q_ASSERT(inLight->type != QSSGRenderLight::Type::DirectionalLight);
// setup light matrix
quint32 mapRes = inLight->m_shadowMapRes;
QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
static const QQuaternion rotOfs[6] {
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
};
const auto gt = inData.getGlobalTransform(*inLight);
const QVector3D inLightPos = QSSGRenderNode::getGlobalPos(gt);
constexpr QVector3D lightPivot = QVector3D(0, 0, 0);
for (int i = 0; i < 6; ++i) {
inCameras[i].parent = nullptr;
inCameras[i].clipPlanes = {1.0f, shadowMapFar};
inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inLightPos, QSSGRenderNode::initScale, lightPivot, rotOfs[i]);
QSSGRenderCamera::calculateProjectionInternal(inCameras[i], theViewport);
}
/*
if ( inLight->type == RenderLightTypes::Point ) return;
QVector3D viewDirs[6];
QVector3D viewUp[6];
QMatrix3x3 theDirMatrix( inLight->m_GlobalTransform.getUpper3x3() );
viewDirs[0] = theDirMatrix.transform( QVector3D( 1.f, 0.f, 0.f ) );
viewDirs[2] = theDirMatrix.transform( QVector3D( 0.f, -1.f, 0.f ) );
viewDirs[4] = theDirMatrix.transform( QVector3D( 0.f, 0.f, 1.f ) );
viewDirs[0].normalize(); viewDirs[2].normalize(); viewDirs[4].normalize();
viewDirs[1] = -viewDirs[0];
viewDirs[3] = -viewDirs[2];
viewDirs[5] = -viewDirs[4];
viewUp[0] = viewDirs[2];
viewUp[1] = viewDirs[2];
viewUp[2] = viewDirs[5];
viewUp[3] = viewDirs[4];
viewUp[4] = viewDirs[2];
viewUp[5] = viewDirs[2];
for (int i = 0; i < 6; ++i)
{
inCameras[i].LookAt( inLightPos, viewUp[i], inLightPos + viewDirs[i] );
inCameras[i].CalculateGlobalVariables( theViewport, QVector2D( theViewport.m_Width,
theViewport.m_Height ) );
}
*/
}
static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
{
// TODO: non-static so it can be used from QSSGCustomMaterialSystem::rhiPrepareRenderable()?
const bool instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable, cameraDirection, cameraPosition, renderable->instancingLodMin, renderable->instancingLodMax);
int instanceBufferBinding = 0;
if (instancing) {
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
// set up new bindings for instanced buffers
const quint32 stride = renderable->modelContext.model.instanceTable->stride();
QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
std::copy(ia.inputLayout.cbeginBindings(), ia.inputLayout.cendBindings(), std::back_inserter(bindings));
bindings.append({ stride, QRhiVertexInputBinding::PerInstance });
instanceBufferBinding = bindings.size() - 1;
ia.inputLayout.setBindings(bindings.cbegin(), bindings.cend());
}
return instanceBufferBinding;
}
static void rhiPrepareResourcesForReflectionMap(const QSSGRenderContextInterface &context,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
QSSGReflectionMapEntry *pEntry,
QSSGRhiGraphicsPipelineState *ps,
const QSSGRenderableObjectList &sortedOpaqueObjects,
QSSGRenderCamera &inCamera,
QSSGRenderer &renderer,
QSSGRenderTextureCubeFace cubeFace)
{
using namespace RenderHelpers;
QSSGRhiContext *rhiCtx = context.rhiContext().get();
if ((inData.layer.background == QSSGRenderLayer::Background::SkyBox && (inData.layer.lightProbe || inData.layer.skyMaterial))
|| inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap
|| (inData.layer.background == QSSGRenderLayer::Background::SkyMaterial && inData.layer.skyMaterial))
rhiPrepareSkyBoxForReflectionMap(context, passKey, inData.layer, inCamera, renderer, pEntry, cubeFace);
QSSGShaderFeatures features = inData.getShaderFeatures();
// because of alteredCamera/alteredMvp below
features.set(QSSGShaderFeatures::Feature::DisableMultiView, true);
features.set(QSSGShaderFeatures::Feature::Ssao, false);
const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
for (const auto &handle : sortedOpaqueObjects) {
QSSGRenderableObject &inObject = *handle.obj;
QMatrix4x4 modelViewProjection;
if (inObject.type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || inObject.type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(inObject));
const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(renderable.shaderDescription) > 0;
const QMatrix4x4 &globalTransform = renderable.modelContext.globalTransform;
modelViewProjection = hasSkinning ? pEntry->m_viewProjection
: pEntry->m_viewProjection * globalTransform;
}
// here we pass on our own alteredCamera and alteredModelViewProjection
rhiPrepareRenderable(rhiCtx, passKey, inData, inObject, pEntry->m_rhiRenderPassDesc, ps, features, 1, 1,
&inCamera, &modelViewProjection, cubeFace, pEntry);
}
}
static inline void addDepthTextureBindings(QSSGRhiContext *rhiCtx,
QSSGRhiShaderPipeline *shaderPipeline,
QSSGRhiShaderResourceBindingList &bindings,
QBitArray *specifiedBindings = nullptr)
{
const auto markSpecifiedBinding = [specifiedBindings](int binding) {
if (specifiedBindings)
specifiedBindings->setBit(binding);
};
if (shaderPipeline->depthTexture()) {
const int depthTextureBinding = shaderPipeline->bindingForTexture("qt_depthTexture", int(QSSGRhiSamplerBindingHints::DepthTexture));
const int depthTextureArrayBinding = shaderPipeline->bindingForTexture("qt_depthTextureArray", int(QSSGRhiSamplerBindingHints::DepthTextureArray));
if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
// nearest min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
if (depthTextureBinding >= 0) {
markSpecifiedBinding(depthTextureBinding);
bindings.addTexture(depthTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
}
if (depthTextureArrayBinding >= 0) {
markSpecifiedBinding(depthTextureArrayBinding);
bindings.addTexture(depthTextureArrayBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
}
} // else ignore, not an error
}
// SSAO texture
if (shaderPipeline->ssaoTexture()) {
const int ssaoTextureBinding = shaderPipeline->bindingForTexture("qt_aoTexture", int(QSSGRhiSamplerBindingHints::AoTexture));
const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture("qt_aoTextureArray", int(QSSGRhiSamplerBindingHints::AoTextureArray));
if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
// linear min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
if (ssaoTextureBinding >= 0) {
markSpecifiedBinding(ssaoTextureBinding);
bindings.addTexture(ssaoTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->ssaoTexture(), sampler);
}
if (ssaoTextureArrayBinding >= 0) {
markSpecifiedBinding(ssaoTextureArrayBinding);
bindings.addTexture(ssaoTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->ssaoTexture(), sampler);
}
} // else ignore, not an error
}
}
static inline void addNormalTextureBindings(QSSGRhiContext *rhiCtx,
QSSGRhiShaderPipeline *shaderPipeline,
QSSGRhiShaderResourceBindingList &bindings)
{
if (shaderPipeline->normalTexture()) {
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
// nearest min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->normalTexture(), sampler);
} // else ignore, not an error
}
}
void RenderHelpers::addAccumulatorImageBindings(QSSGRhiShaderPipeline *shaderPipeline,
QSSGRhiShaderResourceBindingList &bindings)
{
const auto images = shaderPipeline->oitImages();
if (!images[0])
return;
#ifdef QSSG_OIT_USE_BUFFERS
bindings.addStorageBufferStore(3, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[0]);
bindings.addStorageBufferLoadStore(4, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[1]);
bindings.addStorageBufferLoadStore(5, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[2]);
#else
int abuf = shaderPipeline->bindingForStorageImage("qt_imgAbuffer");
int aux = shaderPipeline->bindingForStorageImage("qt_imgAux");
int counter = shaderPipeline->bindingForStorageImage("qt_imgCounter");
if (abuf == -1 || aux == -1 || counter == -1) {
qWarning()<<"Shader is missing image binding points;";
return;
}
bindings.addImageStore(abuf, QRhiShaderResourceBinding::FragmentStage, images[0], 0);
// atomic operations require loadstore
bindings.addImageLoadStore(aux, QRhiShaderResourceBinding::FragmentStage, images[1], 0);
bindings.addImageLoadStore(counter, QRhiShaderResourceBinding::FragmentStage, images[2], 0);
#endif
}
// Returns the srb for the bindings and records it in the draw call data, releasing the
// previously recorded srb if the bindings changed. The bindings refer to the draw call
// data's uniform buffer, so the srb must be owned by the draw call data; otherwise
// releaseDrawCallData() cannot purge it and the cache keeps an srb referencing a
// deleted buffer.
static QRhiShaderResourceBindings *drawCallDataSrb(QSSGRhiContextPrivate *rhiCtxD,
QSSGRhiDrawCallData &dcd,
const QSSGRhiShaderResourceBindingList &bindings)
{
if (!dcd.srb || bindings != dcd.bindings) {
dcd.srb = rhiCtxD->srb(bindings);
rhiCtxD->releaseCachedSrb(dcd.bindings);
dcd.bindings = bindings;
}
return dcd.srb;
}
static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx,
const QSSGLayerRenderData &inData,
QSSGPassKey passKey,
QSSGShadowMapEntry *pEntry,
QSSGRhiGraphicsPipelineState *ps,
const QVector2D *depthAdjust,
const QSSGRenderableObjectList &sortedOpaqueObjects,
QSSGRenderCamera &inCamera,
bool orthographic,
QSSGRenderTextureCubeFace cubeFace,
quint32 cascadeIndex)
{
QSSGShaderFeatures featureSet;
if (orthographic)
featureSet.set(QSSGShaderFeatures::Feature::OrthoShadowPass, true);
else
featureSet.set(QSSGShaderFeatures::Feature::PerspectiveShadowPass, true);
// Do note how updateUniformsForDefaultMaterial() get a single camera and a
// custom mvp; make sure multiview is disabled in the shader generator using
// the common flag, instead of it having to write logic for checking for
// OrthoShadowPoss || CubeShadowPass.
featureSet.set(QSSGShaderFeatures::Feature::DisableMultiView, true);
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
for (const auto &handle : sortedOpaqueObjects) {
QSSGRenderableObject *theObject = handle.obj;
QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
QSSGShaderFeatures objectFeatureSet = featureSet;
const bool isOpaqueDepthPrePass = theObject->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
if (isOpaqueDepthPrePass)
objectFeatureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
QSSGRhiDrawCallData *dcd = nullptr;
QMatrix4x4 modelViewProjection;
QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(*theObject));
if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(renderable.shaderDescription) > 0;
modelViewProjection = hasSkinning ? pEntry->m_lightViewProjection[cascadeIndex]
: pEntry->m_lightViewProjection[cascadeIndex] * renderable.modelContext.globalTransform;
// cascadeIndex is 0..3 for directional light and 0 for the pointlight & spotlight
// cubeFaceIdx is 0 for directional & spotlight and 0..5 for the pointlight
// pEntry is unique per light and a light can only be one of directional, point, or spotlight.
// NOTE: The material is deliberately not part of the key here. The draw call data belongs to
// the shadow map entry, so swapping the material reuses it instead of creating a new one.
const quintptr entryIdx = cascadeIndex + cubeFaceIdx + (quintptr(renderable.subset.offset) << 3);
dcd = &rhiCtxD->drawCallData({ passKey, &renderable.modelContext.model, nullptr, pEntry, entryIdx });
}
QSSGRhiShaderResourceBindingList bindings;
QSSGRhiShaderPipelinePtr shaderPipeline;
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*theObject));
if (theObject->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset) {
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, objectFeatureSet);
if (!shaderPipeline)
continue;
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
// calls updateUni with an alteredCamera and alteredModelViewProjection
QSSGRenderCameraList cameras({ &inCamera });
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, depthAdjust, &modelViewProjection);
if (blendParticles)
QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
if (blendParticles)
QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
} else if (theObject->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset) {
const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
ps->cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), objectFeatureSet);
if (!shaderPipeline)
continue;
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
// inCamera is the shadow camera, not the same as inData.renderedCameras
QSSGRenderCameraList cameras({ &inCamera });
customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, material, subsetRenderable,
cameras, depthAdjust, &modelViewProjection);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// Depth and SSAO textures, in case a custom material's shader code does something with them.
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
// and the normal texture, if there is one.
addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
if (isOpaqueDepthPrePass) {
addOpaqueDepthPrePassBindings(rhiCtx,
shaderPipeline.get(),
subsetRenderable.firstImage,
bindings,
(theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
}
// There is no screen texture at this stage. But the shader from a
// custom material may rely on it, and an object with that material
// can end up in the shadow map's object list. So bind a dummy
// texture then due to the lack of other options.
const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
if (screenTextureBinding >= 0) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(screenTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
dummyTexture, sampler);
}
if (screenTextureArrayBinding >= 0) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates, QSize(64, 64), Qt::black, inData.layer.viewCount);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(screenTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
dummyTexture, sampler);
}
}
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
subsetRenderable.rhiRenderData.shadowPass.pipeline = rhiCtxD->pipeline(*ps, pEntry->m_rhiRenderPassDesc[cascadeIndex], srb);
subsetRenderable.rhiRenderData.shadowPass.srb[cubeFaceIdx] = srb;
}
}
}
static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
{
// Assuming default values in the other TargetBlend fields
switch (materialBlend) {
case QSSGRenderDefaultMaterial::MaterialBlendMode::Screen:
targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
targetBlend->dstColor = QRhiGraphicsPipeline::One;
targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
break;
case QSSGRenderDefaultMaterial::MaterialBlendMode::Multiply:
targetBlend->srcColor = QRhiGraphicsPipeline::DstColor;
targetBlend->dstColor = QRhiGraphicsPipeline::Zero;
targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
break;
default:
// Use SourceOver for everything else
targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
targetBlend->dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
targetBlend->dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
break;
}
}
void rhiPrepareRenderableImp(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
QSSGRenderableObject &inObject,
QRhiRenderPassDescriptor *renderPassDescriptor,
QSSGRhiGraphicsPipelineState *ps,
QSSGShaderFeatures featureSet,
int samples,
int viewCount,
bool screenMapPass,
QSSGRenderCamera *alteredCamera,
QMatrix4x4 *alteredModelViewProjection,
QSSGRenderTextureCubeFace cubeFace,
QSSGReflectionMapEntry *entry,
bool oit)
{
const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
switch (inObject.type) {
case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
{
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
if ((cubeFace != QSSGRenderTextureCubeFaceNone)) {
// Disable tonemapping for the reflection pass
featureSet.disableTonemapping();
}
if (subsetRenderable.renderableFlags.rendersWithLightmap())
featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
const auto &shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
if (shaderPipeline) {
// Unlike the subsetRenderable (which is allocated per frame so is
// not persistent in any way), the model reference is persistent in
// the sense that it references the model node in the scene graph.
// Combined with the layer node (multiple View3Ds may share the
// same scene!), this is suitable as a key to get the uniform
// buffers that were used with the rendering of the same model in
// the previous frame.
QSSGRhiShaderResourceBindingList bindings;
const auto &modelNode = subsetRenderable.modelContext.model;
const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
// NOTE:
// - entryIdx should 0 for QSSGRenderTextureCubeFaceNone (and a null entry).
// In all other cases the entryIdx is a combination of the cubeface idx and the subset
// offset, where the lower bits are the cubeface idx. The per-probe reflection map
// entry, if any, goes in the key's mapEntry slot. The key's resource must be the
// material itself, and nothing but the material, since
// cleanupDrawCallDataForResource() releases the entries for a to-be-destroyed
// material by matching on the resource.
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone)
* (cubeFaceIdx + (quintptr(subsetRenderable.subset.offset) << 3));
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, &modelNode, &subsetRenderable.material, entry,
entryIdx });
shaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
if (alteredCamera) {
Q_ASSERT(alteredModelViewProjection);
QSSGRenderCameraList cameras({ alteredCamera });
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, nullptr, alteredModelViewProjection);
} else {
Q_ASSERT(!alteredModelViewProjection);
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
}
if (blendParticles)
QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
if (blendParticles)
QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
ps->samples = samples;
ps->viewCount = viewCount;
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
if (!oit || (oit && inData.layer.oitMethod == QSSGRenderLayer::OITMethod::None))
fillTargetBlend(&ps->targetBlend[0], material.blendMode);
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
QVector3D cameraDirection = cameraDatas[0].direction;
QVector3D cameraPosition = cameraDatas[0].position;
if (alteredCamera) {
const QMatrix4x4 camGlobalTranform = inData.getGlobalTransform(*alteredCamera);
cameraDirection = QSSGRenderNode::getScalingCorrectDirection(camGlobalTranform);
cameraPosition = QSSGRenderNode::getGlobalPos(camGlobalTranform);
}
int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf, 0, shaderPipeline->ub0Size());
if (shaderPipeline->isLightingEnabled()) {
bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd.ubuf,
shaderPipeline->ub0LightDataOffset(),
sizeof(QSSGShaderLightsUniformData));
bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd.ubuf,
shaderPipeline->ub0DirectionalLightDataOffset(),
sizeof(QSSGShaderDirectionalLightsUniformData));
}
// Texture maps
QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
} // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
renderableImage = renderableImage->m_nextImage;
}
if (shaderPipeline->isLightingEnabled()) {
// Shadow map atlas
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture"); binding >= 0) {
QRhiTexture *texture = shaderPipeline->shadowMapAtlasTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture({ }, resourceUpdates, QSize(1, 1), Qt::black, 2);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Shadow map blue noise
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
if (binding >= 0) {
QRhiTexture *texture = shadowMapBlueNoise;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
} else {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
}
// Prioritize reflection texture over Light Probe texture because
// reflection texture also contains the irradiance and pre filtered
// values for the light probe.
if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
const auto mipMapFilter = reflectionTexture && reflectionTexture->flags().testFlag(QRhiTexture::Flag::MipMapped)
? QRhiSampler::Linear
: QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
mipMapFilter,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
if (reflectionSampler >= 0 && reflectionTexture)
bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
}
if (int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
binding >= 0) {
auto texture = shaderPipeline->lightProbeTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
Q_ASSERT(texture);
QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::Linear, // enables mipmapping
QSSGRhiHelpers::toRhi(tiling.first),
QSSGRhiHelpers::toRhi(tiling.second),
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Screen Texture
if (shaderPipeline->screenTexture()) {
const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
QRhiTexture *screenTexture = shaderPipeline->screenTexture();
// If we're called as part of the screen map pass there's obviosly no screen texture available, but the shader may still expect it, so bind a dummy texture.
if (screenMapPass) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
// Just mirror what is set up by the ScreenMap pass.
const QRhiTexture::Flags flags = screenTexture->flags();
// and set the dummy texture so there's something for the shader to sample.
screenTexture = rhiCtx->dummyTexture(flags, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
// linear min/mag, mipmap filtering depends on the
// texture, with SCREEN_TEXTURE there are no mipmaps, but
// once SCREEN_MIP_TEXTURE is seen the texture (the same
// one) has mipmaps generated.
QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
? QRhiSampler::Linear : QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
if (screenTextureBinding >= 0) {
bindings.addTexture(screenTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
screenTexture, sampler);
}
if (screenTextureArrayBinding >= 0) {
bindings.addTexture(screenTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
screenTexture, sampler);
}
} // else ignore, not an error
}
if (shaderPipeline->lightmapTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
if (binding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
bindings.addTexture(binding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->lightmapTexture(), sampler);
} // else ignore, not an error
}
}
// Depth and SSAO textures
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
// Normal texture
addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
if (oit && inData.layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
RenderHelpers::addAccumulatorImageBindings(shaderPipeline.get(), bindings);
// Instead of always doing a QHash find in srb(), store the binding
// list and the srb object in the per-model+material
// QSSGRhiUniformBufferSet. While this still needs comparing the
// binding list, to see if something has changed, it results in
// significant gains with lots of models in the scene (because the
// srb hash table becomes large then, so avoiding the lookup as
// much as possible is helpful)
QRhiShaderResourceBindings *&srb = dcd.srb;
bool srbChanged = false;
if (!srb || bindings != dcd.bindings) {
srb = rhiCtxD->srb(bindings);
rhiCtxD->releaseCachedSrb(dcd.bindings);
dcd.bindings = bindings;
srbChanged = true;
}
if (cubeFace != QSSGRenderTextureCubeFaceNone)
subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
else
subsetRenderable.rhiRenderData.mainPass.srb = srb;
const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
if (dcd.pipeline
&& !srbChanged
&& dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
&& dcd.renderTargetDescription == pipelineKey.renderTargetDescription
&& dcd.ps == *ps)
{
if (cubeFace != QSSGRenderTextureCubeFaceNone)
subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
else
subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
} else {
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(pipelineKey,
renderPassDescriptor,
srb);
dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
} else {
subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(pipelineKey,
renderPassDescriptor,
srb);
dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
}
dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
dcd.ps = *ps;
}
}
break;
}
case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
{
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
featureSet.set(QSSGShaderFeatures::Feature::LightProbe, inData.layer.lightProbe || material.m_iblProbe);
if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
// Disable tonemapping for the reflection pass
featureSet.disableTonemapping();
}
if (subsetRenderable.renderableFlags.rendersWithLightmap())
featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
customMaterialSystem.rhiPrepareRenderable(ps, passKey, subsetRenderable, featureSet,
material, inData, renderPassDescriptor, samples, viewCount, screenMapPass,
alteredCamera, cubeFace, alteredModelViewProjection, entry, oit);
break;
}
case QSSGRenderableObject::Type::Particles:
{
QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable, featureSet);
if (shaderPipeline) {
QSSGParticleRenderer::rhiPrepareRenderable(*shaderPipeline, passKey, rhiCtx, ps, particleRenderable, inData, renderPassDescriptor, samples, viewCount,
alteredCamera, cubeFace, entry, oit);
}
break;
}
}
}
void RenderHelpers::rhiPrepareRenderable(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
QSSGRenderableObject &inObject,
QRhiRenderPassDescriptor *renderPassDescriptor,
QSSGRhiGraphicsPipelineState *ps,
QSSGShaderFeatures featureSet,
int samples,
int viewCount,
QSSGRenderCamera *alteredCamera,
QMatrix4x4 *alteredModelViewProjection,
QSSGRenderTextureCubeFace cubeFace,
QSSGReflectionMapEntry *entry,
bool oit)
{
rhiPrepareRenderableImp(rhiCtx, passKey, inData, inObject, renderPassDescriptor, ps, featureSet, samples, viewCount,
false, alteredCamera, alteredModelViewProjection, cubeFace, entry, oit);
}
void RenderHelpers::rhiPrepareRenderableForScreenMapPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
QSSGRenderableObject &inObject,
QRhiRenderPassDescriptor *renderPassDescriptor,
QSSGRhiGraphicsPipelineState *ps,
QSSGShaderFeatures featureSet,
int samples,
int viewCount,
QSSGRenderCamera *alteredCamera,
QMatrix4x4 *alteredModelViewProjection,
QSSGRenderTextureCubeFace cubeFace,
QSSGReflectionMapEntry *entry,
bool oit)
{
rhiPrepareRenderableImp(rhiCtx, passKey, inData, inObject, renderPassDescriptor, ps, featureSet, samples, viewCount,
true, alteredCamera, alteredModelViewProjection, cubeFace, entry, oit);
}
void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
const QSSGRhiGraphicsPipelineState &state,
QSSGRenderableObject &object,
bool *needsSetViewport,
QSSGRenderTextureCubeFace cubeFace,
qsizetype userPassIndex)
{
switch (object.type) {
case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
{
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
QSSG_ASSERT(QSSGUserRenderPassManager::maxUserPassSlots() == std::size(subsetRenderable.rhiRenderData.userPassData), return);
QRhiGraphicsPipeline *ps = (userPassIndex >= 0 && size_t(userPassIndex) < QSSGUserRenderPassManager::maxUserPassSlots())
? subsetRenderable.rhiRenderData.userPassData[userPassIndex].pipeline : subsetRenderable.rhiRenderData.mainPass.pipeline;
QRhiShaderResourceBindings *srb = (userPassIndex >= 0 && size_t(userPassIndex) < QSSGUserRenderPassManager::maxUserPassSlots())
? subsetRenderable.rhiRenderData.userPassData[userPassIndex].srb : subsetRenderable.rhiRenderData.mainPass.srb;
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
}
if (!ps || !srb)
return;
QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
// QRhi optimizes out unnecessary binding of the same pipline
cb->setGraphicsPipeline(ps);
cb->setShaderResources(srb);
if (*needsSetViewport) {
cb->setViewport(state.viewport);
if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
cb->setScissor(state.scissor);
*needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if ( subsetRenderable.modelContext.model.instancing()) {
instances = subsetRenderable.modelContext.model.instanceCount();
// If the instance count is 0, the bail out before trying to do any
// draw calls. Making an instanced draw call with a count of 0 is invalid
// for Metal and likely other API's as well.
// It is possible that the particale system may produce 0 instances here
if (instances == 0)
return;
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
vertexBufferCount = 2;
}
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
cb->setStencilRef(state.stencilRef);
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
QVector<int>({subsetRenderable.modelContext.model.profilingId,
subsetRenderable.material.profilingId}));
break;
}
case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
{
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
customMaterialSystem.rhiRenderRenderable(rhiCtx, subsetRenderable, needsSetViewport, cubeFace, state, userPassIndex);
break;
}
case QSSGRenderableObject::Type::Particles:
{
QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
break;
}
}
}
static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp) {
// Convert normalized offsets/scales to actual pixel values
float x = atlasEntry.uOffset * atlasPixelSize.width();
float w = atlasEntry.uvScale * atlasPixelSize.width();
float h = atlasEntry.uvScale * atlasPixelSize.height();
// Y in atlasEntry is top-based, whereas QRhiViewport is bottom-based
// topY in pixel = atlasEntry.vOffset * atlasPixelSize.height()
// so bottom-left Y = totalHeight - topY - h
float y;
if (!yIsUp)
y = atlasPixelSize.height() - (atlasEntry.vOffset * atlasPixelSize.height()) - h;
else
y = atlasEntry.vOffset * atlasPixelSize.height();
return QRhiViewport(x, y, w, h);
}
void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
QSSGRhiGraphicsPipelineState &ps,
QSSGRenderShadowMap &shadowMapManager,
const QSSGRenderCamera &camera,
QSSGRenderCamera *debugCamera,
const QSSGShaderLightList &globalLights,
const QSSGRenderableObjectList &sortedOpaqueObjects,
QSSGRenderer &renderer,
const QSSGBounds3 &castingObjectsBox,
const QSSGBounds3 &receivingObjectsBox)
{
const QSSGLayerRenderData &layerData = *QSSGLayerRenderData::getCurrent(renderer);
QSSGDebugDrawSystem *debugDrawSystem = renderer.contextInterface()->debugDrawSystem().get();
const bool drawDirectionalLightShadowBoxes = layerData.layer.drawDirectionalLightShadowBoxes;
const bool drawPointLightShadowBoxes = layerData.layer.drawPointLightShadowBoxes;
const bool drawShadowCastingBounds = layerData.layer.drawShadowCastingBounds;
const bool drawShadowReceivingBounds = layerData.layer.drawShadowReceivingBounds;
const bool drawCascades = layerData.layer.drawCascades;
const bool drawSceneCascadeIntersection = layerData.layer.drawSceneCascadeIntersection;
const bool disableShadowCameraUpdate = layerData.layer.disableShadowCameraUpdate;
const bool drawCulledObjects = layerData.layer.drawCulledObjects;
QVector<bool> debugIsObjectCulled = drawCulledObjects ? QVector<bool>(sortedOpaqueObjects.size(), true) : QVector<bool>();
static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
QSSGRhiGraphicsPipelineState *ps,
const QSSGRenderableObjectList &sortedOpaqueObjects,
int cubeFace,
const QSSGBounds3 cameraBounds,
QVector<bool> &debugIsObjectCulled,
bool drawCulledObjects) {
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
bool needsSetViewport = true;
for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
const QSSGRenderableObjectHandle &handle = sortedOpaqueObjects[i];
QSSGRenderableObject *theObject = handle.obj;
// Only attempt to cull models if both of its bounds are valid
if (theObject->globalBoundsInstancing.isFinite() && theObject->globalBounds.isFinite()) {
const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
: theObject->globalBounds;
if (!globalBounds.isEmpty() && !cameraBounds.intersects(globalBounds)) {
continue;
}
}
if (Q_UNLIKELY(drawCulledObjects))
debugIsObjectCulled[i] = false;
QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
? renderable->subset.rhi.indexBuffer->buffer()
: nullptr;
// Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
if (!renderable->rhiRenderData.shadowPass.pipeline)
continue;
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
cb->setShaderResources(srb);
if (needsSetViewport) {
cb->setViewport(ps->viewport);
needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if (renderable->modelContext.model.instancing()) {
instances = renderable->modelContext.model.instanceCount();
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
vertexBufferCount = 2;
}
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, renderable->subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances, renderable->subset.lodOffset(renderable->subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(renderable->subset.count, instances, renderable->subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
QVector<int>({renderable->modelContext.model.profilingId,
renderable->material.profilingId}));
}
}
};
static const auto rhiClearShadowMap = [](QSSGRenderer &renderer, QSSGRenderShadowMap &shadowMapManager, QSSGRhiContext *rhiCtx, QSSGRhiGraphicsPipelineState *ps, QRhiRenderPassDescriptor *renderPassDesc) {
auto clearShadowMapShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiClearShadowMapShader();
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, clearShadowMapShaderPipeline.get());
// Disable Depth Test and Depth Write
ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, false);
ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, shadowMapManager.shadowClearSrb(), renderPassDesc, {});
// Reset
ps->flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
};
QRhi *rhi = rhiCtx->rhi();
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
// We need to deal with a clip depth range of [0, 1] or
// [-1, 1], depending on the graphics API underneath.
QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
if (rhi->isClipDepthZeroToOne()) {
// d is [0, 1] so no need for any mapping
depthAdjust[0] = 0.0f;
depthAdjust[1] = 1.0f;
} else {
// d is [-1, 1]
depthAdjust[0] = 1.0f;
depthAdjust[1] = 0.5f;
}
if (drawShadowCastingBounds)
ShadowmapHelpers::addDebugBox(castingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Red, debugDrawSystem);
if (drawShadowReceivingBounds)
ShadowmapHelpers::addDebugBox(receivingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Green, debugDrawSystem);
// Create shadow map for each light in the scene
const QSize atlasTextureSize = shadowMapManager.shadowMapAtlasTexture()->pixelSize();
// Make sure quad renderer is ready
renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
continue;
QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(i);
if (!pEntry)
continue;
const auto &light = globalLights[i].light;
if (!shadowMapManager.shadowMapAtlasTexture())
break;
if (light->type == QSSGRenderLight::Type::DirectionalLight || light->type == QSSGRenderLight::Type::SpotLight) {
const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
// This is just a way to store the old camera so we can use it for debug
// drawing. There are probably cleaner ways to do this
if (!disableShadowCameraUpdate && debugCamera) {
debugCamera->clipPlanes = camera.clipPlanes;
debugCamera->projection = camera.projection;
// NOTE: Since the debug camera is an internally injected camera, there will only be
// the local transform. Anywhere the global transform is looked up for the debug camera
// it will return the local transform.
debugCamera->localTransform = layerData.getGlobalTransform(camera);
}
QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
if (light->type == QSSGRenderLight::Type::DirectionalLight) {
const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
const float clipNear = camera.clipPlanes.clipNear();
const float clipFar = qMin(light->m_shadowMapFar, camera.clipPlanes.clipFar());
const float clipRange = clipFar - clipNear;
cascades = setupCascadingCamerasForShadowMap(layerData,
disableShadowCameraUpdate ? *debugCamera : camera,
light,
size.width(),
pcfRadius,
clipNear,
clipFar,
castingObjectsBox,
receivingObjectsBox,
light->m_lockShadowmapTexels,
debugDrawSystem,
drawCascades,
drawSceneCascadeIntersection);
// Write the split distances from value 0 in the z-axis of the eye view-space
pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
pEntry->m_shadowMapFar = clipFar;
} else if (light->type == QSSGRenderLight::Type::SpotLight) {
auto spotlightCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderCamera::Type::PerspectiveCamera);
spotlightCamera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(light->m_coneAngle * 2.0f);
spotlightCamera->clipPlanes = { 1.0f, light->m_shadowMapFar };
const QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(*light);
const QVector3D lightDir = QSSGRenderNode::getDirection(lightGlobalTransform);
const QVector3D lightPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform) - lightDir * spotlightCamera->clipPlanes.clipNear();
const QVector3D lightPivot = light->pivot;
const QVector3D forward = lightDir.normalized();
const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
: QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
spotlightCamera->localTransform = QSSGRenderNode::calculateTransformMatrix(lightPos,
QSSGRenderNode::initScale,
lightPivot,
QQuaternion::fromDirection(forward, up));
QRectF theViewport(0.0f, 0.0f, (float)light->m_shadowMapRes, (float)light->m_shadowMapRes);
QSSGRenderCamera::calculateProjectionInternal(*spotlightCamera, theViewport);
cascades.push_back(std::move(spotlightCamera));
pEntry->m_shadowMapFar = light->m_shadowMapFar;
} else {
Q_UNREACHABLE();
}
memset(pEntry->m_csmActive, 0, sizeof(pEntry->m_csmActive));
QMatrix4x4 cascadeCameraGlobalTransforms(Qt::Uninitialized);
const QMatrix4x4 bias = { 0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0 };
for (int cascadeIndex = 0; cascadeIndex < cascades.length(); cascadeIndex++) {
const auto &cascadeCamera = cascades[cascadeIndex];
if (!cascadeCamera)
continue;
cascadeCameraGlobalTransforms = layerData.getGlobalTransform(*cascadeCamera);
pEntry->m_csmActive[cascadeIndex] = 1.f;
QMatrix4x4 &viewProjection = pEntry->m_lightViewProjection[cascadeIndex];
cascadeCamera->calculateViewProjectionMatrix(cascadeCameraGlobalTransforms, viewProjection);
pEntry->m_lightViewProjection[cascadeIndex] = viewProjection;
pEntry->m_fixedScaleBiasMatrix[cascadeIndex] = bias * viewProjection;
const QMatrix4x4 inverted = viewProjection.inverted();
const float x = 0.5f / (inverted * QVector4D(1, 0, 0, 0)).length();
const float y = 0.5f / (inverted * QVector4D(0, 1, 0, 0)).length();
const float z = 0.5f / (inverted * QVector4D(0, 0, 1, 0)).length();
const QSSGBoxPoints frustumPoints = computeFrustumBounds(viewProjection);
const QSSGBounds3 bounds = QSSGBounds3(frustumPoints);
pEntry->m_dimensionsInverted[cascadeIndex] = QVector4D(x, y, z, 0.0f);
pEntry->m_lightView = cascadeCameraGlobalTransforms.inverted(); // pre-calculate this for the material
const bool isOrtho = cascadeCamera->type == QSSGRenderGraphObject::Type::OrthographicCamera;
ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[cascadeIndex], rhi->isYUpInFramebuffer());
rhiPrepareResourcesForShadowMap(rhiCtx, layerData, passKey, pEntry, &ps, &depthAdjust, sortedOpaqueObjects, *cascadeCamera, isOrtho, QSSGRenderTextureCubeFaceNone, cascadeIndex);
// Render into the 2D texture pEntry->m_rhiDepthMap, using
// pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[cascadeIndex];
cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
rhiClearShadowMap(renderer, shadowMapManager, rhiCtx, &ps, rt->renderPassDescriptor());
rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0, bounds, debugIsObjectCulled, drawCulledObjects);
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
if (drawDirectionalLightShadowBoxes) {
ShadowmapHelpers::addDirectionalLightDebugBox(frustumPoints, debugDrawSystem);
}
}
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
} else { // Point Light
const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
const float shadowMapFar = qMax<float>(2.0f, light->m_shadowMapFar);
setupCubeShadowCameras(layerData, light, shadowMapFar, theCameras);
pEntry->m_lightView = QMatrix4x4();
pEntry->m_shadowMapFar = shadowMapFar;
const bool swapYFaces = !rhi->isYUpInFramebuffer();
QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
for (const auto face : QSSGRenderTextureCubeFaces) {
cameraGlobalTransform = layerData.getGlobalTransform(theCameras[quint8(face)]);
theCameras[quint8(face)].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_lightViewProjection[0]);
pEntry->m_lightCubeView[quint8(face)] = cameraGlobalTransform.inverted(); // pre-calculate this for the material
rhiPrepareResourcesForShadowMap(rhiCtx,
layerData,
passKey,
pEntry,
&ps,
&depthAdjust,
sortedOpaqueObjects,
theCameras[quint8(face)],
false,
face,
0);
}
// The bounds should be the same for all view projections of the cube
const QVector3D center = QSSGRenderNode::getGlobalPos(layerData.getGlobalTransform(*light));
const QSSGBounds3 bounds = QSSGBounds3(center - QVector3D(shadowMapFar, shadowMapFar, shadowMapFar),
center + QVector3D(shadowMapFar, shadowMapFar, shadowMapFar));
for (const auto face : QSSGRenderTextureCubeFaces) {
// Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
// pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
QSSGRenderTextureCubeFace outFace = face;
// FACE S T GL
// +x -z, -y right
// -x +z, -y left
// +y +x, +z top
// -y +x, -z bottom
// +z +x, -y front
// -z -x, -y back
// FACE S T D3D
// +x -z, +y right
// -x +z, +y left
// +y +x, -z bottom
// -y +x, +z top
// +z +x, +y front
// -z -x, +y back
if (swapYFaces) {
// +Y and -Y faces get swapped (D3D, Vulkan, Metal).
// See shadowMapping.glsllib. This is complemented there by reversing T as well.
if (outFace == QSSGRenderTextureCubeFace::PosY)
outFace = QSSGRenderTextureCubeFace::NegY;
else if (outFace == QSSGRenderTextureCubeFace::NegY)
outFace = QSSGRenderTextureCubeFace::PosY;
}
QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargetCube[quint8(outFace)];
cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face), bounds, debugIsObjectCulled, drawCulledObjects);
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
}
// Render the CubeMap into the shadowMapAtlasTexture
// NOTE: These passes don't require a manual clear because we will always write every pixel in that atlas space
// Render the front hemisphere of the cube map into the shadow map atlas
QRhiTextureRenderTarget *rtFront = pEntry->m_rhiRenderTargets[0]; // A layer in the Atlas
QRhiRenderPassDescriptor *frontDesc = pEntry->m_rhiRenderPassDesc[0];
auto atlasShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiCubeMapToAtlasShader();
ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[0], rhi->isYUpInFramebuffer());
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, atlasShaderPipeline.get());
QRhiShaderResourceBindings *srb = pEntry->m_cubeToAtlasFrontSrb;
cb->beginPass(rtFront, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtFront));
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, frontDesc, QSSGRhiQuadRenderer::UvCoords);
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 6, 0));
// Render the back hemisphere of the cube map into the shadow map atlas
QRhiTextureRenderTarget *rtBack = pEntry->m_rhiRenderTargets[1]; // A layer in the Atlas
QRhiRenderPassDescriptor *backDesc = pEntry->m_rhiRenderPassDesc[1];
srb = pEntry->m_cubeToAtlasBackSrb;
ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[1], rhi->isYUpInFramebuffer());
cb->beginPass(rtBack, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtBack));
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, backDesc, QSSGRhiQuadRenderer::UvCoords);
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 7, 0));
// reset pipeline (This part is necessary, but not ideal)
ps = layerData.getPipelineState();
ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
ps.depthBias = 2;
ps.slopeScaledDepthBias = 1.5f;
if (drawPointLightShadowBoxes) {
ShadowmapHelpers::addDebugBox(bounds.toQSSGBoxPoints(), QColorConstants::Yellow, debugDrawSystem);
}
}
}
if (Q_UNLIKELY(drawCulledObjects)) {
for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
QSSGRenderableObject *theObject = sortedOpaqueObjects[i].obj;
const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
: theObject->globalBounds;
const QColor color = debugIsObjectCulled[i] ? QColorConstants::Red : QColorConstants::Green;
ShadowmapHelpers::addDebugBox(globalBounds.toQSSGBoxPointsNoEmptyCheck(), color, debugDrawSystem);
}
}
}
void RenderHelpers::rhiRenderReflectionMap(const QSSGRenderContextInterface &context,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
QSSGRhiGraphicsPipelineState *ps,
QSSGRenderReflectionMap &reflectionMapManager,
const std::vector<QSSGRenderReflectionProbe *> &reflectionProbes,
const QSSGRenderableObjectList &reflectionPassObjects,
QSSGRenderer &renderer)
{
QSSGRhiContext *rhiCtx = context.rhiContext().get();
QRhi *rhi = rhiCtx->rhi();
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox
|| inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap
|| inData.layer.background == QSSGRenderLayer::Background::SkyMaterial)
&& rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch);
for (size_t i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(int(i));
if (!pEntry)
continue;
if (!pEntry->m_needsRender)
continue;
if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
continue;
if (reflectionProbes[i]->texture)
continue;
Q_ASSERT(pEntry->m_rhiDepthStencil);
Q_ASSERT(pEntry->m_rhiCube);
const QSize size = pEntry->m_rhiCube->pixelSize();
ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
setupCubeReflectionCameras(inData, reflectionProbes[i], theCameras);
const bool swapYFaces = !rhi->isYUpInFramebuffer();
QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
for (const auto face : QSSGRenderTextureCubeFaces) {
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
cameraGlobalTransform = inData.getGlobalTransform(theCameras[cubeFaceIdx]);
theCameras[cubeFaceIdx].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_viewProjection);
rhiPrepareResourcesForReflectionMap(context, passKey, inData, pEntry, ps, reflectionPassObjects, theCameras[cubeFaceIdx], renderer, face);
}
QRhiRenderPassDescriptor *renderPassDesc = nullptr;
for (auto face : QSSGRenderTextureCubeFaces) {
if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
face = pEntry->m_timeSliceFace;
QSSGRenderTextureCubeFace outFace = face;
// Faces are swapped similarly to shadow maps due to differences in backends
// Prefilter step handles correcting orientation differences in the final render
if (swapYFaces) {
if (outFace == QSSGRenderTextureCubeFace::PosY)
outFace = QSSGRenderTextureCubeFace::NegY;
else if (outFace == QSSGRenderTextureCubeFace::NegY)
outFace = QSSGRenderTextureCubeFace::PosY;
}
QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
cb->beginPass(rt, reflectionProbes[i]->clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
const auto &shaderCache = renderer.contextInterface()->shaderCache();
const bool useSkyBoxQuad = inData.layer.background == QSSGRenderLayer::Background::SkyBox
|| inData.layer.background == QSSGRenderLayer::Background::SkyMaterial;
const auto &shaderPipeline = useSkyBoxQuad
? shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(QSSGRenderLayer::TonemapMode::None,
inData.layer.skyBoxIsRgbe8,
1)
: shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(QSSGRenderLayer::TonemapMode::None,
!inData.layer.skyBoxIsSrgb,
1);
Q_ASSERT(shaderPipeline);
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
if (!renderPassDesc)
renderPassDesc = rt->newCompatibleRenderPassDescriptor();
rt->setRenderPassDescriptor(renderPassDesc);
useSkyBoxQuad ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, renderPassDesc, {})
: renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, renderPassDesc, {});
}
bool needsSetViewport = true;
for (const auto &handle : reflectionPassObjects)
rhiRenderRenderable(rhiCtx, *ps, *handle.obj, &needsSetViewport, face);
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
break;
}
if (renderPassDesc)
renderPassDesc->deleteLater();
pEntry->renderMips(rhiCtx);
if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(pEntry->m_timeSliceFace); // Wraps
if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
pEntry->m_rendered = true;
reflectionProbes[i]->hasScheduledUpdate = false;
pEntry->m_needsRender = false;
}
}
bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx,
const QSize &size,
QSSGRhiRenderableTexture *renderableTex,
quint8 viewCount)
{
QRhi *rhi = rhiCtx->rhi();
bool needsBuild = false;
if (!renderableTex->texture) {
QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
// the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
if (viewCount <= 1)
renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
else
renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
needsBuild = true;
} else if (renderableTex->texture->pixelSize() != size) {
renderableTex->texture->setPixelSize(size);
needsBuild = true;
}
if (needsBuild) {
if (!renderableTex->texture->create()) {
qWarning("Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
renderableTex->reset();
return false;
}
renderableTex->resetRenderTarget();
QRhiTextureRenderTargetDescription desc;
QRhiColorAttachment colorAttachment(renderableTex->texture);
colorAttachment.setMultiViewCount(viewCount);
desc.setColorAttachments({ colorAttachment });
renderableTex->rt = rhi->newTextureRenderTarget(desc);
renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
if (!renderableTex->rt->create()) {
qWarning("Failed to build render target for ambient occlusion texture");
renderableTex->reset();
return false;
}
}
return true;
}
void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
QSSGRenderer &renderer,
QSSGRhiShaderPipeline &shaderPipeline,
QSSGRhiGraphicsPipelineState &ps,
const QSSGAmbientOcclusionSettings &ao,
const QSSGRhiRenderableTexture &rhiAoTexture,
const QSSGRhiRenderableTexture &rhiDepthTexture,
const QSSGRenderCamera &camera)
{
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
// no texelFetch in GLSL <= 120 and GLSL ES 100
if (!rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
// just clear and stop there
cb->beginPass(rhiAoTexture.rt, Qt::white, { 1.0f, 0 });
QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
cb->endPass();
QSSGRHICTX_STAT(rhiCtx, endRenderPass());
return;
}
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, &shaderPipeline);
const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
const QSize textureSize = rhiAoTexture.texture->pixelSize();
const float rw = float(textureSize.width());
const float rh = float(textureSize.height());
const float fov = camera.fov.asVerticalFov(rw / rh).radians();
const float tanHalfFovY = tanf(0.5f * fov * (rh / rw));
const float invFocalLenX = tanHalfFovY * (rw / rh);
const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
const QVector2D cameraProps = camera.clipPlanes;
// layout(std140, binding = 0) uniform buf {
// vec4 aoProperties;
// vec4 aoProperties2;
// vec4 aoScreenConst;
// vec4 uvToEyeConst;
// vec2 cameraProperties;
const int UBUF_SIZE = 72;
QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, nullptr, 0 }));
if (!dcd.ubuf) {
dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
dcd.ubuf->create();
}
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
memcpy(ubufData, &aoProps, 16);
memcpy(ubufData + 16, &aoProps2, 16);
memcpy(ubufData + 32, &aoScreenConst, 16);
memcpy(ubufData + 48, &uvToEyeConst, 16);
memcpy(ubufData + 64, &cameraProps, 8);
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
// binding 1 is either a sampler2D or sampler2DArray, matching
// rhiDepthTexture.texture, no special casing needed here
bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiDepthTexture.texture, sampler);
QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &ps, srb, rhiAoTexture.rt, {});
}
bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx,
const QSize &size,
bool wantsMips,
QSSGRhiRenderableTexture *renderableTex,
quint8 viewCount)
{
QRhi *rhi = rhiCtx->rhi();
bool needsBuild = false;
QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
if (wantsMips)
flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
if (!renderableTex->texture) {
// always non-msaa, even if multisampling is used in the main pass
if (viewCount <= 1)
renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
else
renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
needsBuild = true;
} else if (renderableTex->texture->pixelSize() != size) {
renderableTex->texture->setPixelSize(size);
needsBuild = true;
}
if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
if (viewCount <= 1)
renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
else
renderableTex->depthTexture = rhi->newTextureArray(QRhiTexture::D24S8, viewCount, size, 1, QRhiTexture::RenderTarget);
needsBuild = true;
} else {
if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
renderableTex->depthStencil->setPixelSize(size);
needsBuild = true;
} else if (renderableTex->depthTexture && renderableTex->depthTexture->pixelSize() != size) {
renderableTex->depthTexture->setPixelSize(size);
needsBuild = true;
}
}
if (needsBuild) {
if (!renderableTex->texture->create()) {
qWarning("Failed to build screen texture (size %dx%d)", size.width(), size.height());
renderableTex->reset();
return false;
}
if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
qWarning("Failed to build depth-stencil buffer for screen texture (size %dx%d)",
size.width(), size.height());
renderableTex->reset();
return false;
} else if (renderableTex->depthTexture && !renderableTex->depthTexture->create()) {
qWarning("Failed to build depth-stencil texture array (multiview) for screen texture (size %dx%d)",
size.width(), size.height());
renderableTex->reset();
return false;
}
renderableTex->resetRenderTarget();
QRhiTextureRenderTargetDescription desc;
QRhiColorAttachment colorAttachment(renderableTex->texture);
colorAttachment.setMultiViewCount(viewCount);
desc.setColorAttachments({ colorAttachment });
if (renderableTex->depthStencil)
desc.setDepthStencilBuffer(renderableTex->depthStencil);
else if (renderableTex->depthTexture)
desc.setDepthTexture(renderableTex->depthTexture);
renderableTex->rt = rhi->newTextureRenderTarget(desc);
renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
if (!renderableTex->rt->create()) {
qWarning("Failed to build render target for screen texture");
renderableTex->reset();
return false;
}
}
return true;
}
void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
{
QSSG_ASSERT(layer.renderData, return);
const auto *renderData = layer.renderData;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
QSSGRhiShaderResourceBindingList bindings;
int uniformBinding = 0;
const int ubufSize = cameras.count() >= 2 ? 276 : 148;
QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, nullptr, 0 })); // Change to Grid?
QRhi *rhi = rhiCtx->rhi();
if (!dcd.ubuf) {
dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
dcd.ubuf->create();
}
// Param
const auto clipPlanes = cameras[0]->clipPlanes;
const float scale = layer.gridScale;
const quint32 gridFlags = layer.gridFlags;
const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
quint32 ubufOffset = 0;
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
QMatrix4x4 viewProj(Qt::Uninitialized);
for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
cameraGlobalTransform = renderData->getGlobalTransform(*cameras[viewIdx]);
cameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProj);
QMatrix4x4 invViewProj = viewProj.inverted();
quint32 viewDataOffset = ubufOffset;
memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProj.constData(), 64);
viewDataOffset += 64 * cameras.count();
memcpy(ubufData + viewDataOffset + viewIdx * 64, invViewProj.constData(), 64);
}
ubufOffset += (64 + 64) * cameras.count();
memcpy(ubufData + ubufOffset, &clipPlanes, 8);
ubufOffset += 8;
memcpy(ubufData + ubufOffset, &scale, 4);
ubufOffset += 4;
memcpy(ubufData + ubufOffset, &yFactor, 4);
ubufOffset += 4;
memcpy(ubufData + ubufOffset, &gridFlags, 4);
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
bindings.addUniformBuffer(uniformBinding, RENDERER_VISIBILITY_ALL, dcd.ubuf);
layer.gridSrb = rhiCtxD->srb(bindings);
renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
cb->debugMarkEnd();
}
static void rhiPrepareSkyBox_helper(const QSSGRenderContextInterface &context,
QSSGPassKey passKey,
QSSGRenderLayer &layer,
QSSGRenderCameraList &cameras,
QSSGRenderer &renderer,
QSSGReflectionMapEntry *entry = nullptr,
QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone,
uint tonemapMode = 0)
{
QSSG_ASSERT(layer.renderData, return);
const auto *renderData = layer.renderData;
auto rhiCtx = context.rhiContext().get();
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
QSSGRenderImageTexture lightProbeTexture;
switch (layer.background) {
case QSSGRenderLayer::Background::SkyBox:
lightProbeTexture = renderer.contextInterface()->bufferManager()->loadRenderImage(layer.lightProbe,
QSSGBufferManager::MipModeBsdf);
break;
case QSSGRenderLayer::Background::SkyBoxCubeMap:
lightProbeTexture = renderer.contextInterface()->bufferManager()->loadRenderImage(layer.skyBoxCubeMap,
QSSGBufferManager::MipModeDisable);
break;
case QSSGRenderLayer::Background::SkyMaterial:
lightProbeTexture = renderData->skyMaterialTexture;
break;
default:
Q_UNREACHABLE_RETURN();
}
if (lightProbeTexture.m_texture == nullptr) {
// No valid texture this frame; drop any previously built SRB rather than
// reuse one that may reference a texture ensureTextures() has since deleted.
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
// All 6 face SRBs bind the same light probe texture, so all of them (not
// just the face passed in this call) are equally stale.
for (QRhiShaderResourceBindings *&srb : entry->m_skyBoxSrbs)
srb = nullptr;
} else {
layer.skyBoxSrb = nullptr;
}
return;
}
const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
if (cubeFace == QSSGRenderTextureCubeFaceNone)
layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
if (cubeMapMode)
layer.skyBoxIsSrgb = !lightProbeTexture.m_flags.isLinear();
QSSGRhiShaderResourceBindingList bindings;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
QRhiSampler::Repeat,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
int samplerBinding = 1; // the shader code is hand-written, so we don't need to look that up
bindings.addTexture(samplerBinding, QRhiShaderResourceBinding::FragmentStage, lightProbeTexture.m_texture, sampler);
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, nullptr, nullptr, entry, entryIdx });
QRhi *rhi = rhiCtx->rhi();
const quint32 ubufSize = cameras.count() >= 2 ? 416 : 240; // same ubuf layout for both skybox and skyboxcube
if (!dcd.ubuf || dcd.ubuf->size() != ubufSize) {
delete dcd.ubuf;
dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, int(ubufSize));
dcd.ubuf->create();
}
float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
const float exposure = layer.lightProbeSettings.probeExposure;
// orientation
const QMatrix3x3 &rotationMatrix(layer.lightProbeSettings.probeOrientation);
// The cubemap shader doesn't use blur or mipmapping, so it uses those for tonemapping and texture color space
const float blurAmountOrSrgb = cubeMapMode ? layer.skyBoxIsSrgb : layer.skyboxBlurAmount;
const float maxMipLevelOrTonemapMode = cubeMapMode ? float(tonemapMode) : float(lightProbeTexture.m_mipmapCount - 2);
const QVector4D skyboxProperties = {
adjustY,
exposure,
blurAmountOrSrgb,
maxMipLevelOrTonemapMode
};
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
quint32 ubufOffset = 0;
// skyboxProperties
memcpy(ubufData + ubufOffset, &skyboxProperties, 16);
ubufOffset += 16;
// orientation
memcpy(ubufData + ubufOffset, rotationMatrix.constData(), 12);
ubufOffset += 16;
memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 12, 12);
ubufOffset += 16;
memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 24, 12);
ubufOffset += 16;
for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
const QMatrix4x4 &inverseProjection = cameras[viewIdx]->projection.inverted();
const QMatrix4x4 &viewMatrix = renderData->getGlobalTransform(*cameras[viewIdx]);
QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
cameras[viewIdx]->calculateViewProjectionWithoutTranslation(viewMatrix, 0.1f, 5.0f, viewProjection);
quint32 viewDataOffset = ubufOffset;
memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProjection.constData(), 64);
viewDataOffset += cameras.count() * 64;
memcpy(ubufData + viewDataOffset + viewIdx * 64, inverseProjection.constData(), 64);
viewDataOffset += cameras.count() * 64;
memcpy(ubufData + viewDataOffset + viewIdx * 48, viewMatrix.constData(), 48);
}
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtxD->srb(bindings);
} else {
layer.skyBoxSrb = rhiCtxD->srb(bindings);
}
if (cubeMapMode)
renderer.rhiCubeRenderer()->prepareCube(rhiCtx, nullptr);
else
renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
}
void RenderHelpers::rhiPrepareSkyBox(const QSSGRenderContextInterface &context,
QSSGPassKey passKey,
QSSGRenderLayer &layer,
QSSGRenderCameraList &cameras,
QSSGRenderer &renderer,
uint tonemapMode)
{
auto rhiCtx = context.rhiContext().get();
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
rhiPrepareSkyBox_helper(context, passKey, layer, cameras, renderer, nullptr, QSSGRenderTextureCubeFaceNone, tonemapMode);
cb->debugMarkEnd();
}
void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(const QSSGRenderContextInterface &context,
QSSGPassKey passKey,
QSSGRenderLayer &layer,
QSSGRenderCamera &inCamera,
QSSGRenderer &renderer,
QSSGReflectionMapEntry *entry,
QSSGRenderTextureCubeFace cubeFace)
{
QSSGRhiContext *rhiCtx = context.rhiContext().get();
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
QSSGRenderCameraList cameras({ &inCamera });
rhiPrepareSkyBox_helper(context, passKey, layer, cameras, renderer, entry, cubeFace);
cb->debugMarkEnd();
}
bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGRhiGraphicsPipelineState &basePipelineState,
QRhiRenderPassDescriptor *rpDesc,
QSSGLayerRenderData &inData,
const QSSGRenderableObjectList &sortedOpaqueObjects,
const QSSGRenderableObjectList &sortedTransparentObjects,
int samples,
int viewCount)
{
static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
QSSGLayerRenderData &inData,
QSSGRenderableObject *obj,
QRhiRenderPassDescriptor *rpDesc,
QSSGRhiGraphicsPipelineState *ps) {
QSSGRhiShaderPipelinePtr shaderPipeline;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
QSSGShaderFeatures featureSet;
featureSet.set(QSSGShaderFeatures::Feature::DepthPass, true);
if (isOpaqueDepthPrePass)
featureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
QSSGRhiDrawCallData *dcd = nullptr;
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
const void *modelNode = &subsetRenderable.modelContext.model;
dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, nullptr, 0 });
}
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
} else {
return false;
}
} else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
ps->cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, customMaterial, subsetRenderable,
inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
} else {
return false;
}
}
// the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// Depth and SSAO textures, in case a custom material's shader code does something with them.
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
if (isOpaqueDepthPrePass) {
addOpaqueDepthPrePassBindings(rhiCtx,
shaderPipeline.get(),
subsetRenderable.firstImage,
bindings,
(obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
}
// There is no normal texture at this stage. But the shader from a
// custom material may rely on it. Bind a dummy texture then due to
// the lack of other options.
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
}
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtxD->pipeline(*ps,
rpDesc,
srb);
subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
}
return true;
};
// Phase 1 (prepare) for the Z prepass or the depth texture generation.
// These renders opaque (Z prepass), or opaque and transparent (depth
// texture), objects with depth test/write enabled, and color write
// disabled, using a very simple set of shaders.
QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
// We took a copy of the pipeline state since we do not want to conflict
// with what rhiPrepare() collects for its own use. So here just change
// whatever we need.
ps.samples = samples;
ps.viewCount = viewCount;
ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
ps.targetBlend[0].colorWrite = {};
for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
return false;
}
for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
return false;
}
return true;
}
void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
const QSSGRhiGraphicsPipelineState &pipelineState,
const QSSGRenderableObjectList &sortedOpaqueObjects,
const QSSGRenderableObjectList &sortedTransparentObjects,
bool *needsSetViewport)
{
static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
const QSSGRhiGraphicsPipelineState &pipelineState,
const QSSGRenderableObjectList &objects,
bool *needsSetViewport) {
for (const auto &oh : objects) {
QSSGRenderableObject *obj = oh.obj;
// casts to SubsetRenderableBase so it works for both default and custom materials
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
? subsetRenderable->subset.rhi.indexBuffer->buffer()
: nullptr;
QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
if (!ps)
return;
QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
if (!srb)
return;
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
cb->setGraphicsPipeline(ps);
cb->setShaderResources(srb);
if (*needsSetViewport) {
cb->setViewport(pipelineState.viewport);
*needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if (subsetRenderable->modelContext.model.instancing()) {
instances = subsetRenderable->modelContext.model.instanceCount();
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
vertexBufferCount = 2;
}
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
QVector<int>({subsetRenderable->modelContext.model.profilingId,
subsetRenderable->material.profilingId}));
}
}
};
rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
}
bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx,
const QSize &size,
QSSGRhiRenderableTexture *renderableTex,
quint8 viewCount,
int samples)
{
QRhi *rhi = rhiCtx->rhi();
bool needsBuild = false;
if (!renderableTex->texture) {
QRhiTexture::Format format = QRhiTexture::D32F;
if (!rhi->isTextureFormatSupported(format))
format = QRhiTexture::D16;
if (!rhi->isTextureFormatSupported(format))
qWarning("Depth texture not supported");
if (viewCount <= 1)
renderableTex->texture = rhiCtx->rhi()->newTexture(format, size, samples, QRhiTexture::RenderTarget);
else
renderableTex->texture = rhiCtx->rhi()->newTextureArray(format, viewCount, size, 1, QRhiTexture::RenderTarget);
needsBuild = true;
} else if (renderableTex->texture->pixelSize() != size) {
renderableTex->texture->setPixelSize(size);
needsBuild = true;
}
if (needsBuild) {
if (!renderableTex->texture->create()) {
qWarning("Failed to build depth texture (size %dx%d, format %d)",
size.width(), size.height(), int(renderableTex->texture->format()));
renderableTex->reset();
return false;
}
renderableTex->resetRenderTarget();
QRhiTextureRenderTargetDescription rtDesc;
rtDesc.setDepthTexture(renderableTex->texture);
renderableTex->rt = rhi->newTextureRenderTarget(rtDesc);
renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
if (!renderableTex->rt->create()) {
qWarning("Failed to build render target for depth texture");
renderableTex->reset();
return false;
}
}
return true;
}
bool RenderHelpers::rhiPrepareNormalPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGRhiGraphicsPipelineState &basePipelineState,
QRhiRenderPassDescriptor *rpDesc,
QSSGLayerRenderData &inData,
const QSSGRenderableObjectList &sortedOpaqueObjects)
{
QSSGRhiGraphicsPipelineState ps = basePipelineState;
ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
QSSGRenderableObject *obj = handle.obj;
QSSGRhiShaderPipelinePtr shaderPipeline;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
QSSGShaderFeatures featureSet;
featureSet.set(QSSGShaderFeatures::Feature::NormalPass, true);
QSSGRhiDrawCallData *dcd = nullptr;
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const void *modelNode = &subsetRenderable.modelContext.model;
dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, nullptr, 0 });
}
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
} else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
ps.cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
const auto &customMaterialSystem = subsetRenderable.renderer->contextInterface()->customMaterialSystem();
shaderPipeline = customMaterialSystem->shadersForCustomMaterial(&ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
customMaterialSystem->updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, customMaterial, subsetRenderable,
inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
}
// the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// Texture maps
QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
} // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
renderableImage = renderableImage->m_nextImage;
}
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
// There is no normal texture at this stage, obviously.
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
}
// Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
subsetRenderable.rhiRenderData.normalPass.pipeline = rhiCtxD->pipeline(ps,
rpDesc,
srb);
subsetRenderable.rhiRenderData.normalPass.srb = srb;
}
}
return true;
}
void RenderHelpers::rhiRenderNormalPass(QSSGRhiContext *rhiCtx,
const QSSGRhiGraphicsPipelineState &pipelineState,
const QSSGRenderableObjectList &sortedOpaqueObjects,
bool *needsSetViewport)
{
for (const auto &oh : sortedOpaqueObjects) {
QSSGRenderableObject *obj = oh.obj;
// casts to SubsetRenderableBase so it works for both default and custom materials
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
? subsetRenderable->subset.rhi.indexBuffer->buffer()
: nullptr;
QRhiGraphicsPipeline *graphicsPipeline = subsetRenderable->rhiRenderData.normalPass.pipeline;
if (!graphicsPipeline)
return;
QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.normalPass.srb;
if (!srb)
return;
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
cb->setGraphicsPipeline(graphicsPipeline);
cb->setShaderResources(srb);
if (needsSetViewport) {
cb->setViewport(pipelineState.viewport);
*needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if (subsetRenderable->modelContext.model.instancing()) {
instances = subsetRenderable->modelContext.model.instanceCount();
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
vertexBufferCount = 2;
}
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
QVector<int>({subsetRenderable->modelContext.model.profilingId,
subsetRenderable->material.profilingId}));
}
}
}
qsizetype RenderHelpers::rhiPrepareOverrideMaterialUserPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGRhiGraphicsPipelineState &basePipelineState,
QRhiRenderPassDescriptor *rpDesc,
QSSGRenderGraphObject *overrideMaterial,
QSSGLayerRenderData &inData,
QSSGRenderableObjectList &inObjects,
QSSGShaderFeatures featureSet)
{
// Make sure we have a valid override material before acquireing a user pass slot!
const qsizetype index = overrideMaterial ? inData.getUserRenderPassManager()->acquireUserPassSlot() : -1;
// If the index is negative, it means there are no more available slots for user passes, so we can skip the preparation and rendering of this pass.
if (index < 0)
return index;
QSSGRhiGraphicsPipelineState ps = basePipelineState;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
const bool isCustomMaterial = (overrideMaterial->type == QSSGRenderGraphObject::Type::CustomMaterial);
const bool isDefaultMaterial = (overrideMaterial->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
overrideMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
overrideMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
if (!isCustomMaterial && !isDefaultMaterial) {
qDebug() << "Override material must be a default or custom material.";
return -1;
}
for (const QSSGRenderableObjectHandle &handle : std::as_const(inObjects)) {
QSSGRenderableObject *obj = handle.obj;
if (obj->type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
obj->type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
continue;
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const void *modelNode = &subsetRenderable.modelContext.model;
// All user passes share the pass key, so the slot index tells them apart.
QSSGRhiDrawCallData *dcd = &rhiCtxD->drawCallData({ passKey, modelNode, overrideMaterial, nullptr,
quintptr(index) });
// Update the shader key to reflect the override material's properties
// The subsetRenderable.shaderDescription was set during prepareModelsForRender for the original material,
// but we need it to reflect the override material when paired with this model/subset.
{
QSSGRenderableObjectFlags renderableFlags = subsetRenderable.renderableFlags;
float opacity = subsetRenderable.opacity;
// Determine lighting conditions from the subset's lights
const bool hasAnyLights = !subsetRenderable.lights.isEmpty();
const bool anyLightHasShadows = std::any_of(subsetRenderable.lights.begin(),
subsetRenderable.lights.end(),
[](const QSSGShaderLight &light) { return light.shadows; });
// We need a non-const reference to layerPrepResult flags
// Make a copy since the prepare functions might modify it
auto layerPrepFlags = inData.layerPrepResult.getFlags();
if (isCustomMaterial) {
auto &material = static_cast<QSSGRenderCustomMaterial &>(*overrideMaterial);
auto prepResult = inData.prepareCustomMaterialForRender(material, renderableFlags, opacity,
false, hasAnyLights, anyLightHasShadows,
layerPrepFlags);
subsetRenderable.shaderDescription = prepResult.materialKey;
} else {
auto &material = static_cast<QSSGRenderDefaultMaterial &>(*overrideMaterial);
auto prepResult = inData.prepareDefaultMaterialForRender(material, renderableFlags, opacity,
hasAnyLights, anyLightHasShadows,
layerPrepFlags);
subsetRenderable.shaderDescription = prepResult.materialKey;
}
}
QSSGRhiShaderPipelinePtr shaderPipeline;
if (isCustomMaterial) {
const auto &material = static_cast<const QSSGRenderCustomMaterial &>(*overrideMaterial);
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable,
inData.getDefaultMaterialPropertyTable(), featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
} else {
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(*overrideMaterial);
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable,
inData.renderedCameras, nullptr, nullptr, overrideMaterial);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
}
if (!shaderPipeline)
continue;
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// Texture maps from the renderable
QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
}
renderableImage = renderableImage->m_nextImage;
}
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
// Dummy normal texture
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
}
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
// Lighting
if (shaderPipeline->isLightingEnabled()) {
bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0LightDataOffset(),
sizeof(QSSGShaderLightsUniformData));
bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0DirectionalLightDataOffset(),
sizeof(QSSGShaderDirectionalLightsUniformData));
// Shadow map atlas
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture"); binding >= 0) {
QRhiTexture *texture = shaderPipeline->shadowMapAtlasTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture({ }, resourceUpdates, QSize(1, 1), Qt::black, 2);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Shadow map blue noise
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
QRhiTexture *texture = shaderPipeline->shadowMapBlueNoiseTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture({ }, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
QRhiSampler *sampler = rhiCtx->sampler(
{ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Light probe
if (int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
binding >= 0) {
auto texture = shaderPipeline->lightProbeTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
Q_ASSERT(texture);
QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::Linear, // enables mipmapping
QSSGRhiHelpers::toRhi(tiling.first),
QSSGRhiHelpers::toRhi(tiling.second),
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
QSSG_ASSERT(srb, continue);
auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
rhiPassData.pipeline = rhiCtxD->pipeline(ps, rpDesc, srb);
rhiPassData.srb = srb;
}
return index;
}
qsizetype RenderHelpers::rhiPrepareOriginalMaterialUserPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGRhiGraphicsPipelineState &basePipelineState,
QRhiRenderPassDescriptor *rpDesc,
const QSSGLayerRenderData &inData,
QSSGRenderableObjectList &inObjects,
QSSGShaderFeatures featureSet)
{
const qsizetype index = inData.getUserRenderPassManager()->acquireUserPassSlot();
// If the index is negative, it means there are no more available slots for user passes, so we can't prepare the original material pass.
if (index < 0)
return index;
QSSGRhiGraphicsPipelineState ps = basePipelineState;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
for (const QSSGRenderableObjectHandle &handle : std::as_const(inObjects)) {
QSSGRenderableObject *obj = handle.obj;
QSSGRhiShaderPipelinePtr shaderPipeline;
QSSGRhiDrawCallData *dcd = nullptr;
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const void *modelNode = &subsetRenderable.modelContext.model;
// All user passes share the pass key, so the slot index tells them apart.
dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, nullptr, quintptr(index) });
}
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable,
inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
} else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable,
inData.getDefaultMaterialPropertyTable(), featureSet);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
}
if (!shaderPipeline)
continue;
// The rest is common for both default and custom materials
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// Texture maps
QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
}
renderableImage = renderableImage->m_nextImage;
}
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
// Dummy normal texture
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
}
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
// Lighting
if (shaderPipeline->isLightingEnabled()) {
bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0LightDataOffset(),
sizeof(QSSGShaderLightsUniformData));
bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0DirectionalLightDataOffset(),
sizeof(QSSGShaderDirectionalLightsUniformData));
// Shadow map atlas
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture"); binding >= 0) {
QRhiTexture *texture = shaderPipeline->shadowMapAtlasTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture({ }, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Shadow map blue noise
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
if (binding >= 0) {
QRhiTexture *texture = shadowMapBlueNoise;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
} else {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
}
// Reflection probe or light probe
if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
const auto mipMapFilter = reflectionTexture && reflectionTexture->flags().testFlag(QRhiTexture::Flag::MipMapped)
? QRhiSampler::Linear
: QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
mipMapFilter,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
if (reflectionSampler >= 0 && reflectionTexture)
bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
}
if (int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
binding >= 0) {
auto texture = shaderPipeline->lightProbeTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
Q_ASSERT(texture);
QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::Linear, // enables mipmapping
QSSGRhiHelpers::toRhi(tiling.first),
QSSGRhiHelpers::toRhi(tiling.second),
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Screen Texture
if (shaderPipeline->screenTexture()) {
const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
? QRhiSampler::Linear : QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
if (screenTextureBinding >= 0) {
bindings.addTexture(screenTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->screenTexture(), sampler);
}
if (screenTextureArrayBinding >= 0) {
bindings.addTexture(screenTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->screenTexture(), sampler);
}
}
}
if (shaderPipeline->lightmapTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
if (binding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
bindings.addTexture(binding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->lightmapTexture(), sampler);
}
}
}
// Custom property textures
{
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
int maxSamplerBinding = -1;
QVector<QShaderDescription::InOutVariable> samplerVars =
shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
const auto combinedSamplers = shaderPipeline->vertexStage()->shader().description().combinedImageSamplers();
for (const QShaderDescription::InOutVariable &var : combinedSamplers) {
auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
[&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
if (it == samplerVars.cend())
samplerVars.append(var);
}
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
if (maxSamplerBinding >= 0) {
int extraTexCount = shaderPipeline->extraTextureCount();
for (int i = 0; i < extraTexCount; ++i) {
QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
if (samplerBinding >= 0) {
rhiCtx->checkAndAdjustForNPoT(t.texture, &t.samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
bindings.addTexture(samplerBinding,
QRhiShaderResourceBinding::FragmentStage,
t.texture,
sampler);
}
}
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
QSSG_ASSERT(srb, continue);
auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
rhiPassData.pipeline = rhiCtxD->pipeline(ps, rpDesc, srb);
rhiPassData.srb = srb;
}
}
return index;
}
qsizetype RenderHelpers::rhiPrepareAugmentedUserPass(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGRhiGraphicsPipelineState &basePipelineState,
QRhiRenderPassDescriptor *rpDesc,
const QSSGUserShaderAugmentation &shaderAugmentation,
const QSSGLayerRenderData &inData,
QSSGRenderableObjectList &inObjects,
QSSGShaderFeatures featureSet)
{
const qsizetype index = inData.getUserRenderPassManager()->acquireUserPassSlot();
// If there's no available slot, we can't prepare this pass. This can happen if the user shader augmentation tries to use more slots than the maximum allowed.
if (index < 0)
return index;
QSSGRhiGraphicsPipelineState ps = basePipelineState;
for (const QSSGRenderableObjectHandle &handle : std::as_const(inObjects)) {
QSSGRenderableObject *obj = handle.obj;
QSSGRhiShaderPipelinePtr shaderPipeline;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
const auto &bufferManager = inData.contextInterface()->bufferManager();
featureSet.set(QSSGShaderFeatures::Feature::UserRenderPass, true);
QSSGRhiDrawCallData *dcd = nullptr;
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const void *modelNode = &subsetRenderable.modelContext.model;
// All user passes share the pass key, so the slot index tells them apart.
dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, nullptr, quintptr(index) });
}
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet, shaderAugmentation);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
// Now do user augmentation specific uniform updates
for (const auto &u : shaderAugmentation.propertyUniforms)
shaderPipeline->setShaderResources(ubufData, *bufferManager, u.name, u.value, u.shaderDataType);
updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
} else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
if (!ps.userSetCullMode)
ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
// Don't apply a shader augmentation to an unshaded custom material (they should do their own augmentations with preprocessor conditionals)
if (material.m_shadingMode == QSSGRenderCustomMaterial::ShadingMode::Unshaded)
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
else
shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet, shaderAugmentation);
if (shaderPipeline) {
shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
// Apply user augmentation specific uniform updates
for (const auto &u : shaderAugmentation.propertyUniforms)
shaderPipeline->setShaderResources(ubufData, *bufferManager, u.name, u.value, u.shaderDataType);
customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
subsetRenderable, inData.renderedCameras, nullptr, nullptr);
dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
}
}
if (!shaderPipeline) {
// bail
qDebug() << "Failed to prepare user augmented pass for object.";
return -1;
}
// the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiShaderResourceBindingList bindings;
bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
// The generated shader declares a sampler for every custom
// property texture, and some graphics APIs reject an srb that
// leaves one of them unbound. Track which sampler bindings get
// taken as they are added, so the unused ones can be filled with
// dummy textures at the end.
QVector<QShaderDescription::InOutVariable> samplerVars =
shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
const auto combinedSamplers = shaderPipeline->vertexStage()->shader().description().combinedImageSamplers();
for (const QShaderDescription::InOutVariable &var : combinedSamplers) {
auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
[&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
if (it == samplerVars.cend())
samplerVars.append(var);
}
int maxSamplerBinding = -1;
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
QBitArray specifiedBindings(maxSamplerBinding + 1);
const auto addTrackedTexture = [&](int binding, QRhiShaderResourceBinding::StageFlags stage,
QRhiTexture *texture, QRhiSampler *sampler) {
specifiedBindings.setBit(binding);
bindings.addTexture(binding, stage, texture, sampler);
};
// Texture maps
QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
addTrackedTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
}
} // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
renderableImage = renderableImage->m_nextImage;
}
addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings, &specifiedBindings);
// There is no normal texture at this stage, obviously.
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
addTrackedTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
}
// Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
// Skinning
if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
addTrackedTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
}
}
// Morphing
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
addTrackedTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
}
}
// Lighting
if (shaderPipeline->isLightingEnabled()) {
bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0LightDataOffset(),
sizeof(QSSGShaderLightsUniformData));
bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
shaderPipeline->ub0DirectionalLightDataOffset(),
sizeof(QSSGShaderDirectionalLightsUniformData));
// Shadow map atlas
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture"); binding >= 0) {
QRhiTexture *texture = shaderPipeline->shadowMapAtlasTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture({ }, resourceUpdates, QSize(1, 1), Qt::black, 2);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
addTrackedTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Shadow map blue noise
if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
if (binding >= 0) {
QRhiTexture *texture = shadowMapBlueNoise;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
addTrackedTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
} else {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::None,
QRhiSampler::Repeat,
QRhiSampler::Repeat,
QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
addTrackedTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
}
// Prioritize reflection texture over Light Probe texture because
// reflection texture also contains the irradiance and pre filtered
// values for the light probe.
if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
const auto mipMapFilter = reflectionTexture && reflectionTexture->flags().testFlag(QRhiTexture::Flag::MipMapped)
? QRhiSampler::Linear
: QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
mipMapFilter,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat });
if (reflectionSampler >= 0 && reflectionTexture)
addTrackedTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
}
if (int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
binding >= 0) {
auto texture = shaderPipeline->lightProbeTexture();
if (!texture) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
texture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
Q_ASSERT(texture);
QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::Linear, // enables mipmapping
QSSGRhiHelpers::toRhi(tiling.first),
QSSGRhiHelpers::toRhi(tiling.second),
QRhiSampler::Repeat });
addTrackedTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
// Screen Texture
if (shaderPipeline->screenTexture()) {
const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
// linear min/mag, mipmap filtering depends on the
// texture, with SCREEN_TEXTURE there are no mipmaps, but
// once SCREEN_MIP_TEXTURE is seen the texture (the same
// one) has mipmaps generated.
QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
? QRhiSampler::Linear : QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
if (screenTextureBinding >= 0) {
addTrackedTexture(screenTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->screenTexture(), sampler);
}
if (screenTextureArrayBinding >= 0) {
addTrackedTexture(screenTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->screenTexture(), sampler);
}
} // else ignore, not an error
}
if (shaderPipeline->lightmapTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
if (binding >= 0) {
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
addTrackedTexture(binding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->lightmapTexture(), sampler);
} // else ignore, not an error
}
}
{
if (maxSamplerBinding >= 0) {
// custom property textures
int extraTexCount = shaderPipeline->extraTextureCount();
for (int i = 0; i < extraTexCount; ++i) {
QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
if (samplerBinding >= 0) {
rhiCtx->checkAndAdjustForNPoT(t.texture, &t.samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
addTrackedTexture(samplerBinding,
QRhiShaderResourceBinding::FragmentStage,
t.texture,
sampler);
}
}
}
// Use a dummy texture for the samplers left unbound above.
QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiTexture *dummyTextureArray = rhiCtx->dummyTexture({}, resourceUpdates, QSize(64, 64), Qt::black, qMax(2u, inData.layer.viewCount));
QRhiTexture *dummyTexture3D = rhiCtx->dummyTexture(QRhiTexture::ThreeDimensional, resourceUpdates);
QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
QRhiTexture *dummyCubeTextureArray = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates, QSize(64, 64), Qt::black, qMax(2u, inData.layer.viewCount));
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars)) {
if (!specifiedBindings.testBit(var.binding)) {
QRhiTexture *t = nullptr;
if (var.type == QShaderDescription::SamplerCube)
t = dummyCubeTexture;
else if (var.type == QShaderDescription::Sampler3D)
t = dummyTexture3D;
else if (var.type == QShaderDescription::Sampler2DArray)
t = dummyTextureArray;
else if (var.type == QShaderDescription::SamplerCubeArray)
t = dummyCubeTextureArray;
else
t = dummyTexture;
bindings.addTexture(var.binding, RENDERER_VISIBILITY_ALL, t, dummySampler);
}
}
}
QRhiShaderResourceBindings *srb = drawCallDataSrb(rhiCtxD, *dcd, bindings);
QSSG_ASSERT(srb, return -1);
auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
rhiPassData.pipeline = rhiCtxD->pipeline(ps, rpDesc, srb);
rhiPassData.srb = srb;
}
}
return index;
}
bool RenderHelpers::rhiPrepareMotionVectorTexture(QSSGRhiContext *rhiCtx,
const QSize &size,
QSSGRhiRenderableTexture *renderableTex){
QRhi *rhi = rhiCtx->rhi();
bool needsBuild = false;
QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
if (!renderableTex->texture) {
renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA16F, size, 1, flags);
needsBuild = true;
} else if (renderableTex->texture->pixelSize() != size) {
renderableTex->texture->setPixelSize(size);
needsBuild = true;
}
if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
needsBuild = true;
} else {
if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
renderableTex->depthStencil->setPixelSize(size);
needsBuild = true;
}
}
if (needsBuild) {
if (!renderableTex->texture->create()) {
qWarning("Failed to build motionvector texture (size %dx%d)", size.width(), size.height());
renderableTex->reset();
return false;
}
if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
qWarning("Failed to build depth-stencil buffer for motionvector texture (size %dx%d)",
size.width(), size.height());
renderableTex->reset();
return false;
}
renderableTex->resetRenderTarget();
QRhiTextureRenderTargetDescription desc;
QRhiColorAttachment colorAttachment(renderableTex->texture);
desc.setColorAttachments({ colorAttachment });
if (renderableTex->depthStencil)
desc.setDepthStencilBuffer(renderableTex->depthStencil);
renderableTex->rt = rhi->newTextureRenderTarget(desc);
renderableTex->rt->setName(QByteArrayLiteral("Motionvector texture"));
renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
if (!renderableTex->rt->create()) {
qWarning("Failed to build render target for motionvector texture");
renderableTex->reset();
return false;
}
}
return true;
}
void RenderHelpers::rhiPrepareMotionVectorRenderable(QSSGRhiContext *rhiCtx,
QSSGPassKey passKey,
const QSSGLayerRenderData &inData,
const QMatrix4x4 &viewProjection,
QSSGRenderableObject &inObject,
QRhiRenderPassDescriptor *renderPassDescriptor,
QSSGRhiGraphicsPipelineState *ps,
QSSGRenderMotionVectorMap &motionVectorMapManager)
{
if (inObject.type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
inObject.type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
return;
QSSGSubsetRenderable &subsetRenderable = static_cast<QSSGSubsetRenderable &>(inObject);
auto &modelNode = subsetRenderable.modelContext.model;
bool skin = modelNode.usesBoneTexture();
bool instance = modelNode.instanceCount() > 0;
bool morph = modelNode.morphTargets.size() > 0;
QSSGRhiShaderPipelinePtr shaderPipeline = inData.contextInterface()->shaderCache()->
getBuiltInRhiShaders().getRhiMotionVectorShader(skin, instance, morph);
if (!shaderPipeline)
return;
QSSGRhiShaderResourceBindingList bindings;
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, &modelNode, nullptr, nullptr, 0 });
QSSGRenderTextureData *boneTextureData = nullptr;
if (modelNode.skin)
boneTextureData = modelNode.skin;
else if (modelNode.skeleton)
boneTextureData = &modelNode.skeleton->boneTexData;
QMatrix4x4 modelViewProjection = subsetRenderable.modelContext.modelViewProjections[0];
QMatrix4x4 instanceLocal;
QMatrix4x4 instanceGlobal;
if (instance) {
instanceLocal = inData.getInstanceTransforms(modelNode).local;
instanceGlobal = inData.getInstanceTransforms(modelNode).global;
if (!skin)
modelViewProjection = viewProjection;
}
auto motionData = motionVectorMapManager.trackMotionData(&modelNode,
modelViewProjection,
instanceLocal,
instanceGlobal,
boneTextureData,
modelNode.instanceTable,
modelNode.morphWeights);
float velocityAmount = subsetRenderable.modelContext.model.motionVectorScale;
int morphTargetCount = modelNode.morphWeights.count();
// mat4 mvp; // 64
// mat4 prevMVP; // 64
// mat4 instanceLocal; // 64
// mat4 instanceGlobal; // 64
// mat4 prevInstanceLocal; // 64
// mat4 prevInstanceGlobal; // 64
// vec4 currentAndLastJitter; // 16
// float velocityAmount; // 4
// int morphTargetCount; // 4
// vec2 padding; // 8
const int ubufSize = 416;
if (!dcd.ubuf) {
dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic,
QRhiBuffer::UniformBuffer,
ubufSize);
dcd.ubuf->create();
}
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
int ubufOffset = 0;
memcpy(ubufData, modelViewProjection.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, motionData.prevModelViewProjection.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, instanceLocal.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, instanceGlobal.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, motionData.prevInstanceLocal.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, motionData.prevInstanceGlobal.constData(), 64);
ubufOffset += 64;
memcpy(ubufData + ubufOffset, &inData.layer.currentAndLastJitter, 16);
ubufOffset += 16;
memcpy(ubufData + ubufOffset, &velocityAmount, 4);
ubufOffset += 4;
memcpy(ubufData + ubufOffset, &morphTargetCount, 4);
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage |
QRhiShaderResourceBinding::FragmentStage, dcd.ubuf);
QRhiSampler *nearestSampler = nullptr;
if (skin || instance) {
nearestSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
}
if (skin) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
inData.getBonemapTexture(subsetRenderable.modelContext),
nearestSampler);
binding = shaderPipeline->bindingForTexture("lastBoneTexture");
if (binding >= 0) {
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
motionData.prevBoneTexture.m_texture,
nearestSampler);
}
}
}
if (instance) {
int binding = shaderPipeline->bindingForTexture("lastInstanceTexture");
if (binding >= 0) {
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
motionData.prevInstanceTexture.m_texture,
nearestSampler);
}
}
auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
if ((binding = shaderPipeline->bindingForTexture("morphWeightTexture")) >= 0)
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, motionData.currentMorphWeightTexture.m_texture, targetsSampler);
if ((binding = shaderPipeline->bindingForTexture("lastMorphWeightTexture")) >= 0)
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, motionData.prevMorphWeightTexture.m_texture, targetsSampler);
}
}
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
ia = subsetRenderable.subset.rhi.ia;
const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
QVector3D cameraDirection = cameraDatas[0].direction;
QVector3D cameraPosition = cameraDatas[0].position;
int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
QRhiShaderResourceBindings *&srb = dcd.srb;
bool srbChanged = false;
if (!srb || bindings != dcd.bindings) {
srb = rhiCtxD->srb(bindings);
rhiCtxD->releaseCachedSrb(dcd.bindings);
dcd.bindings = bindings;
srbChanged = true;
}
subsetRenderable.rhiRenderData.motionVectorPass.srb = srb;
const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
if (dcd.pipeline
&& !srbChanged
&& dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
&& dcd.renderTargetDescription == pipelineKey.renderTargetDescription
&& dcd.ps == *ps)
{
subsetRenderable.rhiRenderData.motionVectorPass.pipeline = dcd.pipeline;
} else {
subsetRenderable.rhiRenderData.motionVectorPass.pipeline = rhiCtxD->pipeline(pipelineKey,
renderPassDescriptor,
srb);
dcd.pipeline = subsetRenderable.rhiRenderData.motionVectorPass.pipeline;
dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
dcd.ps = *ps;
}
}
void RenderHelpers::rhiRenderMotionVector(QSSGRhiContext *rhiCtx,
const QSSGRhiGraphicsPipelineState &state,
const QSSGRenderableObjectList *motionVectorPassObjects,
int bucketsCount)
{
bool needsSetViewport = true;
auto renderObjectList = [&](const QSSGRenderableObjectList &objects) {
for (const auto &handle : objects) {
QSSGRenderableObject &object = *handle.obj;
// Only handle mesh subsets
if (object.type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
object.type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
continue;
QSSGSubsetRenderable &subsetRenderable = static_cast<QSSGSubsetRenderable &>(object);
QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.motionVectorPass.pipeline;
QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.motionVectorPass.srb;
if (!ps || !srb)
return;
QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
// QRhi optimizes out unnecessary binding of the same pipline
cb->setGraphicsPipeline(ps);
cb->setShaderResources(srb);
if (needsSetViewport) {
cb->setViewport(state.viewport);
if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
cb->setScissor(state.scissor);
needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if ( subsetRenderable.modelContext.model.instancing()) {
instances = subsetRenderable.modelContext.model.instanceCount();
// If the instance count is 0, bail out before trying to do any
// draw calls. Making an instanced draw call with a count of 0 is invalid
// for Metal and likely other API's as well.
// It is possible that the particle systems may produce 0 instances here
if (instances == 0)
return;
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
vertexBufferCount = 2;
}
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
cb->setStencilRef(state.stencilRef);
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
QVector<int>({subsetRenderable.modelContext.model.profilingId,
subsetRenderable.material.profilingId}));
}
};
for (int i = 0; i < bucketsCount; ++i)
renderObjectList(motionVectorPassObjects[i]);
}
QT_END_NAMESPACE