1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssgdebugdrawsystem_p.h"
5#include "qssgrenderhelpers_p.h"
6
7#include "qssgrenderer_p.h"
8#include "qssglayerrenderdata_p.h"
9#include "qssgrhiparticles_p.h"
10#include "qssgrhiquadrenderer_p.h"
11#include "../qssgrendercontextcore.h"
12#include "../qssgrhicustommaterialsystem_p.h"
13#include "../resourcemanager/qssgrenderbuffermanager_p.h"
14#include "../qssgrenderdefaultmaterialshadergenerator_p.h"
15#include "rendererimpl/qssgshadowmaphelpers_p.h"
16#include <QtQuick3DUtils/private/qssgassert_p.h>
17
18#include <QtGui/qquaternion.h>
19
20#include <QtCore/qbitarray.h>
21
22#include <cmath>
23
24QT_BEGIN_NAMESPACE
25
26static constexpr float QSSG_PI = float(M_PI);
27static constexpr float QSSG_HALFPI = float(M_PI_2);
28
29static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL =
30 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
31
32static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps,
33 QSSGSubsetRenderable &subsetRenderable,
34 const QSSGShaderFeatures &featureSet)
35{
36 auto &renderer(subsetRenderable.renderer);
37 const auto &shaderPipeline = QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(renderer&: *renderer, inRenderable&: subsetRenderable, inFeatureSet: featureSet);
38 if (shaderPipeline)
39 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps&: *ps, pipeline: shaderPipeline.get());
40 return shaderPipeline;
41}
42
43static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps,
44 QSSGParticlesRenderable &particleRenderable,
45 QSSGRenderLayer::OITMethod method)
46{
47 const auto &renderer(particleRenderable.renderer);
48 const auto &shaderCache = renderer->contextInterface()->shaderCache();
49 auto featureLevel = particleRenderable.particles.m_featureLevel;
50 const auto &shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiParticleShader(featureLevel, viewCount: ps->viewCount, method);
51 if (shaderPipeline)
52 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps&: *ps, pipeline: shaderPipeline.get());
53 return shaderPipeline;
54}
55
56static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline,
57 QSSGRhiContext *rhiCtx,
58 const QSSGLayerRenderData &inData,
59 char *ubufData,
60 QSSGRhiGraphicsPipelineState *ps,
61 QSSGSubsetRenderable &subsetRenderable,
62 const QSSGRenderCameraList &cameras,
63 const QVector2D *depthAdjust,
64 const QMatrix4x4 *alteredModelViewProjection)
65{
66 const auto &renderer(subsetRenderable.renderer);
67 const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
68 QSSGRenderMvpArray alteredMvpList;
69 if (alteredModelViewProjection)
70 alteredMvpList[0] = *alteredModelViewProjection;
71
72 const auto &modelNode = subsetRenderable.modelContext.model;
73 QRhiTexture *lightmapTexture = inData.getLightmapTexture(modelContext: subsetRenderable.modelContext);
74
75 const auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(node: modelNode);
76 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : inData.getGlobalTransform(node: modelNode));
77
78 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*renderer->contextInterface(),
79 shaders&: shaderPipeline,
80 ubufData,
81 inPipelineState: ps,
82 inMaterial: subsetRenderable.material,
83 inKey: subsetRenderable.shaderDescription,
84 inProperties: inData.getDefaultMaterialPropertyTable(),
85 inCameras: cameras,
86 inModelViewProjections: alteredModelViewProjection ? alteredMvpList : subsetRenderable.modelContext.modelViewProjections,
87 inNormalMatrix: subsetRenderable.modelContext.normalMatrix,
88 inGlobalTransform: modelMatrix,
89 clipSpaceCorrMatrix,
90 localInstanceTransform,
91 globalInstanceTransform,
92 inMorphWeights: toDataView(type: modelNode.morphWeights),
93 inFirstImage: subsetRenderable.firstImage,
94 inOpacity: subsetRenderable.opacity,
95 inRenderProperties: inData,
96 inLights: subsetRenderable.lights,
97 reflectionProbe: subsetRenderable.reflectionProbe,
98 receivesShadows: subsetRenderable.renderableFlags.receivesShadows(),
99 receivesReflections: subsetRenderable.renderableFlags.receivesReflections(),
100 shadowDepthAdjust: depthAdjust,
101 lightmapTexture);
102}
103
104std::pair<QSSGBounds3, QSSGBounds3> RenderHelpers::calculateSortedObjectBounds(const QSSGRenderableObjectList &sortedOpaqueObjects,
105 const QSSGRenderableObjectList &sortedTransparentObjects)
106{
107 QSSGBounds3 boundsCasting;
108 QSSGBounds3 boundsReceiving;
109 for (const auto handles : { &sortedOpaqueObjects, &sortedTransparentObjects }) {
110 // Since we may have nodes that are not a child of the camera parent we go through all
111 // the opaque objects and include them in the bounds. Failing to do this can result in
112 // too small bounds.
113 for (const QSSGRenderableObjectHandle &handle : *handles) {
114 const QSSGRenderableObject &obj = *handle.obj;
115 // We skip objects not casting or receiving shadows since they don't influence or need to be covered by the shadow map
116 if (obj.renderableFlags.castsShadows()) {
117 if (!obj.globalBoundsInstancing.isEmpty())
118 boundsCasting.include(b: obj.globalBoundsInstancing);
119 else
120 boundsCasting.include(b: obj.globalBounds);
121 }
122 if (obj.renderableFlags.receivesShadows()) {
123 if (!obj.globalBoundsInstancing.isEmpty())
124 boundsReceiving.include(b: obj.globalBoundsInstancing);
125 else
126 boundsReceiving.include(b: obj.globalBounds);
127 }
128 }
129 }
130 return { boundsCasting, boundsReceiving };
131}
132
133static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
134{
135 bool invertible = false;
136 QMatrix4x4 inv = projection.inverted(invertible: &invertible);
137 Q_ASSERT(invertible);
138
139 // The frustum points will be in this orientation
140 //
141 // bottom top
142 // 4__________5 7__________6
143 // \ / \ /
144 // \ / \ /
145 // \____/ \____/
146 // 0 1 3 2
147 return { inv.map(point: QVector3D(-1, -1, -1)), inv.map(point: QVector3D(+1, -1, -1)), inv.map(point: QVector3D(+1, +1, -1)),
148 inv.map(point: QVector3D(-1, +1, -1)), inv.map(point: QVector3D(-1, -1, +1)), inv.map(point: QVector3D(+1, -1, +1)),
149 inv.map(point: QVector3D(+1, +1, +1)), inv.map(point: QVector3D(-1, +1, +1)) };
150}
151
152static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
153{
154 QSSGBoxPoints pts;
155 for (int i = 0; i < 4; ++i) {
156 const QVector3D forward = frustumPoints[i + 4] - frustumPoints[i];
157 pts[i] = frustumPoints[i] + forward * t0;
158 pts[i + 4] = frustumPoints[i] + forward * t1;
159 }
160 return pts;
161}
162
163static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix,
164 const QMatrix4x4 &lightMatrixInverted,
165 const QVector3D &lightPivot,
166 const QVector3D &lightForward,
167 const QVector3D &lightUp,
168 const float shadowMapResolution,
169 const float pcfRadius,
170 const QSSGBoxPoints &frustumPoints,
171 float frustumStartT,
172 float frustumEndT,
173 float frustumRadius,
174 bool lockShadowmapTexels,
175 const QSSGBounds3 &castingBox,
176 const QSSGBounds3 &receivingBox,
177 QSSGDebugDrawSystem *debugDrawSystem,
178 const bool drawCascades,
179 const bool drawSceneCascadeIntersection)
180{
181 if (!castingBox.isFinite() || castingBox.isEmpty() || !receivingBox.isFinite() || receivingBox.isEmpty())
182 return nullptr; // Return early, no casting or receiving objects means no shadows
183
184 Q_ASSERT(frustumStartT <= frustumEndT);
185 Q_ASSERT(frustumStartT >= 0.f);
186 Q_ASSERT(frustumEndT <= 1.0f);
187
188 auto transformPoints = [&](const QSSGBoxPoints &points) {
189 QSSGBoxPoints result;
190 for (int i = 0; i < int(points.size()); ++i) {
191 result[i] = lightMatrix.map(point: points[i]);
192 }
193 return result;
194 };
195
196 QSSGBoxPoints frustumPointsSliced = sliceFrustum(frustumPoints, t0: frustumStartT, t1: frustumEndT);
197 if (drawCascades)
198 ShadowmapHelpers::addDebugFrustum(frustumPoints: frustumPointsSliced, color: QColorConstants::Black, debugDrawSystem);
199
200 QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPoints: frustumPointsSliced,
201 box: receivingBox.toQSSGBoxPoints(),
202 debugDrawSystem: drawSceneCascadeIntersection ? debugDrawSystem : nullptr,
203 color: QColorConstants::DarkGray);
204 if (receivingSliced.isEmpty())
205 return nullptr;
206
207 QSSGBounds3 receivingFrustumSlicedLightSpace;
208 for (const QVector3D &point : receivingSliced)
209 receivingFrustumSlicedLightSpace.include(v: lightMatrix.map(point));
210
211 // Slice casting box by frustumBounds' left, right, up, down planes
212 QList<QVector3D> castingPointsLightSpace = ShadowmapHelpers::intersectBoxByBox(boxBounds: receivingFrustumSlicedLightSpace,
213 box: transformPoints(castingBox.toQSSGBoxPointsNoEmptyCheck()));
214 if (castingPointsLightSpace.isEmpty())
215 return nullptr;
216
217 // Create box containing casting and receiving from light space:
218 QSSGBounds3 castReceiveBounds;
219 for (const QVector3D &p : castingPointsLightSpace) {
220 castReceiveBounds.include(v: p);
221 }
222
223 for (const QVector3D &p : receivingFrustumSlicedLightSpace.toQSSGBoxPointsNoEmptyCheck()) {
224 float zMax = qMax(a: p.z(), b: castReceiveBounds.maximum.z());
225 float zMin = qMin(a: p.z(), b: castReceiveBounds.minimum.z());
226 castReceiveBounds.maximum.setZ(zMax);
227 castReceiveBounds.minimum.setZ(zMin);
228 }
229
230 // Expand to fit pcf radius
231 castReceiveBounds.fatten(distance: pcfRadius);
232
233 QVector3D boundsCenterWorld = lightMatrixInverted.map(point: castReceiveBounds.center());
234 QVector3D boundsDims = castReceiveBounds.dimensions();
235 boundsDims.setZ(boundsDims.z() * 1.01f); // Expand slightly in z direction to avoid pancaking precision errors
236
237 if (lockShadowmapTexels) {
238 // Calculate center position aligned to texel size to avoid shimmering
239 const float diam = (pcfRadius + frustumRadius) * 2.0f;
240 const float texelsPerUnit = diam / shadowMapResolution;
241 QVector3D centerLight = lightMatrix.map(point: boundsCenterWorld);
242 float x = centerLight.x();
243 float y = centerLight.y();
244 float z = centerLight.z();
245 centerLight = QVector3D(int(x / texelsPerUnit), int(y / texelsPerUnit), int(z / texelsPerUnit)) * texelsPerUnit;
246 boundsCenterWorld = lightMatrixInverted.map(point: centerLight);
247 boundsDims.setX(diam);
248 boundsDims.setY(diam);
249 } else {
250 // We expand the shadowmap to cover the bounds with one extra texel on all sides
251 const float texelExpandFactor = shadowMapResolution / (shadowMapResolution - 2);
252 boundsDims.setX(boundsDims.x() * texelExpandFactor);
253 boundsDims.setY(boundsDims.y() * texelExpandFactor);
254 }
255
256 QRectF theViewport(0.0f, 0.0f, boundsDims.x(), boundsDims.y());
257
258 auto camera = std::make_unique<QSSGRenderCamera>(args: QSSGRenderGraphObject::Type::OrthographicCamera);
259 camera->clipPlanes = QSSGRenderCamera::ClipPlanes{-0.5f * boundsDims.z(), 0.5f * boundsDims.z()};
260 camera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(fov: 90.f);
261 camera->parent = nullptr;
262 camera->localTransform = QSSGRenderNode::calculateTransformMatrix(position: boundsCenterWorld,
263 scale: QSSGRenderNode::initScale,
264 pivot: lightPivot,
265 rotation: QQuaternion::fromDirection(direction: lightForward, up: lightUp));
266 QSSGRenderCamera::calculateProjectionInternal(camera&: *camera, inViewport: theViewport);
267
268 return camera;
269}
270
271static QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> setupCascadingCamerasForShadowMap(const QSSGLayerRenderData &data,
272 const QSSGRenderCamera &inCamera,
273 const QSSGRenderLight *inLight,
274 const int shadowMapResolution,
275 const float pcfRadius,
276 const float clipNear,
277 const float clipFar,
278 const QSSGBounds3 &castingObjectsBox,
279 const QSSGBounds3 &receivingObjectsBox,
280 bool lockShadowmapTexels,
281 QSSGDebugDrawSystem *debugDrawSystem,
282 bool drawCascades,
283 bool drawSceneCascadeIntersection)
284{
285 Q_ASSERT(inLight->type == QSSGRenderLight::Type::DirectionalLight);
286 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> result;
287
288 if (clipNear >= clipFar || qFuzzyCompare(p1: clipNear, p2: clipFar))
289 return result;
290
291 const QMatrix4x4 lightGlobalTransform = data.getGlobalTransform(node: *inLight);
292 const QVector3D lightDir = inLight->getDirection(globalTransform: lightGlobalTransform);
293 const QVector3D lightPivot = inLight->pivot;
294
295 const QVector3D forward = lightDir.normalized();
296 const QVector3D right = qFuzzyCompare(p1: qAbs(t: forward.y()), p2: 1.0f)
297 ? QVector3D::crossProduct(v1: forward, v2: QVector3D(1, 0, 0)).normalized()
298 : QVector3D::crossProduct(v1: forward, v2: QVector3D(0, 1, 0)).normalized();
299 const QVector3D up = QVector3D::crossProduct(v1: right, v2: forward).normalized();
300
301 QMatrix4x4 lightMatrix;
302 lightMatrix.setRow(index: 0, value: QVector4D(right, 0.0f));
303 lightMatrix.setRow(index: 1, value: QVector4D(up, 0.0f));
304 lightMatrix.setRow(index: 2, value: QVector4D(forward, 0.0f));
305 lightMatrix.setRow(index: 3, value: QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
306 QMatrix4x4 lightMatrixInverted = lightMatrix.inverted();
307
308 const float farScale = (clipFar - clipNear) / (inCamera.clipPlanes.clipFar() - inCamera.clipPlanes.clipNear());
309
310 const QMatrix4x4 cameraGlobalTransform = data.getGlobalTransform(node: inCamera);
311 QMatrix4x4 viewProjection(Qt::Uninitialized);
312 inCamera.calculateViewProjectionMatrix(globalTransform: cameraGlobalTransform, outMatrix&: viewProjection);
313 const QSSGBoxPoints frustum = computeFrustumBounds(projection: viewProjection);
314 const QSSGBoxPoints frustumUntransformed = lockShadowmapTexels ? computeFrustumBounds(projection: inCamera.projection) : QSSGBoxPoints();
315
316 // We calculate the radius of the cascade without rotation or translation so we always get
317 // the same floating point value.
318 const auto calcFrustumRadius = [&](float t0, float t1) -> float {
319 const QSSGBoxPoints pts = sliceFrustum(frustumPoints: frustumUntransformed, t0: t0 * farScale, t1: t1 * farScale);
320
321 QVector3D center = QVector3D(0.f, 0.f, 0.f);
322 for (QVector3D point : pts) {
323 center += point;
324 }
325 center = center * 0.125f;
326
327 float radiusSquared = 0;
328 for (QVector3D point : pts) {
329 radiusSquared = qMax(a: radiusSquared, b: (point - center).lengthSquared());
330 }
331
332 return std::sqrt(x: radiusSquared);
333 };
334
335 const auto computeSplitRanges = [inLight](const QVarLengthArray<float, 3> &splits) -> QVarLengthArray<QPair<float, float>, 4> {
336 QVarLengthArray<QPair<float, float>, 4> ranges;
337 const float csmBlendRatio = inLight->m_csmBlendRatio;
338 float t0 = 0.f;
339 for (qsizetype i = 0; i < splits.length(); i++) {
340 const float tI = qBound(min: qMin(a: t0 + 0.01f, b: 1.0f), val: splits[i], max: 1.0f);
341 ranges.emplace_back(args&: t0, args: qMin(a: 1.0f, b: tI + csmBlendRatio));
342 t0 = tI;
343 }
344 ranges.emplace_back(args&: t0, args: 1.0f);
345 return ranges;
346 };
347
348 const auto computeFrustums = [&](const QVarLengthArray<float, 3> &splits) {
349 for (const auto &range : computeSplitRanges(splits)) {
350 const float frustumRadius = lockShadowmapTexels ? calcFrustumRadius(range.first, range.second) : 0.0f;
351 auto camera = computeShadowCameraFromFrustum(lightMatrix,
352 lightMatrixInverted,
353 lightPivot,
354 lightForward: forward,
355 lightUp: up,
356 shadowMapResolution,
357 pcfRadius,
358 frustumPoints: frustum,
359 frustumStartT: range.first * farScale,
360 frustumEndT: range.second * farScale,
361 frustumRadius,
362 lockShadowmapTexels,
363 castingBox: castingObjectsBox,
364 receivingBox: receivingObjectsBox,
365 debugDrawSystem,
366 drawCascades,
367 drawSceneCascadeIntersection);
368 result.emplace_back(args: std::move(camera));
369 }
370 };
371
372 switch (inLight->m_csmNumSplits) {
373 case 0: {
374 computeFrustums({});
375 break;
376 }
377 case 1: {
378 computeFrustums({ inLight->m_csmSplit1 });
379 break;
380 }
381 case 2: {
382 computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2 });
383 break;
384 }
385 case 3: {
386 computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2, inLight->m_csmSplit3 });
387 break;
388 }
389 default:
390 Q_UNREACHABLE();
391 break;
392 }
393
394 return result;
395}
396
397static void setupCubeReflectionCameras(const QSSGLayerRenderData &inData, const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
398{
399 Q_ASSERT(inProbe != nullptr);
400
401 // setup light matrix
402 quint32 mapRes = 1 << inProbe->reflectionMapRes;
403 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
404 static const QQuaternion rotOfs[6] {
405 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: -QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
406 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
407 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: QSSG_HALFPI), yaw: 0.f, roll: 0.f),
408 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: -QSSG_HALFPI), yaw: 0.f, roll: 0.f),
409 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_PI), roll: qRadiansToDegrees(radians: -QSSG_PI)),
410 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: 0.f, roll: qRadiansToDegrees(radians: QSSG_PI)),
411 };
412
413 auto inProbeGlobalTranform = inData.getGlobalTransform(node: *inProbe);
414 const QVector3D inProbePos = QSSGRenderNode::getGlobalPos(globalTransform: inProbeGlobalTranform);
415 const QVector3D inProbePivot = inProbe->pivot;
416
417 for (int i = 0; i < 6; ++i) {
418 inCameras[i].parent = nullptr;
419 inCameras[i].clipPlanes = {1.0f, qMax<float>(a: 2.0f, b: 10000.0f)};
420 inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(fov: 90.f);
421
422 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(position: inProbePos, scale: QSSGRenderNode::initScale, pivot: inProbePivot, rotation: rotOfs[i]);
423 QSSGRenderCamera::calculateProjectionInternal(camera&: inCameras[i], inViewport: theViewport);
424 }
425}
426
427static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx,
428 QSSGRhiShaderPipeline *shaderPipeline,
429 QSSGRenderableImage *renderableImage,
430 QSSGRhiShaderResourceBindingList &bindings,
431 bool isCustomMaterialMeshSubset = false)
432{
433 static const auto imageAffectsAlpha = [](QSSGRenderableImage::Type mapType) {
434 return mapType == QSSGRenderableImage::Type::BaseColor ||
435 mapType == QSSGRenderableImage::Type::Diffuse ||
436 mapType == QSSGRenderableImage::Type::Translucency ||
437 mapType == QSSGRenderableImage::Type::Opacity;
438 };
439
440 while (renderableImage) {
441 const auto mapType = renderableImage->m_mapType;
442 if (imageAffectsAlpha(mapType)) {
443 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: mapType);
444 const int samplerHint = int(mapType);
445 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
446 if (samplerBinding >= 0) {
447 QRhiTexture *texture = renderableImage->m_texture.m_texture;
448 if (samplerBinding >= 0 && texture) {
449 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
450 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_minFilterType),
451 .magFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_magFilterType),
452 .mipmap: mipmapped ? QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
453 .hTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
454 .vTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
455 .zTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_depthTilingMode)
456 });
457 bindings.addTexture(binding: samplerBinding, stage: RENDERER_VISIBILITY_ALL, tex: texture, sampler);
458 }
459 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
460 }
461 renderableImage = renderableImage->m_nextImage;
462 }
463 // For custom Materials we can't know which maps affect alpha, so map all
464 if (isCustomMaterialMeshSubset) {
465 QVector<QShaderDescription::InOutVariable> samplerVars =
466 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
467 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
468 auto it = std::find_if(first: samplerVars.cbegin(), last: samplerVars.cend(),
469 pred: [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
470 if (it == samplerVars.cend())
471 samplerVars.append(t: var);
472 }
473
474 int maxSamplerBinding = -1;
475 for (const QShaderDescription::InOutVariable &var : samplerVars)
476 maxSamplerBinding = qMax(a: maxSamplerBinding, b: var.binding);
477
478 // Will need to set unused image-samplers to something dummy
479 // because the shader code contains all custom property textures,
480 // and not providing a binding for all of them is invalid with some
481 // graphics APIs (and will need a real texture because setting a
482 // null handle or similar is not permitted with some of them so the
483 // srb does not accept null QRhiTextures either; but first let's
484 // figure out what bindings are unused in this frame)
485 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
486
487 if (maxSamplerBinding >= 0) {
488 // custom property textures
489 int customTexCount = shaderPipeline->extraTextureCount();
490 for (int i = 0; i < customTexCount; ++i) {
491 const QSSGRhiTexture &t(shaderPipeline->extraTextureAt(index: i));
492 const int samplerBinding = shaderPipeline->bindingForTexture(name: t.name);
493 if (samplerBinding >= 0) {
494 samplerBindingsSpecified.setBit(samplerBinding);
495 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: t.samplerDesc);
496 bindings.addTexture(binding: samplerBinding,
497 stage: RENDERER_VISIBILITY_ALL,
498 tex: t.texture,
499 sampler);
500 }
501 }
502 }
503
504 // use a dummy texture for the unused samplers in the shader
505 QRhiSampler *dummySampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
506 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
507 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
508 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
509 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(flags: QRhiTexture::CubeMap, rub: resourceUpdates);
510 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
511
512 for (const QShaderDescription::InOutVariable &var : samplerVars) {
513 if (!samplerBindingsSpecified.testBit(i: var.binding)) {
514 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
515 bindings.addTexture(binding: var.binding, stage: RENDERER_VISIBILITY_ALL, tex: t, sampler: dummySampler);
516 }
517 }
518 }
519}
520
521static void setupCubeShadowCameras(const QSSGLayerRenderData &inData,
522 const QSSGRenderLight *inLight,
523 float shadowMapFar,
524 QSSGRenderCamera inCameras[6])
525{
526 Q_ASSERT(inLight != nullptr);
527 Q_ASSERT(inLight->type != QSSGRenderLight::Type::DirectionalLight);
528
529 // setup light matrix
530 quint32 mapRes = inLight->m_shadowMapRes;
531 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
532 static const QQuaternion rotOfs[6] {
533 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: -QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
534 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
535 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: QSSG_HALFPI), yaw: 0.f, roll: 0.f),
536 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: -QSSG_HALFPI), yaw: 0.f, roll: 0.f),
537 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_PI), roll: qRadiansToDegrees(radians: -QSSG_PI)),
538 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: 0.f, roll: qRadiansToDegrees(radians: QSSG_PI)),
539 };
540
541 const auto gt = inData.getGlobalTransform(node: *inLight);
542 const QVector3D inLightPos = QSSGRenderNode::getGlobalPos(globalTransform: gt);
543 constexpr QVector3D lightPivot = QVector3D(0, 0, 0);
544
545 for (int i = 0; i < 6; ++i) {
546 inCameras[i].parent = nullptr;
547 inCameras[i].clipPlanes = {1.0f, shadowMapFar};
548 inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(fov: 90.f);
549 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(position: inLightPos, scale: QSSGRenderNode::initScale, pivot: lightPivot, rotation: rotOfs[i]);
550 QSSGRenderCamera::calculateProjectionInternal(camera&: inCameras[i], inViewport: theViewport);
551 }
552
553 /*
554 if ( inLight->type == RenderLightTypes::Point ) return;
555
556 QVector3D viewDirs[6];
557 QVector3D viewUp[6];
558 QMatrix3x3 theDirMatrix( inLight->m_GlobalTransform.getUpper3x3() );
559
560 viewDirs[0] = theDirMatrix.transform( QVector3D( 1.f, 0.f, 0.f ) );
561 viewDirs[2] = theDirMatrix.transform( QVector3D( 0.f, -1.f, 0.f ) );
562 viewDirs[4] = theDirMatrix.transform( QVector3D( 0.f, 0.f, 1.f ) );
563 viewDirs[0].normalize(); viewDirs[2].normalize(); viewDirs[4].normalize();
564 viewDirs[1] = -viewDirs[0];
565 viewDirs[3] = -viewDirs[2];
566 viewDirs[5] = -viewDirs[4];
567
568 viewUp[0] = viewDirs[2];
569 viewUp[1] = viewDirs[2];
570 viewUp[2] = viewDirs[5];
571 viewUp[3] = viewDirs[4];
572 viewUp[4] = viewDirs[2];
573 viewUp[5] = viewDirs[2];
574
575 for (int i = 0; i < 6; ++i)
576 {
577 inCameras[i].LookAt( inLightPos, viewUp[i], inLightPos + viewDirs[i] );
578 inCameras[i].CalculateGlobalVariables( theViewport, QVector2D( theViewport.m_Width,
579 theViewport.m_Height ) );
580 }
581 */
582}
583
584static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
585{
586 // TODO: non-static so it can be used from QSSGCustomMaterialSystem::rhiPrepareRenderable()?
587 const bool instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable, cameraDirection, cameraPosition, minThreshold: renderable->instancingLodMin, maxThreshold: renderable->instancingLodMax);
588 int instanceBufferBinding = 0;
589 if (instancing) {
590 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
591 // set up new bindings for instanced buffers
592 const quint32 stride = renderable->modelContext.model.instanceTable->stride();
593 QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
594 std::copy(first: ia.inputLayout.cbeginBindings(), last: ia.inputLayout.cendBindings(), result: std::back_inserter(x&: bindings));
595 bindings.append(t: { stride, QRhiVertexInputBinding::PerInstance });
596 instanceBufferBinding = bindings.size() - 1;
597 ia.inputLayout.setBindings(first: bindings.cbegin(), last: bindings.cend());
598 }
599 return instanceBufferBinding;
600}
601
602static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx,
603 QSSGPassKey passKey,
604 const QSSGLayerRenderData &inData,
605 QSSGReflectionMapEntry *pEntry,
606 QSSGRhiGraphicsPipelineState *ps,
607 const QSSGRenderableObjectList &sortedOpaqueObjects,
608 QSSGRenderCamera &inCamera,
609 QSSGRenderer &renderer,
610 QSSGRenderTextureCubeFace cubeFace)
611{
612 using namespace RenderHelpers;
613
614 if ((inData.layer.background == QSSGRenderLayer::Background::SkyBox && inData.layer.lightProbe) ||
615 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
616 rhiPrepareSkyBoxForReflectionMap(rhiCtx, passKey, layer&: inData.layer, inCamera, renderer, entry: pEntry, cubeFace);
617
618 QSSGShaderFeatures features = inData.getShaderFeatures();
619 // because of alteredCamera/alteredMvp below
620 features.set(feature: QSSGShaderFeatures::Feature::DisableMultiView, val: true);
621
622 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
623
624 for (const auto &handle : sortedOpaqueObjects) {
625 QSSGRenderableObject &inObject = *handle.obj;
626
627 QMatrix4x4 modelViewProjection;
628 if (inObject.type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || inObject.type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
629 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(inObject));
630 const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(inDataStore: renderable.shaderDescription) > 0;
631 const QMatrix4x4 &globalTransform = renderable.modelContext.globalTransform;
632 modelViewProjection = hasSkinning ? pEntry->m_viewProjection
633 : pEntry->m_viewProjection * globalTransform;
634 }
635
636 // here we pass on our own alteredCamera and alteredModelViewProjection
637 rhiPrepareRenderable(rhiCtx, passKey, inData, inObject, renderPassDescriptor: pEntry->m_rhiRenderPassDesc, ps, featureSet: features, samples: 1, viewCount: 1,
638 alteredCamera: &inCamera, alteredModelViewProjection: &modelViewProjection, cubeFace, entry: pEntry);
639 }
640}
641
642static inline void addDepthTextureBindings(QSSGRhiContext *rhiCtx,
643 QSSGRhiShaderPipeline *shaderPipeline,
644 QSSGRhiShaderResourceBindingList &bindings)
645{
646 if (shaderPipeline->depthTexture()) {
647 const int depthTextureBinding = shaderPipeline->bindingForTexture(name: "qt_depthTexture", hint: int(QSSGRhiSamplerBindingHints::DepthTexture));
648 const int depthTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_depthTextureArray", hint: int(QSSGRhiSamplerBindingHints::DepthTextureArray));
649 if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
650 // nearest min/mag, no mipmap
651 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
652 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
653 if (depthTextureBinding >= 0)
654 bindings.addTexture(binding: depthTextureBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: shaderPipeline->depthTexture(), sampler);
655 if (depthTextureArrayBinding >= 0)
656 bindings.addTexture(binding: depthTextureBinding, stage: QRhiShaderResourceBinding::FragmentStage, tex: shaderPipeline->depthTexture(), sampler);
657 } // else ignore, not an error
658 }
659
660 // SSAO texture
661 if (shaderPipeline->ssaoTexture()) {
662 const int ssaoTextureBinding = shaderPipeline->bindingForTexture(name: "qt_aoTexture", hint: int(QSSGRhiSamplerBindingHints::AoTexture));
663 const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_aoTextureArray", hint: int(QSSGRhiSamplerBindingHints::AoTextureArray));
664 if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
665 // linear min/mag, no mipmap
666 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
667 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
668 if (ssaoTextureBinding >= 0) {
669 bindings.addTexture(binding: ssaoTextureBinding,
670 stage: QRhiShaderResourceBinding::FragmentStage,
671 tex: shaderPipeline->ssaoTexture(), sampler);
672 }
673 if (ssaoTextureArrayBinding >= 0) {
674 bindings.addTexture(binding: ssaoTextureArrayBinding,
675 stage: QRhiShaderResourceBinding::FragmentStage,
676 tex: shaderPipeline->ssaoTexture(), sampler);
677 }
678 } // else ignore, not an error
679 }
680}
681
682static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx,
683 const QSSGLayerRenderData &inData,
684 QSSGPassKey passKey,
685 QSSGShadowMapEntry *pEntry,
686 QSSGRhiGraphicsPipelineState *ps,
687 const QVector2D *depthAdjust,
688 const QSSGRenderableObjectList &sortedOpaqueObjects,
689 QSSGRenderCamera &inCamera,
690 bool orthographic,
691 QSSGRenderTextureCubeFace cubeFace,
692 quint32 cascadeIndex)
693{
694 QSSGShaderFeatures featureSet;
695 if (orthographic)
696 featureSet.set(feature: QSSGShaderFeatures::Feature::OrthoShadowPass, val: true);
697 else
698 featureSet.set(feature: QSSGShaderFeatures::Feature::PerspectiveShadowPass, val: true);
699
700 // Do note how updateUniformsForDefaultMaterial() get a single camera and a
701 // custom mvp; make sure multiview is disabled in the shader generator using
702 // the common flag, instead of it having to write logic for checking for
703 // OrthoShadowPoss || CubeShadowPass.
704 featureSet.set(feature: QSSGShaderFeatures::Feature::DisableMultiView, val: true);
705
706 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
707 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
708 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
709
710 for (const auto &handle : sortedOpaqueObjects) {
711 QSSGRenderableObject *theObject = handle.obj;
712 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
713
714 QSSGShaderFeatures objectFeatureSet = featureSet;
715 const bool isOpaqueDepthPrePass = theObject->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
716 if (isOpaqueDepthPrePass)
717 objectFeatureSet.set(feature: QSSGShaderFeatures::Feature::OpaqueDepthPrePass, val: true);
718
719 QSSGRhiDrawCallData *dcd = nullptr;
720 QMatrix4x4 modelViewProjection;
721 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(*theObject));
722 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
723 const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(inDataStore: renderable.shaderDescription) > 0;
724 modelViewProjection = hasSkinning ? pEntry->m_lightViewProjection[cascadeIndex]
725 : pEntry->m_lightViewProjection[cascadeIndex] * renderable.modelContext.globalTransform;
726 // cascadeIndex is 0..3 for directional light and 0 for the pointlight & spotlight
727 // cubeFaceIdx is 0 for directional & spotlight and 0..5 for the pointlight
728 // pEntry is unique per light and a light can only be one of directional, point, or spotlight.
729 const quintptr entryIdx = cascadeIndex + cubeFaceIdx + (quintptr(renderable.subset.offset) << 3);
730 dcd = &rhiCtxD->drawCallData(key: { .cid: passKey, .model: &renderable.modelContext.model, .entry: pEntry, .entryIdx: entryIdx });
731 }
732
733 QSSGRhiShaderResourceBindingList bindings;
734 QSSGRhiShaderPipelinePtr shaderPipeline;
735 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*theObject));
736 if (theObject->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset) {
737 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
738 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: material.cullMode);
739 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(inDataStore: subsetRenderable.shaderDescription);
740
741 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet: objectFeatureSet);
742 if (!shaderPipeline)
743 continue;
744 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd->ubuf);
745 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
746 // calls updateUni with an alteredCamera and alteredModelViewProjection
747 QSSGRenderCameraList cameras({ &inCamera });
748 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, depthAdjust, alteredModelViewProjection: &modelViewProjection);
749 if (blendParticles)
750 QSSGParticleRenderer::updateUniformsForParticleModel(shaderPipeline&: *shaderPipeline, ubufData, model: &subsetRenderable.modelContext.model, offset: subsetRenderable.subset.offset);
751 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
752 if (blendParticles)
753 QSSGParticleRenderer::prepareParticlesForModel(shaderPipeline&: *shaderPipeline, rhiCtx, bindings, model: &subsetRenderable.modelContext.model);
754 } else if (theObject->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset) {
755 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
756 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: material.m_cullMode);
757
758 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
759 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material, renderable&: subsetRenderable, defaultMaterialShaderKeyProperties: inData.getDefaultMaterialPropertyTable(), featureSet: objectFeatureSet);
760 if (!shaderPipeline)
761 continue;
762 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd->ubuf);
763 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
764 // inCamera is the shadow camera, not the same as inData.renderedCameras
765 QSSGRenderCameraList cameras({ &inCamera });
766 customMaterialSystem.updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, material, renderable&: subsetRenderable,
767 cameras, depthAdjust, alteredModelViewProjection: &modelViewProjection);
768 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
769 }
770
771 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
772
773 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps&: *ps, pipeline: shaderPipeline.get());
774 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
775 ia = subsetRenderable.subset.rhi.ia;
776 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
777 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection: cameraDatas[0].direction, cameraPosition: cameraDatas[0].position);
778 QSSGRhiHelpers::bakeVertexInputLocations(ia: &ia, shaders: *shaderPipeline, instanceBufferBinding);
779
780
781 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd->ubuf);
782
783 // Depth and SSAO textures, in case a custom material's shader code does something with them.
784 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
785
786 if (isOpaqueDepthPrePass) {
787 addOpaqueDepthPrePassBindings(rhiCtx,
788 shaderPipeline: shaderPipeline.get(),
789 renderableImage: subsetRenderable.firstImage,
790 bindings,
791 isCustomMaterialMeshSubset: (theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
792 }
793
794 // There is no screen texture at this stage. But the shader from a
795 // custom material may rely on it, and an object with that material
796 // can end up in the shadow map's object list. So bind a dummy
797 // texture then due to the lack of other options.
798 const int screenTextureBinding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
799 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_screenTextureArray", hint: int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
800 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
801 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
802 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
803 if (screenTextureBinding >= 0) {
804 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
805 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
806 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
807 bindings.addTexture(binding: screenTextureBinding,
808 stage: QRhiShaderResourceBinding::FragmentStage,
809 tex: dummyTexture, sampler);
810 }
811 if (screenTextureArrayBinding >= 0) {
812 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
813 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates, size: QSize(64, 64), fillColor: Qt::black, arraySize: inData.layer.viewCount);
814 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
815 bindings.addTexture(binding: screenTextureArrayBinding,
816 stage: QRhiShaderResourceBinding::FragmentStage,
817 tex: dummyTexture, sampler);
818 }
819 }
820
821 // Skinning
822 if (QRhiTexture *boneTexture = inData.getBonemapTexture(modelContext: subsetRenderable.modelContext)) {
823 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
824 if (binding >= 0) {
825 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
826 .magFilter: QRhiSampler::Nearest,
827 .mipmap: QRhiSampler::None,
828 .hTiling: QRhiSampler::ClampToEdge,
829 .vTiling: QRhiSampler::ClampToEdge,
830 .zTiling: QRhiSampler::Repeat
831 });
832 bindings.addTexture(binding,
833 stage: QRhiShaderResourceBinding::VertexStage,
834 tex: boneTexture,
835 sampler: boneSampler);
836 }
837 }
838
839 // Morphing
840 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
841 if (targetsTexture) {
842 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
843 if (binding >= 0) {
844 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
845 .magFilter: QRhiSampler::Nearest,
846 .mipmap: QRhiSampler::None,
847 .hTiling: QRhiSampler::ClampToEdge,
848 .vTiling: QRhiSampler::ClampToEdge,
849 .zTiling: QRhiSampler::ClampToEdge
850 });
851 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: subsetRenderable.subset.rhi.targetsTexture, sampler: targetsSampler);
852 }
853 }
854
855 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
856 subsetRenderable.rhiRenderData.shadowPass.pipeline = rhiCtxD->pipeline(ps: *ps, rpDesc: pEntry->m_rhiRenderPassDesc[cascadeIndex], srb);
857 subsetRenderable.rhiRenderData.shadowPass.srb[cubeFaceIdx] = srb;
858 }
859 }
860}
861
862static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
863{
864 // Assuming default values in the other TargetBlend fields
865 switch (materialBlend) {
866 case QSSGRenderDefaultMaterial::MaterialBlendMode::Screen:
867 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
868 targetBlend->dstColor = QRhiGraphicsPipeline::One;
869 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
870 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
871 break;
872 case QSSGRenderDefaultMaterial::MaterialBlendMode::Multiply:
873 targetBlend->srcColor = QRhiGraphicsPipeline::DstColor;
874 targetBlend->dstColor = QRhiGraphicsPipeline::Zero;
875 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
876 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
877 break;
878 default:
879 // Use SourceOver for everything else
880 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
881 targetBlend->dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
882 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
883 targetBlend->dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
884 break;
885 }
886}
887
888void RenderHelpers::rhiPrepareRenderable(QSSGRhiContext *rhiCtx,
889 QSSGPassKey passKey,
890 const QSSGLayerRenderData &inData,
891 QSSGRenderableObject &inObject,
892 QRhiRenderPassDescriptor *renderPassDescriptor,
893 QSSGRhiGraphicsPipelineState *ps,
894 QSSGShaderFeatures featureSet,
895 int samples,
896 int viewCount,
897 QSSGRenderCamera *alteredCamera,
898 QMatrix4x4 *alteredModelViewProjection,
899 QSSGRenderTextureCubeFace cubeFace,
900 QSSGReflectionMapEntry *entry,
901 bool oit)
902{
903 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
904
905 switch (inObject.type) {
906 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
907 {
908 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
909
910 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(flag: QSSGRenderableObjectFlag::ReceivesReflections))
911 featureSet.set(feature: QSSGShaderFeatures::Feature::ReflectionProbe, val: true);
912
913 if ((cubeFace != QSSGRenderTextureCubeFaceNone)) {
914 // Disable tonemapping for the reflection pass
915 featureSet.disableTonemapping();
916 }
917
918 if (subsetRenderable.renderableFlags.rendersWithLightmap())
919 featureSet.set(feature: QSSGShaderFeatures::Feature::Lightmap, val: true);
920
921 const auto &shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
922 if (shaderPipeline) {
923 // Unlike the subsetRenderable (which is allocated per frame so is
924 // not persistent in any way), the model reference is persistent in
925 // the sense that it references the model node in the scene graph.
926 // Combined with the layer node (multiple View3Ds may share the
927 // same scene!), this is suitable as a key to get the uniform
928 // buffers that were used with the rendering of the same model in
929 // the previous frame.
930 QSSGRhiShaderResourceBindingList bindings;
931 const auto &modelNode = subsetRenderable.modelContext.model;
932 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(inDataStore: subsetRenderable.shaderDescription);
933
934
935 // NOTE:
936 // - entryIdx should 0 for QSSGRenderTextureCubeFaceNone.
937 // In all other cases the entryIdx is a combination of the cubeface idx and the subset offset, where the lower bits
938 // are the cubeface idx.
939 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
940 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(subsetRenderable.subset.offset) << 3));
941 // If there's an entry we merge that with the address of the material
942 const auto entryPartA = reinterpret_cast<quintptr>(&subsetRenderable.material);
943 const auto entryPartB = reinterpret_cast<quintptr>(entry);
944 const void *entryId = reinterpret_cast<const void *>(entryPartA ^ entryPartB);
945
946 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
947 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData(key: { .cid: passKey, .model: &modelNode, .entry: entryId, .entryIdx: entryIdx });
948
949 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd.ubuf);
950 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
951 if (alteredCamera) {
952 Q_ASSERT(alteredModelViewProjection);
953 QSSGRenderCameraList cameras({ alteredCamera });
954 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, depthAdjust: nullptr, alteredModelViewProjection);
955 } else {
956 Q_ASSERT(!alteredModelViewProjection);
957 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras: inData.renderedCameras, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
958 }
959
960 if (blendParticles)
961 QSSGParticleRenderer::updateUniformsForParticleModel(shaderPipeline&: *shaderPipeline, ubufData, model: &subsetRenderable.modelContext.model, offset: subsetRenderable.subset.offset);
962 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
963
964 if (blendParticles)
965 QSSGParticleRenderer::prepareParticlesForModel(shaderPipeline&: *shaderPipeline, rhiCtx, bindings, model: &subsetRenderable.modelContext.model);
966
967 // Skinning
968 if (QRhiTexture *boneTexture = inData.getBonemapTexture(modelContext: subsetRenderable.modelContext)) {
969 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
970 if (binding >= 0) {
971 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
972 .magFilter: QRhiSampler::Nearest,
973 .mipmap: QRhiSampler::None,
974 .hTiling: QRhiSampler::ClampToEdge,
975 .vTiling: QRhiSampler::ClampToEdge,
976 .zTiling: QRhiSampler::Repeat
977 });
978 bindings.addTexture(binding,
979 stage: QRhiShaderResourceBinding::VertexStage,
980 tex: boneTexture,
981 sampler: boneSampler);
982 }
983 }
984 // Morphing
985 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
986 if (targetsTexture) {
987 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
988 if (binding >= 0) {
989 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
990 .magFilter: QRhiSampler::Nearest,
991 .mipmap: QRhiSampler::None,
992 .hTiling: QRhiSampler::ClampToEdge,
993 .vTiling: QRhiSampler::ClampToEdge,
994 .zTiling: QRhiSampler::ClampToEdge
995 });
996 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: subsetRenderable.subset.rhi.targetsTexture, sampler: targetsSampler);
997 }
998 }
999
1000 ps->samples = samples;
1001 ps->viewCount = viewCount;
1002
1003 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
1004 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: material.cullMode);
1005 if (!oit)
1006 fillTargetBlend(targetBlend: &ps->targetBlend[0], materialBlend: material.blendMode);
1007
1008 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
1009
1010 ia = subsetRenderable.subset.rhi.ia;
1011 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
1012 QVector3D cameraDirection = cameraDatas[0].direction;
1013 QVector3D cameraPosition = cameraDatas[0].position;
1014 if (alteredCamera) {
1015 const QMatrix4x4 camGlobalTranform = inData.getGlobalTransform(node: *alteredCamera);
1016 cameraDirection = QSSGRenderNode::getScalingCorrectDirection(globalTransform: camGlobalTranform);
1017 cameraPosition = QSSGRenderNode::getGlobalPos(globalTransform: camGlobalTranform);
1018 }
1019 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
1020 QSSGRhiHelpers::bakeVertexInputLocations(ia: &ia, shaders: *shaderPipeline, instanceBufferBinding);
1021
1022 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf, offset: 0, size: shaderPipeline->ub0Size());
1023
1024 if (shaderPipeline->isLightingEnabled()) {
1025 bindings.addUniformBuffer(binding: 1, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf,
1026 offset: shaderPipeline->ub0LightDataOffset(),
1027 size: shaderPipeline->ub0LightDataSize());
1028
1029 if (shaderPipeline->shadowMapCount() > 0) {
1030 bindings.addUniformBuffer(binding: 2, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf,
1031 offset: shaderPipeline->ub0ShadowDataOffset(),
1032 size: shaderPipeline->ub0ShadowDataSize());
1033 }
1034 }
1035
1036 // Texture maps
1037 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
1038 while (renderableImage) {
1039 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: renderableImage->m_mapType);
1040 const int samplerHint = int(renderableImage->m_mapType);
1041 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
1042 if (samplerBinding >= 0) {
1043 QRhiTexture *texture = renderableImage->m_texture.m_texture;
1044 if (samplerBinding >= 0 && texture) {
1045 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
1046 QSSGRhiSamplerDescription samplerDesc = {
1047 .minFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_minFilterType),
1048 .magFilter: QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_magFilterType),
1049 .mipmap: mipmapped ? QSSGRhiHelpers::toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
1050 .hTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
1051 .vTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
1052 .zTiling: QSSGRhiHelpers::toRhi(tiling: renderableImage->m_imageNode.m_depthTilingMode)
1053 };
1054 rhiCtx->checkAndAdjustForNPoT(texture, samplerDescription: &samplerDesc);
1055 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: samplerDesc);
1056 bindings.addTexture(binding: samplerBinding, stage: RENDERER_VISIBILITY_ALL, tex: texture, sampler);
1057 }
1058 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
1059 renderableImage = renderableImage->m_nextImage;
1060 }
1061
1062 if (shaderPipeline->isLightingEnabled()) {
1063 // Shadow map textures
1064 const int shadowMapCount = shaderPipeline->shadowMapCount();
1065 QVarLengthArray<QSize, 4> usedTextureArraySizes;
1066 for (int i = 0; i < shadowMapCount; ++i) {
1067 QSSGRhiShadowMapProperties &shadowMapProperties(shaderPipeline->shadowMapAt(index: i));
1068 const QByteArray &name(shadowMapProperties.shadowMapTextureUniformName);
1069 if (shadowMapProperties.cachedBinding < 0)
1070 shadowMapProperties.cachedBinding = shaderPipeline->bindingForTexture(name);
1071 if (shadowMapProperties.cachedBinding < 0) {
1072 qWarning(msg: "No combined image sampler for shadow map texture '%s'", name.data());
1073 continue;
1074 }
1075
1076 // Re-use same texture array if already created
1077 if (shadowMapProperties.shadowMapTexture->flags() & QRhiTexture::TextureArray) {
1078 if (usedTextureArraySizes.contains(t: shadowMapProperties.shadowMapTexture->pixelSize()))
1079 continue;
1080 usedTextureArraySizes.append(t: shadowMapProperties.shadowMapTexture->pixelSize());
1081 }
1082
1083 QRhiTexture *texture = shadowMapProperties.shadowMapTexture;
1084 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1085 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1086 Q_ASSERT(texture && sampler);
1087 bindings.addTexture(binding: shadowMapProperties.cachedBinding, stage: QRhiShaderResourceBinding::FragmentStage,
1088 tex: texture, sampler);
1089 }
1090
1091 // Prioritize reflection texture over Light Probe texture because
1092 // reflection texture also contains the irradiance and pre filtered
1093 // values for the light probe.
1094 if (featureSet.isSet(feature: QSSGShaderFeatures::Feature::ReflectionProbe)) {
1095 int reflectionSampler = shaderPipeline->bindingForTexture(name: "qt_reflectionMap");
1096 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear,
1097 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1098 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(probeIdx: subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
1099 if (reflectionSampler >= 0 && reflectionTexture)
1100 bindings.addTexture(binding: reflectionSampler, stage: QRhiShaderResourceBinding::FragmentStage, tex: reflectionTexture, sampler);
1101 } else if (shaderPipeline->lightProbeTexture()) {
1102 int binding = shaderPipeline->bindingForTexture(name: "qt_lightProbe", hint: int(QSSGRhiSamplerBindingHints::LightProbe));
1103 if (binding >= 0) {
1104 auto tiling = shaderPipeline->lightProbeTiling();
1105 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear, // enables mipmapping
1106 .hTiling: QSSGRhiHelpers::toRhi(tiling: tiling.first), .vTiling: QSSGRhiHelpers::toRhi(tiling: tiling.second), .zTiling: QRhiSampler::Repeat });
1107 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::FragmentStage,
1108 tex: shaderPipeline->lightProbeTexture(), sampler);
1109 } else {
1110 qWarning(msg: "Could not find sampler for lightprobe");
1111 }
1112 }
1113
1114 // Screen Texture
1115 if (shaderPipeline->screenTexture()) {
1116 const int screenTextureBinding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
1117 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture(name: "qt_screenTextureArray", hint: int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
1118 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
1119 // linear min/mag, mipmap filtering depends on the
1120 // texture, with SCREEN_TEXTURE there are no mipmaps, but
1121 // once SCREEN_MIP_TEXTURE is seen the texture (the same
1122 // one) has mipmaps generated.
1123 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(flag: QRhiTexture::MipMapped)
1124 ? QRhiSampler::Linear : QRhiSampler::None;
1125 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: mipFilter,
1126 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
1127 if (screenTextureBinding >= 0) {
1128 bindings.addTexture(binding: screenTextureBinding,
1129 stage: QRhiShaderResourceBinding::FragmentStage,
1130 tex: shaderPipeline->screenTexture(), sampler);
1131 }
1132 if (screenTextureArrayBinding >= 0) {
1133 bindings.addTexture(binding: screenTextureArrayBinding,
1134 stage: QRhiShaderResourceBinding::FragmentStage,
1135 tex: shaderPipeline->screenTexture(), sampler);
1136 }
1137 } // else ignore, not an error
1138 }
1139
1140 if (shaderPipeline->lightmapTexture()) {
1141 int binding = shaderPipeline->bindingForTexture(name: "qt_lightmap", hint: int(QSSGRhiSamplerBindingHints::LightmapTexture));
1142 if (binding >= 0) {
1143 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1144 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1145 bindings.addTexture(binding,
1146 stage: QRhiShaderResourceBinding::FragmentStage,
1147 tex: shaderPipeline->lightmapTexture(), sampler);
1148 } // else ignore, not an error
1149 }
1150 }
1151
1152 // Depth and SSAO textures
1153 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
1154
1155 // Instead of always doing a QHash find in srb(), store the binding
1156 // list and the srb object in the per-model+material
1157 // QSSGRhiUniformBufferSet. While this still needs comparing the
1158 // binding list, to see if something has changed, it results in
1159 // significant gains with lots of models in the scene (because the
1160 // srb hash table becomes large then, so avoiding the lookup as
1161 // much as possible is helpful)
1162 QRhiShaderResourceBindings *&srb = dcd.srb;
1163 bool srbChanged = false;
1164 if (!srb || bindings != dcd.bindings) {
1165 srb = rhiCtxD->srb(bindings);
1166 rhiCtxD->releaseCachedSrb(bindings&: dcd.bindings);
1167 dcd.bindings = bindings;
1168 srbChanged = true;
1169 }
1170
1171 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1172 subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
1173 else
1174 subsetRenderable.rhiRenderData.mainPass.srb = srb;
1175
1176 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: renderPassDescriptor, srb);
1177 if (dcd.pipeline
1178 && !srbChanged
1179 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
1180 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
1181 && dcd.ps == *ps)
1182 {
1183 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1184 subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
1185 else
1186 subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
1187 } else {
1188 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1189 subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
1190 rpDesc: renderPassDescriptor,
1191 srb);
1192 dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1193 } else {
1194 subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(key: pipelineKey,
1195 rpDesc: renderPassDescriptor,
1196 srb);
1197 dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
1198 }
1199 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
1200 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
1201 dcd.ps = *ps;
1202 }
1203 }
1204 break;
1205 }
1206 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1207 {
1208 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1209 const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1210 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1211
1212 featureSet.set(feature: QSSGShaderFeatures::Feature::LightProbe, val: inData.layer.lightProbe || material.m_iblProbe);
1213
1214 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(flag: QSSGRenderableObjectFlag::ReceivesReflections))
1215 featureSet.set(feature: QSSGShaderFeatures::Feature::ReflectionProbe, val: true);
1216
1217 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1218 // Disable tonemapping for the reflection pass
1219 featureSet.disableTonemapping();
1220 }
1221
1222 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1223 featureSet.set(feature: QSSGShaderFeatures::Feature::Lightmap, val: true);
1224
1225 customMaterialSystem.rhiPrepareRenderable(ps, passKey, renderable&: subsetRenderable, featureSet,
1226 material, layerData: inData, renderPassDescriptor, samples, viewCount,
1227 camera: alteredCamera, cubeFace, modelViewProjection: alteredModelViewProjection, entry, oit);
1228 break;
1229 }
1230 case QSSGRenderableObject::Type::Particles:
1231 {
1232 QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
1233 const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable, method: oit ? inData.layer.oitMethod : QSSGRenderLayer::OITMethod::None);
1234 if (shaderPipeline) {
1235 QSSGParticleRenderer::rhiPrepareRenderable(shaderPipeline&: *shaderPipeline, passKey, rhiCtx, ps, renderable&: particleRenderable, inData, renderPassDescriptor, samples, viewCount,
1236 alteredCamera, cubeFace, entry);
1237 }
1238 break;
1239 }
1240 }
1241}
1242
1243void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
1244 const QSSGRhiGraphicsPipelineState &state,
1245 QSSGRenderableObject &object,
1246 bool *needsSetViewport,
1247 QSSGRenderTextureCubeFace cubeFace)
1248{
1249 switch (object.type) {
1250 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1251 {
1252 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1253
1254 QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.mainPass.pipeline;
1255 QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.mainPass.srb;
1256
1257 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1258 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
1259 ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1260 srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
1261 }
1262
1263 if (!ps || !srb)
1264 return;
1265
1266 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
1267 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
1268
1269 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1270 // QRhi optimizes out unnecessary binding of the same pipline
1271 cb->setGraphicsPipeline(ps);
1272 cb->setShaderResources(srb);
1273
1274 if (*needsSetViewport) {
1275 cb->setViewport(state.viewport);
1276 if (state.flags.testFlag(flag: QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
1277 cb->setScissor(state.scissor);
1278 *needsSetViewport = false;
1279 }
1280
1281 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1282 int vertexBufferCount = 1;
1283 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1284 quint32 instances = 1;
1285 if ( subsetRenderable.modelContext.model.instancing()) {
1286 instances = subsetRenderable.modelContext.model.instanceCount();
1287 // If the instance count is 0, the bail out before trying to do any
1288 // draw calls. Making an instanced draw call with a count of 0 is invalid
1289 // for Metal and likely other API's as well.
1290 // It is possible that the particale system may produce 0 instances here
1291 if (instances == 0)
1292 return;
1293 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
1294 vertexBufferCount = 2;
1295 }
1296 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1297 if (state.flags.testFlag(flag: QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
1298 cb->setStencilRef(state.stencilRef);
1299 if (indexBuffer) {
1300 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: subsetRenderable.subset.rhi.indexBuffer->indexFormat());
1301 cb->drawIndexed(indexCount: subsetRenderable.subset.lodCount(lodLevel: subsetRenderable.subsetLevelOfDetail), instanceCount: instances, firstIndex: subsetRenderable.subset.lodOffset(lodLevel: subsetRenderable.subsetLevelOfDetail));
1302 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
1303 } else {
1304 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
1305 cb->draw(vertexCount: subsetRenderable.subset.count, instanceCount: instances, firstVertex: subsetRenderable.subset.offset);
1306 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
1307 }
1308 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
1309 QVector<int>({subsetRenderable.modelContext.model.profilingId,
1310 subsetRenderable.material.profilingId}));
1311 break;
1312 }
1313 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1314 {
1315 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1316 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1317 customMaterialSystem.rhiRenderRenderable(rhiCtx, renderable&: subsetRenderable, needsSetViewport, cubeFace, state);
1318 break;
1319 }
1320 case QSSGRenderableObject::Type::Particles:
1321 {
1322 QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
1323 QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
1324 break;
1325 }
1326 }
1327}
1328
1329void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
1330 QSSGPassKey passKey,
1331 QSSGRhiGraphicsPipelineState &ps,
1332 QSSGRenderShadowMap &shadowMapManager,
1333 const QSSGRenderCamera &camera,
1334 QSSGRenderCamera *debugCamera,
1335 const QSSGShaderLightList &globalLights,
1336 const QSSGRenderableObjectList &sortedOpaqueObjects,
1337 QSSGRenderer &renderer,
1338 const QSSGBounds3 &castingObjectsBox,
1339 const QSSGBounds3 &receivingObjectsBox)
1340{
1341 const QSSGLayerRenderData &layerData = *QSSGLayerRenderData::getCurrent(renderer);
1342
1343 static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
1344 QSSGRhiGraphicsPipelineState *ps,
1345 const QSSGRenderableObjectList &sortedOpaqueObjects,
1346 int cubeFace) {
1347 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1348 bool needsSetViewport = true;
1349
1350 for (const auto &handle : sortedOpaqueObjects) {
1351 QSSGRenderableObject *theObject = handle.obj;
1352 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1353 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1354 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
1355
1356 QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
1357 QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
1358 ? renderable->subset.rhi.indexBuffer->buffer()
1359 : nullptr;
1360
1361 // Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
1362 if (!renderable->rhiRenderData.shadowPass.pipeline)
1363 continue;
1364
1365 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1366
1367 cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
1368
1369 QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
1370 cb->setShaderResources(srb);
1371
1372 if (needsSetViewport) {
1373 cb->setViewport(ps->viewport);
1374 needsSetViewport = false;
1375 }
1376
1377 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1378 int vertexBufferCount = 1;
1379 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1380 quint32 instances = 1;
1381 if (renderable->modelContext.model.instancing()) {
1382 instances = renderable->modelContext.model.instanceCount();
1383 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
1384 vertexBufferCount = 2;
1385 }
1386 if (indexBuffer) {
1387 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: renderable->subset.rhi.indexBuffer->indexFormat());
1388 cb->drawIndexed(indexCount: renderable->subset.count, instanceCount: instances, firstIndex: renderable->subset.offset);
1389 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.count, instances));
1390 } else {
1391 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
1392 cb->draw(vertexCount: renderable->subset.count, instanceCount: instances, firstVertex: renderable->subset.offset);
1393 QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
1394 }
1395 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
1396 QVector<int>({renderable->modelContext.model.profilingId,
1397 renderable->material.profilingId}));
1398 }
1399 }
1400 };
1401
1402 QRhi *rhi = rhiCtx->rhi();
1403 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1404
1405 // We need to deal with a clip depth range of [0, 1] or
1406 // [-1, 1], depending on the graphics API underneath.
1407 QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
1408 if (rhi->isClipDepthZeroToOne()) {
1409 // d is [0, 1] so no need for any mapping
1410 depthAdjust[0] = 0.0f;
1411 depthAdjust[1] = 1.0f;
1412 } else {
1413 // d is [-1, 1]
1414 depthAdjust[0] = 1.0f;
1415 depthAdjust[1] = 0.5f;
1416 }
1417
1418 QSSGDebugDrawSystem *debugDrawSystem = renderer.contextInterface()->debugDrawSystem().get();
1419 const bool drawDirectionalLightShadowBoxes = layerData.layer.drawDirectionalLightShadowBoxes;
1420 const bool drawPointLightShadowBoxes = layerData.layer.drawPointLightShadowBoxes;
1421 const bool drawShadowCastingBounds = layerData.layer.drawShadowCastingBounds;
1422 const bool drawShadowReceivingBounds = layerData.layer.drawShadowReceivingBounds;
1423 const bool drawCascades = layerData.layer.drawCascades;
1424 const bool drawSceneCascadeIntersection = layerData.layer.drawSceneCascadeIntersection;
1425 const bool disableShadowCameraUpdate = layerData.layer.disableShadowCameraUpdate;
1426
1427 if (drawShadowCastingBounds)
1428 ShadowmapHelpers::addDebugBox(boxUnsorted: castingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), color: QColorConstants::Red, debugDrawSystem);
1429 if (drawShadowReceivingBounds)
1430 ShadowmapHelpers::addDebugBox(boxUnsorted: receivingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), color: QColorConstants::Green, debugDrawSystem);
1431
1432 // Create shadow map for each light in the scene
1433 for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
1434 if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
1435 continue;
1436
1437 QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(lightIdx: i);
1438 if (!pEntry)
1439 continue;
1440
1441 const auto &light = globalLights[i].light;
1442 Q_ASSERT(pEntry->m_rhiDepthStencil[0]);
1443 if (pEntry->m_rhiDepthTextureArray) {
1444 const QSize size = pEntry->m_rhiDepthTextureArray->pixelSize();
1445 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1446
1447 Q_ASSERT(light->type == QSSGRenderLight::Type::DirectionalLight || light->type == QSSGRenderLight::Type::SpotLight);
1448
1449 // This is just a way to store the old camera so we can use it for debug
1450 // drawing. There are probably cleaner ways to do this
1451 if (!disableShadowCameraUpdate && debugCamera) {
1452 debugCamera->clipPlanes = camera.clipPlanes;
1453 debugCamera->projection = camera.projection;
1454 // NOTE: Since the debug camera is an internally injected camera, there will only be
1455 // the local transform. Anywhere the global transform is looked up for the debug camera
1456 // it will return the local transform.
1457 debugCamera->localTransform = layerData.getGlobalTransform(node: camera);
1458 }
1459
1460 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
1461 if (light->type == QSSGRenderLight::Type::DirectionalLight) {
1462 const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
1463 const float clipNear = camera.clipPlanes.clipNear();
1464 const float clipFar = qMin(a: light->m_shadowMapFar, b: camera.clipPlanes.clipFar());
1465 const float clipRange = clipFar - clipNear;
1466 cascades = setupCascadingCamerasForShadowMap(data: layerData,
1467 inCamera: disableShadowCameraUpdate ? *debugCamera : camera,
1468 inLight: light,
1469 shadowMapResolution: size.width(),
1470 pcfRadius,
1471 clipNear,
1472 clipFar,
1473 castingObjectsBox,
1474 receivingObjectsBox,
1475 lockShadowmapTexels: light->m_lockShadowmapTexels,
1476 debugDrawSystem,
1477 drawCascades,
1478 drawSceneCascadeIntersection);
1479
1480 // Write the split distances from value 0 in the z-axis of the eye view-space
1481 pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
1482 pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
1483 pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
1484 pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
1485 pEntry->m_shadowMapFar = clipFar;
1486 } else if (light->type == QSSGRenderLight::Type::SpotLight) {
1487 auto spotlightCamera = std::make_unique<QSSGRenderCamera>(args: QSSGRenderCamera::Type::PerspectiveCamera);
1488 spotlightCamera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(fov: light->m_coneAngle * 2.0f);
1489 spotlightCamera->clipPlanes = { 1.0f, light->m_shadowMapFar };
1490 const QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(node: *light);
1491 const QVector3D lightDir = QSSGRenderNode::getDirection(globalTransform: lightGlobalTransform);
1492 const QVector3D lightPos = QSSGRenderNode::getGlobalPos(globalTransform: lightGlobalTransform) - lightDir * spotlightCamera->clipPlanes.clipNear();
1493 const QVector3D lightPivot = light->pivot;
1494 const QVector3D forward = lightDir.normalized();
1495 const QVector3D right = qFuzzyCompare(p1: qAbs(t: forward.y()), p2: 1.0f)
1496 ? QVector3D::crossProduct(v1: forward, v2: QVector3D(1, 0, 0)).normalized()
1497 : QVector3D::crossProduct(v1: forward, v2: QVector3D(0, 1, 0)).normalized();
1498 const QVector3D up = QVector3D::crossProduct(v1: right, v2: forward).normalized();
1499 spotlightCamera->localTransform = QSSGRenderNode::calculateTransformMatrix(position: lightPos,
1500 scale: QSSGRenderNode::initScale,
1501 pivot: lightPivot,
1502 rotation: QQuaternion::fromDirection(direction: forward, up));
1503 QRectF theViewport(0.0f, 0.0f, (float)light->m_shadowMapRes, (float)light->m_shadowMapRes);
1504 QSSGRenderCamera::calculateProjectionInternal(camera&: *spotlightCamera, inViewport: theViewport);
1505 cascades.push_back(t: std::move(spotlightCamera));
1506 pEntry->m_shadowMapFar = light->m_shadowMapFar;
1507 } else {
1508 Q_UNREACHABLE();
1509 }
1510
1511 memset(s: pEntry->m_csmActive, c: 0, n: sizeof(pEntry->m_csmActive));
1512
1513 QMatrix4x4 cascadeCameraGlobalTransforms(Qt::Uninitialized);
1514 for (int cascadeIndex = 0; cascadeIndex < cascades.length(); cascadeIndex++) {
1515 const auto &cascadeCamera = cascades[cascadeIndex];
1516 if (!cascadeCamera)
1517 continue;
1518
1519 cascadeCameraGlobalTransforms = layerData.getGlobalTransform(node: *cascadeCamera);
1520 pEntry->m_csmActive[cascadeIndex] = 1.f;
1521 cascadeCamera->calculateViewProjectionMatrix(globalTransform: cascadeCameraGlobalTransforms, outMatrix&: pEntry->m_lightViewProjection[cascadeIndex]);
1522
1523 pEntry->m_lightView = cascadeCameraGlobalTransforms.inverted(); // pre-calculate this for the material
1524 const bool isOrtho = cascadeCamera->type == QSSGRenderGraphObject::Type::OrthographicCamera;
1525 rhiPrepareResourcesForShadowMap(rhiCtx, inData: layerData, passKey, pEntry, ps: &ps, depthAdjust: &depthAdjust, sortedOpaqueObjects, inCamera&: *cascadeCamera, orthographic: isOrtho, cubeFace: QSSGRenderTextureCubeFaceNone, cascadeIndex);
1526 // Render into the 2D texture pEntry->m_rhiDepthMap, using
1527 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1528 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[cascadeIndex];
1529 cb->beginPass(rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: rhiCtx->commonPassFlags());
1530 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1531 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1532 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0);
1533 cb->endPass();
1534 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1535
1536 if (drawDirectionalLightShadowBoxes) {
1537 QMatrix4x4 viewProjection(Qt::Uninitialized);
1538 cascadeCamera->calculateViewProjectionMatrix(globalTransform: cascadeCameraGlobalTransforms, outMatrix&: viewProjection);
1539 ShadowmapHelpers::addDirectionalLightDebugBox(box: computeFrustumBounds(projection: viewProjection), debugDrawSystem);
1540 }
1541 }
1542 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
1543 } else {
1544 Q_ASSERT(pEntry->m_rhiDepthCube);
1545 const QSize size = pEntry->m_rhiDepthCube->pixelSize();
1546 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1547
1548 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1549 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1550 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1551 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1552 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1553 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1554 const float shadowMapFar = qMax<float>(a: 2.0f, b: light->m_shadowMapFar);
1555 setupCubeShadowCameras(inData: layerData, inLight: light, shadowMapFar, inCameras: theCameras);
1556 pEntry->m_lightView = QMatrix4x4();
1557 pEntry->m_shadowMapFar = shadowMapFar;
1558
1559 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1560 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1561 for (const auto face : QSSGRenderTextureCubeFaces) {
1562 cameraGlobalTransform = layerData.getGlobalTransform(node: theCameras[quint8(face)]);
1563 theCameras[quint8(face)].calculateViewProjectionMatrix(globalTransform: cameraGlobalTransform, outMatrix&: pEntry->m_lightViewProjection[0]);
1564 pEntry->m_lightCubeView[quint8(face)] = cameraGlobalTransform.inverted(); // pre-calculate this for the material
1565
1566 rhiPrepareResourcesForShadowMap(rhiCtx,
1567 inData: layerData,
1568 passKey,
1569 pEntry,
1570 ps: &ps,
1571 depthAdjust: &depthAdjust,
1572 sortedOpaqueObjects,
1573 inCamera&: theCameras[quint8(face)],
1574 orthographic: false,
1575 cubeFace: face,
1576 cascadeIndex: 0);
1577 }
1578
1579 for (const auto face : QSSGRenderTextureCubeFaces) {
1580 // Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
1581 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1582
1583 QSSGRenderTextureCubeFace outFace = face;
1584 // FACE S T GL
1585 // +x -z, -y right
1586 // -x +z, -y left
1587 // +y +x, +z top
1588 // -y +x, -z bottom
1589 // +z +x, -y front
1590 // -z -x, -y back
1591 // FACE S T D3D
1592 // +x -z, +y right
1593 // -x +z, +y left
1594 // +y +x, -z bottom
1595 // -y +x, +z top
1596 // +z +x, +y front
1597 // -z -x, +y back
1598 if (swapYFaces) {
1599 // +Y and -Y faces get swapped (D3D, Vulkan, Metal).
1600 // See shadowMapping.glsllib. This is complemented there by reversing T as well.
1601 if (outFace == QSSGRenderTextureCubeFace::PosY)
1602 outFace = QSSGRenderTextureCubeFace::NegY;
1603 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1604 outFace = QSSGRenderTextureCubeFace::PosY;
1605 }
1606 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1607 cb->beginPass(rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: rhiCtx->commonPassFlags());
1608 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1609 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1610 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face));
1611 cb->endPass();
1612 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1613 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
1614 }
1615
1616 if (drawPointLightShadowBoxes) {
1617 QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(node: *light);
1618 ShadowmapHelpers::addPointLightDebugBox(lightPos: QSSGRenderNode::getGlobalPos(globalTransform: lightGlobalTransform), shadowMapFar, debugDrawSystem);
1619 }
1620 }
1621 }
1622}
1623
1624void RenderHelpers::rhiRenderReflectionMap(QSSGRhiContext *rhiCtx,
1625 QSSGPassKey passKey,
1626 const QSSGLayerRenderData &inData,
1627 QSSGRhiGraphicsPipelineState *ps,
1628 QSSGRenderReflectionMap &reflectionMapManager,
1629 const QVector<QSSGRenderReflectionProbe *> &reflectionProbes,
1630 const QSSGRenderableObjectList &reflectionPassObjects,
1631 QSSGRenderer &renderer)
1632{
1633 QRhi *rhi = rhiCtx->rhi();
1634 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1635
1636 const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox ||
1637 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1638 && rhiCtx->rhi()->isFeatureSupported(feature: QRhi::TexelFetch);
1639
1640 for (int i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
1641 QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(probeIdx: i);
1642 if (!pEntry)
1643 continue;
1644
1645 if (!pEntry->m_needsRender)
1646 continue;
1647
1648 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
1649 continue;
1650
1651 if (reflectionProbes[i]->texture)
1652 continue;
1653
1654 Q_ASSERT(pEntry->m_rhiDepthStencil);
1655 Q_ASSERT(pEntry->m_rhiCube);
1656
1657 const QSize size = pEntry->m_rhiCube->pixelSize();
1658 ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1659
1660 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1661 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1662 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1663 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1664 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1665 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1666 setupCubeReflectionCameras(inData, inProbe: reflectionProbes[i], inCameras: theCameras);
1667 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1668 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1669 for (const auto face : QSSGRenderTextureCubeFaces) {
1670 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
1671 cameraGlobalTransform = inData.getGlobalTransform(node: theCameras[cubeFaceIdx]);
1672 theCameras[cubeFaceIdx].calculateViewProjectionMatrix(globalTransform: cameraGlobalTransform, outMatrix&: pEntry->m_viewProjection);
1673
1674 rhiPrepareResourcesForReflectionMap(rhiCtx, passKey, inData, pEntry, ps,
1675 sortedOpaqueObjects: reflectionPassObjects, inCamera&: theCameras[cubeFaceIdx], renderer, cubeFace: face);
1676 }
1677 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
1678 for (auto face : QSSGRenderTextureCubeFaces) {
1679 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1680 face = pEntry->m_timeSliceFace;
1681
1682 QSSGRenderTextureCubeFace outFace = face;
1683 // Faces are swapped similarly to shadow maps due to differences in backends
1684 // Prefilter step handles correcting orientation differences in the final render
1685 if (swapYFaces) {
1686 if (outFace == QSSGRenderTextureCubeFace::PosY)
1687 outFace = QSSGRenderTextureCubeFace::NegY;
1688 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1689 outFace = QSSGRenderTextureCubeFace::PosY;
1690 }
1691 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1692 cb->beginPass(rt, colorClearValue: reflectionProbes[i]->clearColor, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: rhiCtx->commonPassFlags());
1693 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1694 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1695
1696 if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
1697 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1698 const bool isSkyBox = inData.layer.background == QSSGRenderLayer::Background::SkyBox;
1699 const auto &shaderPipeline = isSkyBox ? shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(tonemapMode: QSSGRenderLayer::TonemapMode::None, isRGBE: inData.layer.skyBoxIsRgbe8, viewCount: 1)
1700 : shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(viewCount: 1);
1701 Q_ASSERT(shaderPipeline);
1702 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps&: *ps, pipeline: shaderPipeline.get());
1703 QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
1704 if (!renderPassDesc)
1705 renderPassDesc = rt->newCompatibleRenderPassDescriptor();
1706 rt->setRenderPassDescriptor(renderPassDesc);
1707 isSkyBox ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, rpDesc: renderPassDesc, flags: {})
1708 : renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, rpDesc: renderPassDesc, flags: {});
1709 }
1710
1711 bool needsSetViewport = true;
1712 for (const auto &handle : reflectionPassObjects)
1713 rhiRenderRenderable(rhiCtx, state: *ps, object&: *handle.obj, needsSetViewport: &needsSetViewport, cubeFace: face);
1714
1715 cb->endPass();
1716 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1717 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
1718
1719 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1720 break;
1721 }
1722 if (renderPassDesc)
1723 renderPassDesc->deleteLater();
1724
1725 pEntry->renderMips(rhiCtx);
1726
1727 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1728 pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(face: pEntry->m_timeSliceFace); // Wraps
1729
1730 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
1731 pEntry->m_rendered = true;
1732
1733 reflectionProbes[i]->hasScheduledUpdate = false;
1734 pEntry->m_needsRender = false;
1735 }
1736}
1737
1738bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx,
1739 const QSize &size,
1740 QSSGRhiRenderableTexture *renderableTex,
1741 quint8 viewCount)
1742{
1743 QRhi *rhi = rhiCtx->rhi();
1744 bool needsBuild = false;
1745
1746 if (!renderableTex->texture) {
1747 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1748 // the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
1749 if (viewCount <= 1)
1750 renderableTex->texture = rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: size, sampleCount: 1, flags);
1751 else
1752 renderableTex->texture = rhi->newTextureArray(format: QRhiTexture::RGBA8, arraySize: viewCount, pixelSize: size, sampleCount: 1, flags);
1753 needsBuild = true;
1754 } else if (renderableTex->texture->pixelSize() != size) {
1755 renderableTex->texture->setPixelSize(size);
1756 needsBuild = true;
1757 }
1758
1759 if (needsBuild) {
1760 if (!renderableTex->texture->create()) {
1761 qWarning(msg: "Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
1762 renderableTex->reset();
1763 return false;
1764 }
1765 renderableTex->resetRenderTarget();
1766 QRhiTextureRenderTargetDescription desc;
1767 QRhiColorAttachment colorAttachment(renderableTex->texture);
1768 colorAttachment.setMultiViewCount(viewCount);
1769 desc.setColorAttachments({ colorAttachment });
1770 renderableTex->rt = rhi->newTextureRenderTarget(desc);
1771 renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
1772 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
1773 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
1774 if (!renderableTex->rt->create()) {
1775 qWarning(msg: "Failed to build render target for ambient occlusion texture");
1776 renderableTex->reset();
1777 return false;
1778 }
1779 }
1780
1781 return true;
1782}
1783
1784void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
1785 QSSGPassKey passKey,
1786 QSSGRenderer &renderer,
1787 QSSGRhiShaderPipeline &shaderPipeline,
1788 QSSGRhiGraphicsPipelineState &ps,
1789 const QSSGAmbientOcclusionSettings &ao,
1790 const QSSGRhiRenderableTexture &rhiAoTexture,
1791 const QSSGRhiRenderableTexture &rhiDepthTexture,
1792 const QSSGRenderCamera &camera)
1793{
1794 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1795
1796 // no texelFetch in GLSL <= 120 and GLSL ES 100
1797 if (!rhiCtx->rhi()->isFeatureSupported(feature: QRhi::TexelFetch)) {
1798 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1799 // just clear and stop there
1800 cb->beginPass(rt: rhiAoTexture.rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 });
1801 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
1802 cb->endPass();
1803 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1804 return;
1805 }
1806
1807 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: &shaderPipeline);
1808
1809 const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
1810 const QSize textureSize = rhiAoTexture.texture->pixelSize();
1811 const float rw = float(textureSize.width());
1812 const float rh = float(textureSize.height());
1813 const float fov = camera.fov.asVerticalFov(aspectRatio: rw / rh).radians();
1814 const float tanHalfFovY = tanf(x: 0.5f * fov * (rh / rw));
1815 const float invFocalLenX = tanHalfFovY * (rw / rh);
1816
1817 const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
1818 const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
1819 const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
1820 const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
1821 const QVector2D cameraProps = camera.clipPlanes;
1822
1823 // layout(std140, binding = 0) uniform buf {
1824 // vec4 aoProperties;
1825 // vec4 aoProperties2;
1826 // vec4 aoScreenConst;
1827 // vec4 uvToEyeConst;
1828 // vec2 cameraProperties;
1829
1830 const int UBUF_SIZE = 72;
1831 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: nullptr, .entryIdx: 0 }));
1832 if (!dcd.ubuf) {
1833 dcd.ubuf = rhiCtx->rhi()->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: UBUF_SIZE);
1834 dcd.ubuf->create();
1835 }
1836
1837 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1838 memcpy(dest: ubufData, src: &aoProps, n: 16);
1839 memcpy(dest: ubufData + 16, src: &aoProps2, n: 16);
1840 memcpy(dest: ubufData + 32, src: &aoScreenConst, n: 16);
1841 memcpy(dest: ubufData + 48, src: &uvToEyeConst, n: 16);
1842 memcpy(dest: ubufData + 64, src: &cameraProps, n: 8);
1843 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1844
1845 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
1846 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1847 QSSGRhiShaderResourceBindingList bindings;
1848 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
1849 // binding 1 is either a sampler2D or sampler2DArray, matching
1850 // rhiDepthTexture.texture, no special casing needed here
1851 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: rhiDepthTexture.texture, sampler);
1852 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1853
1854 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
1855 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: rhiAoTexture.rt, flags: {});
1856}
1857
1858bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx,
1859 const QSize &size,
1860 bool wantsMips,
1861 QSSGRhiRenderableTexture *renderableTex,
1862 quint8 viewCount)
1863{
1864 QRhi *rhi = rhiCtx->rhi();
1865 bool needsBuild = false;
1866 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1867 if (wantsMips)
1868 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
1869
1870 if (!renderableTex->texture) {
1871 // always non-msaa, even if multisampling is used in the main pass
1872 if (viewCount <= 1)
1873 renderableTex->texture = rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: size, sampleCount: 1, flags);
1874 else
1875 renderableTex->texture = rhi->newTextureArray(format: QRhiTexture::RGBA8, arraySize: viewCount, pixelSize: size, sampleCount: 1, flags);
1876 needsBuild = true;
1877 } else if (renderableTex->texture->pixelSize() != size) {
1878 renderableTex->texture->setPixelSize(size);
1879 needsBuild = true;
1880 }
1881
1882 if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
1883 if (viewCount <= 1)
1884 renderableTex->depthStencil = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: size);
1885 else
1886 renderableTex->depthTexture = rhi->newTextureArray(format: QRhiTexture::D24S8, arraySize: viewCount, pixelSize: size, sampleCount: 1, flags: QRhiTexture::RenderTarget);
1887 needsBuild = true;
1888 } else {
1889 if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
1890 renderableTex->depthStencil->setPixelSize(size);
1891 needsBuild = true;
1892 } else if (renderableTex->depthTexture && renderableTex->depthTexture->pixelSize() != size) {
1893 renderableTex->depthTexture->setPixelSize(size);
1894 needsBuild = true;
1895 }
1896 }
1897
1898 if (needsBuild) {
1899 if (!renderableTex->texture->create()) {
1900 qWarning(msg: "Failed to build screen texture (size %dx%d)", size.width(), size.height());
1901 renderableTex->reset();
1902 return false;
1903 }
1904 if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
1905 qWarning(msg: "Failed to build depth-stencil buffer for screen texture (size %dx%d)",
1906 size.width(), size.height());
1907 renderableTex->reset();
1908 return false;
1909 } else if (renderableTex->depthTexture && !renderableTex->depthTexture->create()) {
1910 qWarning(msg: "Failed to build depth-stencil texture array (multiview) for screen texture (size %dx%d)",
1911 size.width(), size.height());
1912 renderableTex->reset();
1913 return false;
1914 }
1915 renderableTex->resetRenderTarget();
1916 QRhiTextureRenderTargetDescription desc;
1917 QRhiColorAttachment colorAttachment(renderableTex->texture);
1918 colorAttachment.setMultiViewCount(viewCount);
1919 desc.setColorAttachments({ colorAttachment });
1920 if (renderableTex->depthStencil)
1921 desc.setDepthStencilBuffer(renderableTex->depthStencil);
1922 else if (renderableTex->depthTexture)
1923 desc.setDepthTexture(renderableTex->depthTexture);
1924 renderableTex->rt = rhi->newTextureRenderTarget(desc);
1925 renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
1926 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
1927 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
1928 if (!renderableTex->rt->create()) {
1929 qWarning(msg: "Failed to build render target for screen texture");
1930 renderableTex->reset();
1931 return false;
1932 }
1933 }
1934
1935 return true;
1936}
1937
1938void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
1939{
1940 QSSG_ASSERT(layer.renderData, return);
1941
1942 const auto *renderData = layer.renderData;
1943
1944 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1945 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1946 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
1947
1948 QSSGRhiShaderResourceBindingList bindings;
1949
1950 int uniformBinding = 0;
1951 const int ubufSize = cameras.count() >= 2 ? 276 : 148;
1952
1953 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: nullptr, .entryIdx: 0 })); // Change to Grid?
1954
1955 QRhi *rhi = rhiCtx->rhi();
1956 if (!dcd.ubuf) {
1957 dcd.ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
1958 dcd.ubuf->create();
1959 }
1960
1961 // Param
1962 const auto clipPlanes = cameras[0]->clipPlanes;
1963 const float scale = layer.gridScale;
1964 const quint32 gridFlags = layer.gridFlags;
1965
1966 const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
1967
1968 quint32 ubufOffset = 0;
1969 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1970
1971 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1972 QMatrix4x4 viewProj(Qt::Uninitialized);
1973 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
1974 cameraGlobalTransform = renderData->getGlobalTransform(node: *cameras[viewIdx]);
1975 cameras[viewIdx]->calculateViewProjectionMatrix(globalTransform: cameraGlobalTransform, outMatrix&: viewProj);
1976 QMatrix4x4 invViewProj = viewProj.inverted();
1977 quint32 viewDataOffset = ubufOffset;
1978 memcpy(dest: ubufData + viewDataOffset + viewIdx * 64, src: viewProj.constData(), n: 64);
1979 viewDataOffset += 64 * cameras.count();
1980 memcpy(dest: ubufData + viewDataOffset + viewIdx * 64, src: invViewProj.constData(), n: 64);
1981 }
1982 ubufOffset += (64 + 64) * cameras.count();
1983
1984 memcpy(dest: ubufData + ubufOffset, src: &clipPlanes, n: 8);
1985 ubufOffset += 8;
1986 memcpy(dest: ubufData + ubufOffset, src: &scale, n: 4);
1987 ubufOffset += 4;
1988 memcpy(dest: ubufData + ubufOffset, src: &yFactor, n: 4);
1989 ubufOffset += 4;
1990 memcpy(dest: ubufData + ubufOffset, src: &gridFlags, n: 4);
1991
1992 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1993
1994 bindings.addUniformBuffer(binding: uniformBinding, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
1995
1996 layer.gridSrb = rhiCtxD->srb(bindings);
1997 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
1998
1999 cb->debugMarkEnd();
2000}
2001
2002static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx,
2003 QSSGPassKey passKey,
2004 QSSGRenderLayer &layer,
2005 QSSGRenderCameraList &cameras,
2006 QSSGRenderer &renderer,
2007 QSSGReflectionMapEntry *entry = nullptr,
2008 QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone)
2009{
2010 QSSG_ASSERT(layer.renderData, return);
2011
2012 const auto *renderData = layer.renderData;
2013
2014 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
2015 const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
2016 const QSSGRenderImageTexture lightProbeTexture =
2017 cubeMapMode ? renderer.contextInterface()->bufferManager()->loadRenderImage(image: layer.skyBoxCubeMap, inMipMode: QSSGBufferManager::MipModeDisable)
2018 : renderer.contextInterface()->bufferManager()->loadRenderImage(image: layer.lightProbe, inMipMode: QSSGBufferManager::MipModeBsdf);
2019 const bool hasValidTexture = lightProbeTexture.m_texture != nullptr;
2020 if (hasValidTexture) {
2021 if (cubeFace == QSSGRenderTextureCubeFaceNone)
2022 layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
2023
2024 QSSGRhiShaderResourceBindingList bindings;
2025
2026 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear,
2027 .magFilter: QRhiSampler::Linear,
2028 .mipmap: cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
2029 .hTiling: QRhiSampler::Repeat,
2030 .vTiling: QRhiSampler::ClampToEdge,
2031 .zTiling: QRhiSampler::Repeat });
2032 int samplerBinding = 1; //the shader code is hand-written, so we don't need to look that up
2033 const quint32 ubufSize = cameras.count() >= 2 ? 416 : 240; // same ubuf layout for both skybox and skyboxcube
2034 bindings.addTexture(binding: samplerBinding,
2035 stage: QRhiShaderResourceBinding::FragmentStage,
2036 tex: lightProbeTexture.m_texture, sampler);
2037
2038 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
2039 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
2040 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: entry, .entryIdx: entryIdx });
2041
2042 QRhi *rhi = rhiCtx->rhi();
2043 if (!dcd.ubuf) {
2044 dcd.ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
2045 dcd.ubuf->create();
2046 }
2047
2048 float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2049 const float exposure = layer.lightProbeSettings.probeExposure;
2050 // orientation
2051 const QMatrix3x3 &rotationMatrix(layer.lightProbeSettings.probeOrientation);
2052 const float blurAmount = layer.skyboxBlurAmount;
2053 const float maxMipLevel = float(lightProbeTexture.m_mipmapCount - 2);
2054
2055 const QVector4D skyboxProperties = {
2056 adjustY,
2057 exposure,
2058 blurAmount,
2059 maxMipLevel
2060 };
2061
2062 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2063 quint32 ubufOffset = 0;
2064 // skyboxProperties
2065 memcpy(dest: ubufData + ubufOffset, src: &skyboxProperties, n: 16);
2066 ubufOffset += 16;
2067 // orientation
2068 memcpy(dest: ubufData + ubufOffset, src: rotationMatrix.constData(), n: 12);
2069 ubufOffset += 16;
2070 memcpy(dest: ubufData + ubufOffset, src: (char *)rotationMatrix.constData() + 12, n: 12);
2071 ubufOffset += 16;
2072 memcpy(dest: ubufData + ubufOffset, src: (char *)rotationMatrix.constData() + 24, n: 12);
2073 ubufOffset += 16;
2074
2075 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2076 const QMatrix4x4 &inverseProjection = cameras[viewIdx]->projection.inverted();
2077 const QMatrix4x4 &viewMatrix = renderData->getGlobalTransform(node: *cameras[viewIdx]);
2078 QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
2079 cameras[viewIdx]->calculateViewProjectionWithoutTranslation(globalTransform: viewMatrix, near: 0.1f, far: 5.0f, outMatrix&: viewProjection);
2080
2081 quint32 viewDataOffset = ubufOffset;
2082 memcpy(dest: ubufData + viewDataOffset + viewIdx * 64, src: viewProjection.constData(), n: 64);
2083 viewDataOffset += cameras.count() * 64;
2084 memcpy(dest: ubufData + viewDataOffset + viewIdx * 64, src: inverseProjection.constData(), n: 64);
2085 viewDataOffset += cameras.count() * 64;
2086 memcpy(dest: ubufData + viewDataOffset + viewIdx * 48, src: viewMatrix.constData(), n: 48);
2087 }
2088 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2089
2090 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
2091
2092 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
2093 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
2094 entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtxD->srb(bindings);
2095 } else {
2096 layer.skyBoxSrb = rhiCtxD->srb(bindings);
2097 }
2098
2099 if (cubeMapMode)
2100 renderer.rhiCubeRenderer()->prepareCube(rhiCtx, maybeRub: nullptr);
2101 else
2102 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
2103 }
2104}
2105
2106void RenderHelpers::rhiPrepareSkyBox(QSSGRhiContext *rhiCtx,
2107 QSSGPassKey passKey,
2108 QSSGRenderLayer &layer,
2109 QSSGRenderCameraList &cameras,
2110 QSSGRenderer &renderer)
2111{
2112 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2113 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
2114
2115 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer);
2116
2117 cb->debugMarkEnd();
2118}
2119
2120void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(QSSGRhiContext *rhiCtx,
2121 QSSGPassKey passKey,
2122 QSSGRenderLayer &layer,
2123 QSSGRenderCamera &inCamera,
2124 QSSGRenderer &renderer,
2125 QSSGReflectionMapEntry *entry,
2126 QSSGRenderTextureCubeFace cubeFace)
2127{
2128 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2129 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
2130
2131 QSSGRenderCameraList cameras({ &inCamera });
2132 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, entry, cubeFace);
2133
2134 cb->debugMarkEnd();
2135}
2136
2137bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
2138 QSSGPassKey passKey,
2139 const QSSGRhiGraphicsPipelineState &basePipelineState,
2140 QRhiRenderPassDescriptor *rpDesc,
2141 QSSGLayerRenderData &inData,
2142 const QSSGRenderableObjectList &sortedOpaqueObjects,
2143 const QSSGRenderableObjectList &sortedTransparentObjects,
2144 int samples,
2145 int viewCount)
2146{
2147 static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
2148 QSSGPassKey passKey,
2149 QSSGLayerRenderData &inData,
2150 QSSGRenderableObject *obj,
2151 QRhiRenderPassDescriptor *rpDesc,
2152 QSSGRhiGraphicsPipelineState *ps) {
2153 QSSGRhiShaderPipelinePtr shaderPipeline;
2154 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
2155
2156 const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
2157 QSSGShaderFeatures featureSet;
2158 featureSet.set(feature: QSSGShaderFeatures::Feature::DepthPass, val: true);
2159 if (isOpaqueDepthPrePass)
2160 featureSet.set(feature: QSSGShaderFeatures::Feature::OpaqueDepthPrePass, val: true);
2161
2162 QSSGRhiDrawCallData *dcd = nullptr;
2163 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2164 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2165 const void *modelNode = &subsetRenderable.modelContext.model;
2166 dcd = &rhiCtxD->drawCallData(key: { .cid: passKey, .model: modelNode, .entry: &subsetRenderable.material, .entryIdx: 0 });
2167 }
2168
2169 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2170 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2171 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2172 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: material.cullMode);
2173
2174 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
2175 if (shaderPipeline) {
2176 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd->ubuf);
2177 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2178 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras: inData.renderedCameras, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
2179 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2180 } else {
2181 return false;
2182 }
2183 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2184 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2185
2186 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2187
2188 ps->cullMode = QSSGRhiHelpers::toCullMode(cullFaceMode: customMaterial.m_cullMode);
2189
2190 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2191 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material: customMaterial, renderable&: subsetRenderable, defaultMaterialShaderKeyProperties: inData.getDefaultMaterialPropertyTable(), featureSet);
2192
2193 if (shaderPipeline) {
2194 shaderPipeline->ensureCombinedUniformBuffer(ubuf: &dcd->ubuf);
2195 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2196 customMaterialSystem.updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, material: customMaterial, renderable&: subsetRenderable,
2197 cameras: inData.renderedCameras, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
2198 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2199 } else {
2200 return false;
2201 }
2202 }
2203
2204 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2205 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2206 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2207 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps&: *ps);
2208 ia = subsetRenderable.subset.rhi.ia;
2209
2210 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2211 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection: cameraDatas[0].direction, cameraPosition: cameraDatas[0].position);
2212 QSSGRhiHelpers::bakeVertexInputLocations(ia: &ia, shaders: *shaderPipeline, instanceBufferBinding);
2213
2214 QSSGRhiShaderResourceBindingList bindings;
2215 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd->ubuf);
2216
2217 // Depth and SSAO textures, in case a custom material's shader code does something with them.
2218 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
2219
2220 if (isOpaqueDepthPrePass) {
2221 addOpaqueDepthPrePassBindings(rhiCtx,
2222 shaderPipeline: shaderPipeline.get(),
2223 renderableImage: subsetRenderable.firstImage,
2224 bindings,
2225 isCustomMaterialMeshSubset: (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
2226 }
2227
2228 // Skinning
2229 if (QRhiTexture *boneTexture = inData.getBonemapTexture(modelContext: subsetRenderable.modelContext)) {
2230 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
2231 if (binding >= 0) {
2232 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
2233 .magFilter: QRhiSampler::Nearest,
2234 .mipmap: QRhiSampler::None,
2235 .hTiling: QRhiSampler::ClampToEdge,
2236 .vTiling: QRhiSampler::ClampToEdge,
2237 .zTiling: QRhiSampler::Repeat
2238 });
2239 bindings.addTexture(binding,
2240 stage: QRhiShaderResourceBinding::VertexStage,
2241 tex: boneTexture,
2242 sampler: boneSampler);
2243 }
2244 }
2245
2246 // Morphing
2247 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2248 if (targetsTexture) {
2249 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
2250 if (binding >= 0) {
2251 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
2252 .magFilter: QRhiSampler::Nearest,
2253 .mipmap: QRhiSampler::None,
2254 .hTiling: QRhiSampler::ClampToEdge,
2255 .vTiling: QRhiSampler::ClampToEdge,
2256 .zTiling: QRhiSampler::ClampToEdge
2257 });
2258 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: subsetRenderable.subset.rhi.targetsTexture, sampler: targetsSampler);
2259 }
2260 }
2261
2262 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2263
2264 subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtxD->pipeline(ps: *ps,
2265 rpDesc,
2266 srb);
2267 subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
2268 }
2269
2270 return true;
2271 };
2272
2273 // Phase 1 (prepare) for the Z prepass or the depth texture generation.
2274 // These renders opaque (Z prepass), or opaque and transparent (depth
2275 // texture), objects with depth test/write enabled, and color write
2276 // disabled, using a very simple set of shaders.
2277
2278 QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
2279 // We took a copy of the pipeline state since we do not want to conflict
2280 // with what rhiPrepare() collects for its own use. So here just change
2281 // whatever we need.
2282
2283 ps.samples = samples;
2284 ps.viewCount = viewCount;
2285 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
2286 ps.targetBlend[0].colorWrite = {};
2287
2288 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2289 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2290 return false;
2291 }
2292
2293 for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
2294 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2295 return false;
2296 }
2297
2298 return true;
2299}
2300
2301void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
2302 const QSSGRhiGraphicsPipelineState &pipelineState,
2303 const QSSGRenderableObjectList &sortedOpaqueObjects,
2304 const QSSGRenderableObjectList &sortedTransparentObjects,
2305 bool *needsSetViewport)
2306{
2307 static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
2308 const QSSGRhiGraphicsPipelineState &pipelineState,
2309 const QSSGRenderableObjectList &objects,
2310 bool *needsSetViewport) {
2311 for (const auto &oh : objects) {
2312 QSSGRenderableObject *obj = oh.obj;
2313
2314 // casts to SubsetRenderableBase so it works for both default and custom materials
2315 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2316 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2317 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2318
2319 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2320 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2321 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2322 : nullptr;
2323
2324 QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
2325 if (!ps)
2326 return;
2327
2328 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
2329 if (!srb)
2330 return;
2331
2332 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2333 cb->setGraphicsPipeline(ps);
2334 cb->setShaderResources(srb);
2335
2336 if (*needsSetViewport) {
2337 cb->setViewport(pipelineState.viewport);
2338 *needsSetViewport = false;
2339 }
2340
2341 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2342 int vertexBufferCount = 1;
2343 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2344 quint32 instances = 1;
2345 if (subsetRenderable->modelContext.model.instancing()) {
2346 instances = subsetRenderable->modelContext.model.instanceCount();
2347 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2348 vertexBufferCount = 2;
2349 }
2350
2351 if (indexBuffer) {
2352 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2353 cb->drawIndexed(indexCount: subsetRenderable->subset.count, instanceCount: instances, firstIndex: subsetRenderable->subset.offset);
2354 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.count, instances));
2355 } else {
2356 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
2357 cb->draw(vertexCount: subsetRenderable->subset.count, instanceCount: instances, firstVertex: subsetRenderable->subset.offset);
2358 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2359 }
2360 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2361 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2362 subsetRenderable->material.profilingId}));
2363 }
2364 }
2365 };
2366
2367 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
2368 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
2369}
2370
2371bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx,
2372 const QSize &size,
2373 QSSGRhiRenderableTexture *renderableTex,
2374 quint8 viewCount,
2375 int samples)
2376{
2377 QRhi *rhi = rhiCtx->rhi();
2378 bool needsBuild = false;
2379
2380 if (!renderableTex->texture) {
2381 QRhiTexture::Format format = QRhiTexture::D32F;
2382 if (!rhi->isTextureFormatSupported(format))
2383 format = QRhiTexture::D16;
2384 if (!rhi->isTextureFormatSupported(format))
2385 qWarning(msg: "Depth texture not supported");
2386 if (viewCount <= 1)
2387 renderableTex->texture = rhiCtx->rhi()->newTexture(format, pixelSize: size, sampleCount: samples, flags: QRhiTexture::RenderTarget);
2388 else
2389 renderableTex->texture = rhiCtx->rhi()->newTextureArray(format, arraySize: viewCount, pixelSize: size, sampleCount: 1, flags: QRhiTexture::RenderTarget);
2390 needsBuild = true;
2391 } else if (renderableTex->texture->pixelSize() != size) {
2392 renderableTex->texture->setPixelSize(size);
2393 needsBuild = true;
2394 }
2395
2396 if (needsBuild) {
2397 if (!renderableTex->texture->create()) {
2398 qWarning(msg: "Failed to build depth texture (size %dx%d, format %d)",
2399 size.width(), size.height(), int(renderableTex->texture->format()));
2400 renderableTex->reset();
2401 return false;
2402 }
2403 renderableTex->resetRenderTarget();
2404 QRhiTextureRenderTargetDescription rtDesc;
2405 rtDesc.setDepthTexture(renderableTex->texture);
2406 renderableTex->rt = rhi->newTextureRenderTarget(desc: rtDesc);
2407 renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
2408 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2409 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2410 if (!renderableTex->rt->create()) {
2411 qWarning(msg: "Failed to build render target for depth texture");
2412 renderableTex->reset();
2413 return false;
2414 }
2415 }
2416
2417 return true;
2418}
2419
2420QT_END_NAMESPACE
2421

source code of qtquick3d/src/runtimerender/rendererimpl/qssgrenderhelpers.cpp