Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrenderhelpers.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
6
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
25
26static constexpr float QSSG_PI = float(M_PI);
27static constexpr float QSSG_HALFPI = float(M_PI_2);
28
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, subsetRenderable, featureSet);
38 if (shaderPipeline)
39 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, 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, ps->viewCount, method);
51 if (shaderPipeline)
52 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, 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(subsetRenderable.modelContext);
74
75 const auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(modelNode);
76 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : inData.getGlobalTransform(modelNode));
77
78 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*renderer->contextInterface(),
79 shaderPipeline,
80 ubufData,
81 ps,
82 subsetRenderable.material,
83 subsetRenderable.shaderDescription,
84 inData.getDefaultMaterialPropertyTable(),
85 cameras,
86 alteredModelViewProjection ? alteredMvpList : subsetRenderable.modelContext.modelViewProjections,
87 subsetRenderable.modelContext.normalMatrix,
88 modelMatrix,
89 clipSpaceCorrMatrix,
90 localInstanceTransform,
91 globalInstanceTransform,
92 toDataView(modelNode.morphWeights),
93 subsetRenderable.firstImage,
94 subsetRenderable.opacity,
95 inData,
96 subsetRenderable.lights,
97 subsetRenderable.reflectionProbe,
98 subsetRenderable.renderableFlags.receivesShadows(),
99 subsetRenderable.renderableFlags.receivesReflections(),
100 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(obj.globalBoundsInstancing);
119 else
120 boundsCasting.include(obj.globalBounds);
121 }
122 if (obj.renderableFlags.receivesShadows()) {
123 if (!obj.globalBoundsInstancing.isEmpty())
124 boundsReceiving.include(obj.globalBoundsInstancing);
125 else
126 boundsReceiving.include(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);
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(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)), inv.map(QVector3D(+1, +1, -1)),
148 inv.map(QVector3D(-1, +1, -1)), inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
149 inv.map(QVector3D(+1, +1, +1)), inv.map(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(points[i]);
192 }
193 return result;
194 };
195
196 QSSGBoxPoints frustumPointsSliced = sliceFrustum(frustumPoints, frustumStartT, frustumEndT);
197 if (drawCascades)
198 ShadowmapHelpers::addDebugFrustum(frustumPointsSliced, QColorConstants::Black, debugDrawSystem);
199
200 QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPointsSliced,
201 receivingBox.toQSSGBoxPoints(),
202 drawSceneCascadeIntersection ? debugDrawSystem : nullptr,
203 QColorConstants::DarkGray);
204 if (receivingSliced.isEmpty())
205 return nullptr;
206
207 QSSGBounds3 receivingFrustumSlicedLightSpace;
208 for (const QVector3D &point : receivingSliced)
209 receivingFrustumSlicedLightSpace.include(lightMatrix.map(point));
210
211 // Slice casting box by frustumBounds' left, right, up, down planes
212 QList<QVector3D> castingPointsLightSpace = ShadowmapHelpers::intersectBoxByBox(receivingFrustumSlicedLightSpace,
213 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(p);
221 }
222
223 for (const QVector3D &p : receivingFrustumSlicedLightSpace.toQSSGBoxPointsNoEmptyCheck()) {
224 float zMax = qMax(p.z(), castReceiveBounds.maximum.z());
225 float zMin = qMin(p.z(), castReceiveBounds.minimum.z());
226 castReceiveBounds.maximum.setZ(zMax);
227 castReceiveBounds.minimum.setZ(zMin);
228 }
229
230 // Expand to fit pcf radius
231 castReceiveBounds.fatten(pcfRadius);
232
233 QVector3D boundsCenterWorld = lightMatrixInverted.map(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(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(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>(QSSGRenderGraphObject::Type::OrthographicCamera);
259 camera->clipPlanes = QSSGRenderCamera::ClipPlanes{-0.5f * boundsDims.z(), 0.5f * boundsDims.z()};
260 camera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
261 camera->parent = nullptr;
262 camera->localTransform = QSSGRenderNode::calculateTransformMatrix(boundsCenterWorld,
263 QSSGRenderNode::initScale,
264 lightPivot,
265 QQuaternion::fromDirection(lightForward, lightUp));
266 QSSGRenderCamera::calculateProjectionInternal(*camera, theViewport);
267
268 return camera;
269}
270
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(clipNear, clipFar))
289 return result;
290
291 const QMatrix4x4 lightGlobalTransform = data.getGlobalTransform(*inLight);
292 const QVector3D lightDir = inLight->getDirection(lightGlobalTransform);
293 const QVector3D lightPivot = inLight->pivot;
294
295 const QVector3D forward = lightDir.normalized();
296 const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
297 ? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
298 : QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
299 const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
300
301 QMatrix4x4 lightMatrix;
302 lightMatrix.setRow(0, QVector4D(right, 0.0f));
303 lightMatrix.setRow(1, QVector4D(up, 0.0f));
304 lightMatrix.setRow(2, QVector4D(forward, 0.0f));
305 lightMatrix.setRow(3, 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(inCamera);
311 QMatrix4x4 viewProjection(Qt::Uninitialized);
312 inCamera.calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
313 const QSSGBoxPoints frustum = computeFrustumBounds(viewProjection);
314 const QSSGBoxPoints frustumUntransformed = lockShadowmapTexels ? computeFrustumBounds(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(frustumUntransformed, t0 * farScale, 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(radiusSquared, (point - center).lengthSquared());
330 }
331
332 return std::sqrt(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(qMin(t0 + 0.01f, 1.0f), splits[i], 1.0f);
341 ranges.emplace_back(t0, qMin(1.0f, tI + csmBlendRatio));
342 t0 = tI;
343 }
344 ranges.emplace_back(t0, 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 forward,
355 up,
356 shadowMapResolution,
357 pcfRadius,
358 frustum,
359 range.first * farScale,
360 range.second * farScale,
361 frustumRadius,
362 lockShadowmapTexels,
363 castingObjectsBox,
364 receivingObjectsBox,
365 debugDrawSystem,
366 drawCascades,
367 drawSceneCascadeIntersection);
368 result.emplace_back(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(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
406 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
407 QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
408 QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
409 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
410 QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
411 };
412
413 auto inProbeGlobalTranform = inData.getGlobalTransform(*inProbe);
414 const QVector3D inProbePos = QSSGRenderNode::getGlobalPos(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>(2.0f, 10000.0f)};
420 inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
421
422 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inProbePos, QSSGRenderNode::initScale, inProbePivot, rotOfs[i]);
423 QSSGRenderCamera::calculateProjectionInternal(inCameras[i], 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(mapType);
444 const int samplerHint = int(mapType);
445 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, 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(QRhiTexture::MipMapped);
450 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
451 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
452 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
453 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
454 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
455 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
456 });
457 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, 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(samplerVars.cbegin(), samplerVars.cend(),
469 [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
470 if (it == samplerVars.cend())
471 samplerVars.append(var);
472 }
473
474 int maxSamplerBinding = -1;
475 for (const QShaderDescription::InOutVariable &var : samplerVars)
476 maxSamplerBinding = qMax(maxSamplerBinding, 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(i));
492 const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
493 if (samplerBinding >= 0) {
494 samplerBindingsSpecified.setBit(samplerBinding);
495 QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
496 bindings.addTexture(samplerBinding,
497 RENDERER_VISIBILITY_ALL,
498 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({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
506 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
507 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
508 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
509 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
510 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
511
512 for (const QShaderDescription::InOutVariable &var : samplerVars) {
513 if (!samplerBindingsSpecified.testBit(var.binding)) {
514 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
515 bindings.addTexture(var.binding, RENDERER_VISIBILITY_ALL, t, dummySampler);
516 }
517 }
518 }
519}
520
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(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
534 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
535 QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
536 QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
537 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
538 QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
539 };
540
541 const auto gt = inData.getGlobalTransform(*inLight);
542 const QVector3D inLightPos = QSSGRenderNode::getGlobalPos(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(90.f);
549 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inLightPos, QSSGRenderNode::initScale, lightPivot, rotOfs[i]);
550 QSSGRenderCamera::calculateProjectionInternal(inCameras[i], 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, renderable->instancingLodMin, renderable->instancingLodMax);
588 int instanceBufferBinding = 0;
589 if (instancing) {
590 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*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(ia.inputLayout.cbeginBindings(), ia.inputLayout.cendBindings(), std::back_inserter(bindings));
595 bindings.append({ stride, QRhiVertexInputBinding::PerInstance });
596 instanceBufferBinding = bindings.size() - 1;
597 ia.inputLayout.setBindings(bindings.cbegin(), bindings.cend());
598 }
599 return instanceBufferBinding;
600}
601
602static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx,
603 QSSGPassKey passKey,
604 const QSSGLayerRenderData &inData,
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, inData.layer, inCamera, renderer, pEntry, cubeFace);
617
618 QSSGShaderFeatures features = inData.getShaderFeatures();
619 // because of alteredCamera/alteredMvp below
620 features.set(QSSGShaderFeatures::Feature::DisableMultiView, 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(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, pEntry->m_rhiRenderPassDesc, ps, features, 1, 1,
638 &inCamera, &modelViewProjection, cubeFace, 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("qt_depthTexture", int(QSSGRhiSamplerBindingHints::DepthTexture));
648 const int depthTextureArrayBinding = shaderPipeline->bindingForTexture("qt_depthTextureArray", int(QSSGRhiSamplerBindingHints::DepthTextureArray));
649 if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
650 // nearest min/mag, no mipmap
651 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
652 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
653 if (depthTextureBinding >= 0)
654 bindings.addTexture(depthTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
655 if (depthTextureArrayBinding >= 0)
656 bindings.addTexture(depthTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
657 } // else ignore, not an error
658 }
659
660 // SSAO texture
661 if (shaderPipeline->ssaoTexture()) {
662 const int ssaoTextureBinding = shaderPipeline->bindingForTexture("qt_aoTexture", int(QSSGRhiSamplerBindingHints::AoTexture));
663 const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture("qt_aoTextureArray", int(QSSGRhiSamplerBindingHints::AoTextureArray));
664 if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
665 // linear min/mag, no mipmap
666 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
667 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
668 if (ssaoTextureBinding >= 0) {
669 bindings.addTexture(ssaoTextureBinding,
670 QRhiShaderResourceBinding::FragmentStage,
671 shaderPipeline->ssaoTexture(), sampler);
672 }
673 if (ssaoTextureArrayBinding >= 0) {
674 bindings.addTexture(ssaoTextureArrayBinding,
675 QRhiShaderResourceBinding::FragmentStage,
676 shaderPipeline->ssaoTexture(), sampler);
677 }
678 } // else ignore, not an error
679 }
680}
681
682static inline void addNormalTextureBindings(QSSGRhiContext *rhiCtx,
683 QSSGRhiShaderPipeline *shaderPipeline,
684 QSSGRhiShaderResourceBindingList &bindings)
685{
686 if (shaderPipeline->normalTexture()) {
687 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
688 if (normalTextureBinding >= 0) {
689 // nearest min/mag, no mipmap
690 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
691 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
692 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->normalTexture(), sampler);
693 } // else ignore, not an error
694 }
695}
696
697static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx,
698 const QSSGLayerRenderData &inData,
699 QSSGPassKey passKey,
700 QSSGShadowMapEntry *pEntry,
701 QSSGRhiGraphicsPipelineState *ps,
702 const QVector2D *depthAdjust,
703 const QSSGRenderableObjectList &sortedOpaqueObjects,
704 QSSGRenderCamera &inCamera,
705 bool orthographic,
706 QSSGRenderTextureCubeFace cubeFace,
707 quint32 cascadeIndex)
708{
709 QSSGShaderFeatures featureSet;
710 if (orthographic)
711 featureSet.set(QSSGShaderFeatures::Feature::OrthoShadowPass, true);
712 else
713 featureSet.set(QSSGShaderFeatures::Feature::PerspectiveShadowPass, true);
714
715 // Do note how updateUniformsForDefaultMaterial() get a single camera and a
716 // custom mvp; make sure multiview is disabled in the shader generator using
717 // the common flag, instead of it having to write logic for checking for
718 // OrthoShadowPoss || CubeShadowPass.
719 featureSet.set(QSSGShaderFeatures::Feature::DisableMultiView, true);
720
721 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
722 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
723 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
724
725 for (const auto &handle : sortedOpaqueObjects) {
726 QSSGRenderableObject *theObject = handle.obj;
727 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
728
729 QSSGShaderFeatures objectFeatureSet = featureSet;
730 const bool isOpaqueDepthPrePass = theObject->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
731 if (isOpaqueDepthPrePass)
732 objectFeatureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
733
734 QSSGRhiDrawCallData *dcd = nullptr;
735 QMatrix4x4 modelViewProjection;
736 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(*theObject));
737 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
738 const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(renderable.shaderDescription) > 0;
739 modelViewProjection = hasSkinning ? pEntry->m_lightViewProjection[cascadeIndex]
740 : pEntry->m_lightViewProjection[cascadeIndex] * renderable.modelContext.globalTransform;
741 // cascadeIndex is 0..3 for directional light and 0 for the pointlight & spotlight
742 // cubeFaceIdx is 0 for directional & spotlight and 0..5 for the pointlight
743 // pEntry is unique per light and a light can only be one of directional, point, or spotlight.
744 const quintptr entryIdx = cascadeIndex + cubeFaceIdx + (quintptr(renderable.subset.offset) << 3);
745 dcd = &rhiCtxD->drawCallData({ passKey, &renderable.modelContext.model, pEntry, entryIdx });
746 }
747
748 QSSGRhiShaderResourceBindingList bindings;
749 QSSGRhiShaderPipelinePtr shaderPipeline;
750 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*theObject));
751 if (theObject->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset) {
752 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
753 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
754 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
755
756 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, objectFeatureSet);
757 if (!shaderPipeline)
758 continue;
759 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
760 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
761 // calls updateUni with an alteredCamera and alteredModelViewProjection
762 QSSGRenderCameraList cameras({ &inCamera });
763 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, depthAdjust, &modelViewProjection);
764 if (blendParticles)
765 QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
766 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
767 if (blendParticles)
768 QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
769 } else if (theObject->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset) {
770 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
771 ps->cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
772
773 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
774 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), objectFeatureSet);
775 if (!shaderPipeline)
776 continue;
777 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
778 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
779 // inCamera is the shadow camera, not the same as inData.renderedCameras
780 QSSGRenderCameraList cameras({ &inCamera });
781 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, material, subsetRenderable,
782 cameras, depthAdjust, &modelViewProjection);
783 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
784 }
785
786 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
787
788 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
789 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
790 ia = subsetRenderable.subset.rhi.ia;
791 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
792 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
793 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
794
795
796 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
797
798 // Depth and SSAO textures, in case a custom material's shader code does something with them.
799 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
800 // and the normal texture, if there is one.
801 addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
802
803 if (isOpaqueDepthPrePass) {
804 addOpaqueDepthPrePassBindings(rhiCtx,
805 shaderPipeline.get(),
806 subsetRenderable.firstImage,
807 bindings,
808 (theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
809 }
810
811 // There is no screen texture at this stage. But the shader from a
812 // custom material may rely on it, and an object with that material
813 // can end up in the shadow map's object list. So bind a dummy
814 // texture then due to the lack of other options.
815 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
816 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
817 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
818 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
819 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
820 if (screenTextureBinding >= 0) {
821 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
822 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
823 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
824 bindings.addTexture(screenTextureBinding,
825 QRhiShaderResourceBinding::FragmentStage,
826 dummyTexture, sampler);
827 }
828 if (screenTextureArrayBinding >= 0) {
829 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
830 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates, QSize(64, 64), Qt::black, inData.layer.viewCount);
831 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
832 bindings.addTexture(screenTextureArrayBinding,
833 QRhiShaderResourceBinding::FragmentStage,
834 dummyTexture, sampler);
835 }
836 }
837
838 // Skinning
839 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
840 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
841 if (binding >= 0) {
842 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
843 QRhiSampler::Nearest,
844 QRhiSampler::None,
845 QRhiSampler::ClampToEdge,
846 QRhiSampler::ClampToEdge,
847 QRhiSampler::Repeat
848 });
849 bindings.addTexture(binding,
850 QRhiShaderResourceBinding::VertexStage,
851 boneTexture,
852 boneSampler);
853 }
854 }
855
856 // Morphing
857 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
858 if (targetsTexture) {
859 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
860 if (binding >= 0) {
861 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
862 QRhiSampler::Nearest,
863 QRhiSampler::None,
864 QRhiSampler::ClampToEdge,
865 QRhiSampler::ClampToEdge,
866 QRhiSampler::ClampToEdge
867 });
868 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
869 }
870 }
871
872 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
873 subsetRenderable.rhiRenderData.shadowPass.pipeline = rhiCtxD->pipeline(*ps, pEntry->m_rhiRenderPassDesc[cascadeIndex], srb);
874 subsetRenderable.rhiRenderData.shadowPass.srb[cubeFaceIdx] = srb;
875 }
876 }
877}
878
879static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
880{
881 // Assuming default values in the other TargetBlend fields
882 switch (materialBlend) {
883 case QSSGRenderDefaultMaterial::MaterialBlendMode::Screen:
884 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
885 targetBlend->dstColor = QRhiGraphicsPipeline::One;
886 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
887 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
888 break;
889 case QSSGRenderDefaultMaterial::MaterialBlendMode::Multiply:
890 targetBlend->srcColor = QRhiGraphicsPipeline::DstColor;
891 targetBlend->dstColor = QRhiGraphicsPipeline::Zero;
892 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
893 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
894 break;
895 default:
896 // Use SourceOver for everything else
897 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
898 targetBlend->dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
899 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
900 targetBlend->dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
901 break;
902 }
903}
904
905void RenderHelpers::rhiPrepareRenderable(QSSGRhiContext *rhiCtx,
906 QSSGPassKey passKey,
907 const QSSGLayerRenderData &inData,
908 QSSGRenderableObject &inObject,
909 QRhiRenderPassDescriptor *renderPassDescriptor,
910 QSSGRhiGraphicsPipelineState *ps,
911 QSSGShaderFeatures featureSet,
912 int samples,
913 int viewCount,
914 QSSGRenderCamera *alteredCamera,
915 QMatrix4x4 *alteredModelViewProjection,
916 QSSGRenderTextureCubeFace cubeFace,
918 bool oit)
919{
920 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
921
922 switch (inObject.type) {
923 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
924 {
925 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
926
927 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
928 featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
929
930 if ((cubeFace != QSSGRenderTextureCubeFaceNone)) {
931 // Disable tonemapping for the reflection pass
932 featureSet.disableTonemapping();
933 }
934
935 if (subsetRenderable.renderableFlags.rendersWithLightmap())
936 featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
937
938 const auto &shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
939 if (shaderPipeline) {
940 // Unlike the subsetRenderable (which is allocated per frame so is
941 // not persistent in any way), the model reference is persistent in
942 // the sense that it references the model node in the scene graph.
943 // Combined with the layer node (multiple View3Ds may share the
944 // same scene!), this is suitable as a key to get the uniform
945 // buffers that were used with the rendering of the same model in
946 // the previous frame.
947 QSSGRhiShaderResourceBindingList bindings;
948 const auto &modelNode = subsetRenderable.modelContext.model;
949 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
950
951
952 // NOTE:
953 // - entryIdx should 0 for QSSGRenderTextureCubeFaceNone.
954 // In all other cases the entryIdx is a combination of the cubeface idx and the subset offset, where the lower bits
955 // are the cubeface idx.
956 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
957 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(subsetRenderable.subset.offset) << 3));
958 // If there's an entry we merge that with the address of the material
959 const auto entryPartA = reinterpret_cast<quintptr>(&subsetRenderable.material);
960 const auto entryPartB = reinterpret_cast<quintptr>(entry);
961 const void *entryId = reinterpret_cast<const void *>(entryPartA ^ entryPartB);
962
963 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
964 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, &modelNode, entryId, entryIdx });
965
966 shaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
967 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
968 if (alteredCamera) {
969 Q_ASSERT(alteredModelViewProjection);
970 QSSGRenderCameraList cameras({ alteredCamera });
971 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, nullptr, alteredModelViewProjection);
972 } else {
973 Q_ASSERT(!alteredModelViewProjection);
974 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
975 }
976
977 if (blendParticles)
978 QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
979 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
980
981 if (blendParticles)
982 QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
983
984 // Skinning
985 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
986 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
987 if (binding >= 0) {
988 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
989 QRhiSampler::Nearest,
990 QRhiSampler::None,
991 QRhiSampler::ClampToEdge,
992 QRhiSampler::ClampToEdge,
993 QRhiSampler::Repeat
994 });
995 bindings.addTexture(binding,
996 QRhiShaderResourceBinding::VertexStage,
997 boneTexture,
998 boneSampler);
999 }
1000 }
1001 // Morphing
1002 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
1003 if (targetsTexture) {
1004 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
1005 if (binding >= 0) {
1006 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
1007 QRhiSampler::Nearest,
1008 QRhiSampler::None,
1009 QRhiSampler::ClampToEdge,
1010 QRhiSampler::ClampToEdge,
1011 QRhiSampler::ClampToEdge
1012 });
1013 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
1014 }
1015 }
1016
1017 ps->samples = samples;
1018 ps->viewCount = viewCount;
1019
1020 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
1021 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
1022 if (!oit)
1023 fillTargetBlend(&ps->targetBlend[0], material.blendMode);
1024
1025 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
1026
1027 ia = subsetRenderable.subset.rhi.ia;
1028 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
1029 QVector3D cameraDirection = cameraDatas[0].direction;
1030 QVector3D cameraPosition = cameraDatas[0].position;
1031 if (alteredCamera) {
1032 const QMatrix4x4 camGlobalTranform = inData.getGlobalTransform(*alteredCamera);
1033 cameraDirection = QSSGRenderNode::getScalingCorrectDirection(camGlobalTranform);
1034 cameraPosition = QSSGRenderNode::getGlobalPos(camGlobalTranform);
1035 }
1036 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
1037 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
1038
1039 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf, 0, shaderPipeline->ub0Size());
1040
1041 if (shaderPipeline->isLightingEnabled()) {
1042 bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd.ubuf,
1043 shaderPipeline->ub0LightDataOffset(),
1044 sizeof(QSSGShaderLightsUniformData));
1045 bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd.ubuf,
1046 shaderPipeline->ub0DirectionalLightDataOffset(),
1047 sizeof(QSSGShaderDirectionalLightsUniformData));
1048
1049 }
1050
1051 // Texture maps
1052 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
1053 while (renderableImage) {
1054 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
1055 const int samplerHint = int(renderableImage->m_mapType);
1056 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
1057 if (samplerBinding >= 0) {
1058 QRhiTexture *texture = renderableImage->m_texture.m_texture;
1059 if (samplerBinding >= 0 && texture) {
1060 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
1061 QSSGRhiSamplerDescription samplerDesc = {
1062 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
1063 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
1064 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
1065 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
1066 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
1067 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
1068 };
1069 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
1070 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
1071 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
1072 }
1073 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
1074 renderableImage = renderableImage->m_nextImage;
1075 }
1076
1077 if (shaderPipeline->isLightingEnabled()) {
1078 // Shadow map atlas
1079 auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
1080 if (shadowMapAtlas) {
1081 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
1082 if (binding >= 0) {
1083 QRhiTexture *texture = shadowMapAtlas;
1084 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1085 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1086 Q_ASSERT(texture && sampler);
1087 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
1088 }
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(QSSGShaderFeatures::Feature::ReflectionProbe)) {
1095 int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
1096 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
1097 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1098 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
1099 if (reflectionSampler >= 0 && reflectionTexture)
1100 bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
1101 } else if (shaderPipeline->lightProbeTexture()) {
1102 int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
1103 if (binding >= 0) {
1104 auto tiling = shaderPipeline->lightProbeTiling();
1105 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, // enables mipmapping
1106 QSSGRhiHelpers::toRhi(tiling.first), QSSGRhiHelpers::toRhi(tiling.second), QRhiSampler::Repeat });
1107 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage,
1108 shaderPipeline->lightProbeTexture(), sampler);
1109 } else {
1110 qWarning("Could not find sampler for lightprobe");
1111 }
1112 }
1113
1114 // Screen Texture
1115 if (shaderPipeline->screenTexture()) {
1116 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
1117 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", 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(QRhiTexture::MipMapped)
1124 ? QRhiSampler::Linear : QRhiSampler::None;
1125 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
1126 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
1127 if (screenTextureBinding >= 0) {
1128 bindings.addTexture(screenTextureBinding,
1129 QRhiShaderResourceBinding::FragmentStage,
1130 shaderPipeline->screenTexture(), sampler);
1131 }
1132 if (screenTextureArrayBinding >= 0) {
1133 bindings.addTexture(screenTextureArrayBinding,
1134 QRhiShaderResourceBinding::FragmentStage,
1135 shaderPipeline->screenTexture(), sampler);
1136 }
1137 } // else ignore, not an error
1138 }
1139
1140 if (shaderPipeline->lightmapTexture()) {
1141 int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
1142 if (binding >= 0) {
1143 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1144 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1145 bindings.addTexture(binding,
1146 QRhiShaderResourceBinding::FragmentStage,
1147 shaderPipeline->lightmapTexture(), sampler);
1148 } // else ignore, not an error
1149 }
1150 }
1151
1152 // Depth and SSAO textures
1153 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1154
1155 // Normal texture
1156 addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1157
1158 // Instead of always doing a QHash find in srb(), store the binding
1159 // list and the srb object in the per-model+material
1160 // QSSGRhiUniformBufferSet. While this still needs comparing the
1161 // binding list, to see if something has changed, it results in
1162 // significant gains with lots of models in the scene (because the
1163 // srb hash table becomes large then, so avoiding the lookup as
1164 // much as possible is helpful)
1165 QRhiShaderResourceBindings *&srb = dcd.srb;
1166 bool srbChanged = false;
1167 if (!srb || bindings != dcd.bindings) {
1168 srb = rhiCtxD->srb(bindings);
1169 rhiCtxD->releaseCachedSrb(dcd.bindings);
1170 dcd.bindings = bindings;
1171 srbChanged = true;
1172 }
1173
1174 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1175 subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
1176 else
1177 subsetRenderable.rhiRenderData.mainPass.srb = srb;
1178
1179 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
1180 if (dcd.pipeline
1181 && !srbChanged
1182 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
1183 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
1184 && dcd.ps == *ps)
1185 {
1186 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1187 subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
1188 else
1189 subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
1190 } else {
1191 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1192 subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1193 renderPassDescriptor,
1194 srb);
1195 dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1196 } else {
1197 subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1198 renderPassDescriptor,
1199 srb);
1200 dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
1201 }
1202 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
1203 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
1204 dcd.ps = *ps;
1205 }
1206 }
1207 break;
1208 }
1209 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1210 {
1211 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1212 const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1213 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1214
1215 featureSet.set(QSSGShaderFeatures::Feature::LightProbe, inData.layer.lightProbe || material.m_iblProbe);
1216
1217 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
1218 featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
1219
1220 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1221 // Disable tonemapping for the reflection pass
1222 featureSet.disableTonemapping();
1223 }
1224
1225 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1226 featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
1227
1228 customMaterialSystem.rhiPrepareRenderable(ps, passKey, subsetRenderable, featureSet,
1229 material, inData, renderPassDescriptor, samples, viewCount,
1230 alteredCamera, cubeFace, alteredModelViewProjection, entry, oit);
1231 break;
1232 }
1233 case QSSGRenderableObject::Type::Particles:
1234 {
1235 QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
1236 const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable, oit ? inData.layer.oitMethod : QSSGRenderLayer::OITMethod::None);
1237 if (shaderPipeline) {
1238 QSSGParticleRenderer::rhiPrepareRenderable(*shaderPipeline, passKey, rhiCtx, ps, particleRenderable, inData, renderPassDescriptor, samples, viewCount,
1239 alteredCamera, cubeFace, entry);
1240 }
1241 break;
1242 }
1243 }
1244}
1245
1246void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
1247 const QSSGRhiGraphicsPipelineState &state,
1248 QSSGRenderableObject &object,
1249 bool *needsSetViewport,
1250 QSSGRenderTextureCubeFace cubeFace)
1251{
1252 switch (object.type) {
1253 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1254 {
1255 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1256
1257 QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.mainPass.pipeline;
1258 QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.mainPass.srb;
1259
1260 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1261 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
1262 ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1263 srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
1264 }
1265
1266 if (!ps || !srb)
1267 return;
1268
1269 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
1270 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
1271
1272 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1273 // QRhi optimizes out unnecessary binding of the same pipline
1274 cb->setGraphicsPipeline(ps);
1275 cb->setShaderResources(srb);
1276
1277 if (*needsSetViewport) {
1278 cb->setViewport(state.viewport);
1279 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
1280 cb->setScissor(state.scissor);
1281 *needsSetViewport = false;
1282 }
1283
1284 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1285 int vertexBufferCount = 1;
1286 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1287 quint32 instances = 1;
1288 if ( subsetRenderable.modelContext.model.instancing()) {
1289 instances = subsetRenderable.modelContext.model.instanceCount();
1290 // If the instance count is 0, the bail out before trying to do any
1291 // draw calls. Making an instanced draw call with a count of 0 is invalid
1292 // for Metal and likely other API's as well.
1293 // It is possible that the particale system may produce 0 instances here
1294 if (instances == 0)
1295 return;
1296 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
1297 vertexBufferCount = 2;
1298 }
1299 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1300 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
1301 cb->setStencilRef(state.stencilRef);
1302 if (indexBuffer) {
1303 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
1304 cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
1305 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
1306 } else {
1307 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1308 cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
1309 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
1310 }
1311 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
1312 QVector<int>({subsetRenderable.modelContext.model.profilingId,
1313 subsetRenderable.material.profilingId}));
1314 break;
1315 }
1316 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1317 {
1318 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1319 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1320 customMaterialSystem.rhiRenderRenderable(rhiCtx, subsetRenderable, needsSetViewport, cubeFace, state);
1321 break;
1322 }
1323 case QSSGRenderableObject::Type::Particles:
1324 {
1325 QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
1326 QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
1327 break;
1328 }
1329 }
1330}
1331
1332static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp) {
1333 // Convert normalized offsets/scales to actual pixel values
1334 float x = atlasEntry.uOffset * atlasPixelSize.width();
1335 float w = atlasEntry.uvScale * atlasPixelSize.width();
1336 float h = atlasEntry.uvScale * atlasPixelSize.height();
1337
1338 // Y in atlasEntry is top-based, whereas QRhiViewport is bottom-based
1339 // topY in pixel = atlasEntry.vOffset * atlasPixelSize.height()
1340 // so bottom-left Y = totalHeight - topY - h
1341 float y;
1342 if (!yIsUp)
1343 y = atlasPixelSize.height() - (atlasEntry.vOffset * atlasPixelSize.height()) - h;
1344 else
1345 y = atlasEntry.vOffset * atlasPixelSize.height();
1346
1347 return QRhiViewport(x, y, w, h);
1348}
1349
1350void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
1351 QSSGPassKey passKey,
1352 QSSGRhiGraphicsPipelineState &ps,
1353 QSSGRenderShadowMap &shadowMapManager,
1354 const QSSGRenderCamera &camera,
1355 QSSGRenderCamera *debugCamera,
1356 const QSSGShaderLightList &globalLights,
1357 const QSSGRenderableObjectList &sortedOpaqueObjects,
1358 QSSGRenderer &renderer,
1359 const QSSGBounds3 &castingObjectsBox,
1360 const QSSGBounds3 &receivingObjectsBox)
1361{
1362 const QSSGLayerRenderData &layerData = *QSSGLayerRenderData::getCurrent(renderer);
1363 QSSGDebugDrawSystem *debugDrawSystem = renderer.contextInterface()->debugDrawSystem().get();
1364 const bool drawDirectionalLightShadowBoxes = layerData.layer.drawDirectionalLightShadowBoxes;
1365 const bool drawPointLightShadowBoxes = layerData.layer.drawPointLightShadowBoxes;
1366 const bool drawShadowCastingBounds = layerData.layer.drawShadowCastingBounds;
1367 const bool drawShadowReceivingBounds = layerData.layer.drawShadowReceivingBounds;
1368 const bool drawCascades = layerData.layer.drawCascades;
1369 const bool drawSceneCascadeIntersection = layerData.layer.drawSceneCascadeIntersection;
1370 const bool disableShadowCameraUpdate = layerData.layer.disableShadowCameraUpdate;
1371 const bool drawCulledObjects = layerData.layer.drawCulledObjects;
1372 QVector<bool> debugIsObjectCulled = drawCulledObjects ? QVector<bool>(sortedOpaqueObjects.size(), true) : QVector<bool>();
1373
1374 static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
1375 QSSGRhiGraphicsPipelineState *ps,
1376 const QSSGRenderableObjectList &sortedOpaqueObjects,
1377 int cubeFace,
1378 const QSSGBounds3 cameraBounds,
1379 QVector<bool> &debugIsObjectCulled,
1380 bool drawCulledObjects) {
1381 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1382 bool needsSetViewport = true;
1383
1384 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1385 const QSSGRenderableObjectHandle &handle = sortedOpaqueObjects[i];
1386 QSSGRenderableObject *theObject = handle.obj;
1387
1388 // Only attempt to cull models if both of its bounds are valid
1389 if (theObject->globalBoundsInstancing.isFinite() && theObject->globalBounds.isFinite()) {
1390 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1391 : theObject->globalBounds;
1392 if (!globalBounds.isEmpty() && !cameraBounds.intersects(globalBounds)) {
1393 continue;
1394 }
1395 }
1396
1397 if (Q_UNLIKELY(drawCulledObjects))
1398 debugIsObjectCulled[i] = false;
1399
1400 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1401 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1402 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
1403
1404 QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
1405 QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
1406 ? renderable->subset.rhi.indexBuffer->buffer()
1407 : nullptr;
1408
1409 // Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
1410 if (!renderable->rhiRenderData.shadowPass.pipeline)
1411 continue;
1412
1413 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1414
1415 cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
1416
1417 QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
1418 cb->setShaderResources(srb);
1419
1420 if (needsSetViewport) {
1421 cb->setViewport(ps->viewport);
1422 needsSetViewport = false;
1423 }
1424
1425 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1426 int vertexBufferCount = 1;
1427 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1428 quint32 instances = 1;
1429 if (renderable->modelContext.model.instancing()) {
1430 instances = renderable->modelContext.model.instanceCount();
1431 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
1432 vertexBufferCount = 2;
1433 }
1434 if (indexBuffer) {
1435 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, renderable->subset.rhi.indexBuffer->indexFormat());
1436 cb->drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances, renderable->subset.lodOffset(renderable->subsetLevelOfDetail));
1437 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances));
1438 } else {
1439 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1440 cb->draw(renderable->subset.count, instances, renderable->subset.offset);
1441 QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
1442 }
1443 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
1444 QVector<int>({renderable->modelContext.model.profilingId,
1445 renderable->material.profilingId}));
1446 }
1447 }
1448 };
1449
1450 static const auto rhiClearShadowMap = [](QSSGRenderer &renderer, QSSGRenderShadowMap &shadowMapManager, QSSGRhiContext *rhiCtx, QSSGRhiGraphicsPipelineState *ps, QRhiRenderPassDescriptor *renderPassDesc) {
1451 auto clearShadowMapShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiClearShadowMapShader();
1452 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, clearShadowMapShaderPipeline.get());
1453
1454 // Disable Depth Test and Depth Write
1455 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, false);
1456 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1457 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, shadowMapManager.shadowClearSrb(), renderPassDesc, {});
1458 // Reset
1459 ps->flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1460 };
1461
1462 QRhi *rhi = rhiCtx->rhi();
1463 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1464
1465 // We need to deal with a clip depth range of [0, 1] or
1466 // [-1, 1], depending on the graphics API underneath.
1467 QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
1468 if (rhi->isClipDepthZeroToOne()) {
1469 // d is [0, 1] so no need for any mapping
1470 depthAdjust[0] = 0.0f;
1471 depthAdjust[1] = 1.0f;
1472 } else {
1473 // d is [-1, 1]
1474 depthAdjust[0] = 1.0f;
1475 depthAdjust[1] = 0.5f;
1476 }
1477
1478 if (drawShadowCastingBounds)
1479 ShadowmapHelpers::addDebugBox(castingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Red, debugDrawSystem);
1480 if (drawShadowReceivingBounds)
1481 ShadowmapHelpers::addDebugBox(receivingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Green, debugDrawSystem);
1482
1483 // Create shadow map for each light in the scene
1484 const QSize atlasTextureSize = shadowMapManager.shadowMapAtlasTexture()->pixelSize();
1485 // Make sure quad renderer is ready
1486 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
1487 for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
1488 if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
1489 continue;
1490
1491 QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(i);
1492 if (!pEntry)
1493 continue;
1494
1495 const auto &light = globalLights[i].light;
1496
1497 if (!shadowMapManager.shadowMapAtlasTexture())
1498 break;
1499
1500 if (light->type == QSSGRenderLight::Type::DirectionalLight || light->type == QSSGRenderLight::Type::SpotLight) {
1501 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1502
1503 // This is just a way to store the old camera so we can use it for debug
1504 // drawing. There are probably cleaner ways to do this
1505 if (!disableShadowCameraUpdate && debugCamera) {
1506 debugCamera->clipPlanes = camera.clipPlanes;
1507 debugCamera->projection = camera.projection;
1508 // NOTE: Since the debug camera is an internally injected camera, there will only be
1509 // the local transform. Anywhere the global transform is looked up for the debug camera
1510 // it will return the local transform.
1511 debugCamera->localTransform = layerData.getGlobalTransform(camera);
1512 }
1513
1514 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
1515 if (light->type == QSSGRenderLight::Type::DirectionalLight) {
1516 const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
1517 const float clipNear = camera.clipPlanes.clipNear();
1518 const float clipFar = qMin(light->m_shadowMapFar, camera.clipPlanes.clipFar());
1519 const float clipRange = clipFar - clipNear;
1520 cascades = setupCascadingCamerasForShadowMap(layerData,
1521 disableShadowCameraUpdate ? *debugCamera : camera,
1522 light,
1523 size.width(),
1524 pcfRadius,
1525 clipNear,
1526 clipFar,
1527 castingObjectsBox,
1528 receivingObjectsBox,
1529 light->m_lockShadowmapTexels,
1530 debugDrawSystem,
1531 drawCascades,
1532 drawSceneCascadeIntersection);
1533
1534 // Write the split distances from value 0 in the z-axis of the eye view-space
1535 pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
1536 pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
1537 pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
1538 pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
1539 pEntry->m_shadowMapFar = clipFar;
1540 } else if (light->type == QSSGRenderLight::Type::SpotLight) {
1541 auto spotlightCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderCamera::Type::PerspectiveCamera);
1542 spotlightCamera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(light->m_coneAngle * 2.0f);
1543 spotlightCamera->clipPlanes = { 1.0f, light->m_shadowMapFar };
1544 const QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(*light);
1545 const QVector3D lightDir = QSSGRenderNode::getDirection(lightGlobalTransform);
1546 const QVector3D lightPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform) - lightDir * spotlightCamera->clipPlanes.clipNear();
1547 const QVector3D lightPivot = light->pivot;
1548 const QVector3D forward = lightDir.normalized();
1549 const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
1550 ? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
1551 : QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
1552 const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
1553 spotlightCamera->localTransform = QSSGRenderNode::calculateTransformMatrix(lightPos,
1554 QSSGRenderNode::initScale,
1555 lightPivot,
1556 QQuaternion::fromDirection(forward, up));
1557 QRectF theViewport(0.0f, 0.0f, (float)light->m_shadowMapRes, (float)light->m_shadowMapRes);
1558 QSSGRenderCamera::calculateProjectionInternal(*spotlightCamera, theViewport);
1559 cascades.push_back(std::move(spotlightCamera));
1560 pEntry->m_shadowMapFar = light->m_shadowMapFar;
1561 } else {
1562 Q_UNREACHABLE();
1563 }
1564
1565 memset(pEntry->m_csmActive, 0, sizeof(pEntry->m_csmActive));
1566
1567 QMatrix4x4 cascadeCameraGlobalTransforms(Qt::Uninitialized);
1568 const QMatrix4x4 bias = { 0.5, 0.0, 0.0, 0.5,
1569 0.0, 0.5, 0.0, 0.5,
1570 0.0, 0.0, 0.5, 0.5,
1571 0.0, 0.0, 0.0, 1.0 };
1572
1573 for (int cascadeIndex = 0; cascadeIndex < cascades.length(); cascadeIndex++) {
1574 const auto &cascadeCamera = cascades[cascadeIndex];
1575 if (!cascadeCamera)
1576 continue;
1577
1578 cascadeCameraGlobalTransforms = layerData.getGlobalTransform(*cascadeCamera);
1579 pEntry->m_csmActive[cascadeIndex] = 1.f;
1580 QMatrix4x4 &viewProjection = pEntry->m_lightViewProjection[cascadeIndex];
1581 cascadeCamera->calculateViewProjectionMatrix(cascadeCameraGlobalTransforms, viewProjection);
1582 pEntry->m_lightViewProjection[cascadeIndex] = viewProjection;
1583 pEntry->m_fixedScaleBiasMatrix[cascadeIndex] = bias * viewProjection;
1584 const QMatrix4x4 inverted = viewProjection.inverted();
1585 const float x = 0.5f / (inverted * QVector4D(1, 0, 0, 0)).length();
1586 const float y = 0.5f / (inverted * QVector4D(0, 1, 0, 0)).length();
1587 const float z = 0.5f / (inverted * QVector4D(0, 0, 1, 0)).length();
1588 const QSSGBoxPoints frustumPoints = computeFrustumBounds(viewProjection);
1589 const QSSGBounds3 bounds = QSSGBounds3(frustumPoints);
1590 pEntry->m_dimensionsInverted[cascadeIndex] = QVector4D(x, y, z, 0.0f);
1591 pEntry->m_lightView = cascadeCameraGlobalTransforms.inverted(); // pre-calculate this for the material
1592 const bool isOrtho = cascadeCamera->type == QSSGRenderGraphObject::Type::OrthographicCamera;
1593 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[cascadeIndex], rhi->isYUpInFramebuffer());
1594 rhiPrepareResourcesForShadowMap(rhiCtx, layerData, passKey, pEntry, &ps, &depthAdjust, sortedOpaqueObjects, *cascadeCamera, isOrtho, QSSGRenderTextureCubeFaceNone, cascadeIndex);
1595 // Render into the 2D texture pEntry->m_rhiDepthMap, using
1596 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1597 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[cascadeIndex];
1598 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1599 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1600 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1601 rhiClearShadowMap(renderer, shadowMapManager, rhiCtx, &ps, rt->renderPassDescriptor());
1602 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0, bounds, debugIsObjectCulled, drawCulledObjects);
1603 cb->endPass();
1604 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1605
1606 if (drawDirectionalLightShadowBoxes) {
1607 ShadowmapHelpers::addDirectionalLightDebugBox(frustumPoints, debugDrawSystem);
1608 }
1609 }
1610 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
1611 } else { // Point Light
1612 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1613 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1614
1615 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1616 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1617 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1618 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1619 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1620 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1621 const float shadowMapFar = qMax<float>(2.0f, light->m_shadowMapFar);
1622 setupCubeShadowCameras(layerData, light, shadowMapFar, theCameras);
1623 pEntry->m_lightView = QMatrix4x4();
1624 pEntry->m_shadowMapFar = shadowMapFar;
1625
1626 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1627 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1628 for (const auto face : QSSGRenderTextureCubeFaces) {
1629 cameraGlobalTransform = layerData.getGlobalTransform(theCameras[quint8(face)]);
1630 theCameras[quint8(face)].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_lightViewProjection[0]);
1631 pEntry->m_lightCubeView[quint8(face)] = cameraGlobalTransform.inverted(); // pre-calculate this for the material
1632
1633 rhiPrepareResourcesForShadowMap(rhiCtx,
1634 layerData,
1635 passKey,
1636 pEntry,
1637 &ps,
1638 &depthAdjust,
1639 sortedOpaqueObjects,
1640 theCameras[quint8(face)],
1641 false,
1642 face,
1643 0);
1644 }
1645
1646 // The bounds should be the same for all view projections of the cube
1647 const QVector3D center = QSSGRenderNode::getGlobalPos(layerData.getGlobalTransform(*light));
1648 const QSSGBounds3 bounds = QSSGBounds3(center - QVector3D(shadowMapFar, shadowMapFar, shadowMapFar),
1649 center + QVector3D(shadowMapFar, shadowMapFar, shadowMapFar));
1650
1651 for (const auto face : QSSGRenderTextureCubeFaces) {
1652 // Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
1653 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1654
1655 QSSGRenderTextureCubeFace outFace = face;
1656 // FACE S T GL
1657 // +x -z, -y right
1658 // -x +z, -y left
1659 // +y +x, +z top
1660 // -y +x, -z bottom
1661 // +z +x, -y front
1662 // -z -x, -y back
1663 // FACE S T D3D
1664 // +x -z, +y right
1665 // -x +z, +y left
1666 // +y +x, -z bottom
1667 // -y +x, +z top
1668 // +z +x, +y front
1669 // -z -x, +y back
1670 if (swapYFaces) {
1671 // +Y and -Y faces get swapped (D3D, Vulkan, Metal).
1672 // See shadowMapping.glsllib. This is complemented there by reversing T as well.
1673 if (outFace == QSSGRenderTextureCubeFace::PosY)
1674 outFace = QSSGRenderTextureCubeFace::NegY;
1675 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1676 outFace = QSSGRenderTextureCubeFace::PosY;
1677 }
1678 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargetCube[quint8(outFace)];
1679 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1680 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1681 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1682 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face), bounds, debugIsObjectCulled, drawCulledObjects);
1683 cb->endPass();
1684 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1685 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
1686 }
1687
1688 // Render the CubeMap into the shadowMapAtlasTexture
1689 // NOTE: These passes don't require a manual clear because we will always write every pixel in that atlas space
1690
1691 // Render the front hemisphere of the cube map into the shadow map atlas
1692 QRhiTextureRenderTarget *rtFront = pEntry->m_rhiRenderTargets[0]; // A layer in the Atlas
1693 QRhiRenderPassDescriptor *frontDesc = pEntry->m_rhiRenderPassDesc[0];
1694 auto atlasShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiCubeMapToAtlasShader();
1695 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[0], rhi->isYUpInFramebuffer());
1696 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, atlasShaderPipeline.get());
1697 QRhiShaderResourceBindings *srb = pEntry->m_cubeToAtlasFrontSrb;
1698 cb->beginPass(rtFront, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1699 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtFront));
1700 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1701
1702 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, frontDesc, QSSGRhiQuadRenderer::UvCoords);
1703
1704 cb->endPass();
1705 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1706 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 6, 0));
1707
1708 // Render the back hemisphere of the cube map into the shadow map atlas
1709 QRhiTextureRenderTarget *rtBack = pEntry->m_rhiRenderTargets[1]; // A layer in the Atlas
1710 QRhiRenderPassDescriptor *backDesc = pEntry->m_rhiRenderPassDesc[1];
1711 srb = pEntry->m_cubeToAtlasBackSrb;
1712 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[1], rhi->isYUpInFramebuffer());
1713 cb->beginPass(rtBack, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1714 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtBack));
1715 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1716
1717 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, backDesc, QSSGRhiQuadRenderer::UvCoords);
1718
1719 cb->endPass();
1720 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1721 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 7, 0));
1722
1723
1724 // reset pipeline (This part is necessary, but not ideal)
1725 ps = layerData.getPipelineState();
1726 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1727 ps.depthBias = 2;
1728 ps.slopeScaledDepthBias = 1.5f;
1729
1730 if (drawPointLightShadowBoxes) {
1731 ShadowmapHelpers::addDebugBox(bounds.toQSSGBoxPoints(), QColorConstants::Yellow, debugDrawSystem);
1732 }
1733 }
1734 }
1735
1736 if (Q_UNLIKELY(drawCulledObjects)) {
1737 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1738 QSSGRenderableObject *theObject = sortedOpaqueObjects[i].obj;
1739 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1740 : theObject->globalBounds;
1741 const QColor color = debugIsObjectCulled[i] ? QColorConstants::Red : QColorConstants::Green;
1742 ShadowmapHelpers::addDebugBox(globalBounds.toQSSGBoxPointsNoEmptyCheck(), color, debugDrawSystem);
1743 }
1744 }
1745}
1746
1747void RenderHelpers::rhiRenderReflectionMap(QSSGRhiContext *rhiCtx,
1748 QSSGPassKey passKey,
1749 const QSSGLayerRenderData &inData,
1750 QSSGRhiGraphicsPipelineState *ps,
1751 QSSGRenderReflectionMap &reflectionMapManager,
1752 const QVector<QSSGRenderReflectionProbe *> &reflectionProbes,
1753 const QSSGRenderableObjectList &reflectionPassObjects,
1754 QSSGRenderer &renderer)
1755{
1756 QRhi *rhi = rhiCtx->rhi();
1757 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1758
1759 const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox ||
1760 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1761 && rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch);
1762
1763 for (int i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
1764 QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(i);
1765 if (!pEntry)
1766 continue;
1767
1768 if (!pEntry->m_needsRender)
1769 continue;
1770
1771 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
1772 continue;
1773
1774 if (reflectionProbes[i]->texture)
1775 continue;
1776
1777 Q_ASSERT(pEntry->m_rhiDepthStencil);
1778 Q_ASSERT(pEntry->m_rhiCube);
1779
1780 const QSize size = pEntry->m_rhiCube->pixelSize();
1781 ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1782
1783 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1784 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1785 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1786 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1787 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1788 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1789 setupCubeReflectionCameras(inData, reflectionProbes[i], theCameras);
1790 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1791 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1792 for (const auto face : QSSGRenderTextureCubeFaces) {
1793 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
1794 cameraGlobalTransform = inData.getGlobalTransform(theCameras[cubeFaceIdx]);
1795 theCameras[cubeFaceIdx].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_viewProjection);
1796
1797 rhiPrepareResourcesForReflectionMap(rhiCtx, passKey, inData, pEntry, ps,
1798 reflectionPassObjects, theCameras[cubeFaceIdx], renderer, face);
1799 }
1800 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
1801 for (auto face : QSSGRenderTextureCubeFaces) {
1802 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1803 face = pEntry->m_timeSliceFace;
1804
1805 QSSGRenderTextureCubeFace outFace = face;
1806 // Faces are swapped similarly to shadow maps due to differences in backends
1807 // Prefilter step handles correcting orientation differences in the final render
1808 if (swapYFaces) {
1809 if (outFace == QSSGRenderTextureCubeFace::PosY)
1810 outFace = QSSGRenderTextureCubeFace::NegY;
1811 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1812 outFace = QSSGRenderTextureCubeFace::PosY;
1813 }
1814 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1815 cb->beginPass(rt, reflectionProbes[i]->clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1816 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1817 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1818
1819 if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
1820 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1821 const bool isSkyBox = inData.layer.background == QSSGRenderLayer::Background::SkyBox;
1822 const auto &shaderPipeline = isSkyBox ? shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(QSSGRenderLayer::TonemapMode::None, inData.layer.skyBoxIsRgbe8, 1)
1823 : shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(1);
1824 Q_ASSERT(shaderPipeline);
1825 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
1826 QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
1827 if (!renderPassDesc)
1828 renderPassDesc = rt->newCompatibleRenderPassDescriptor();
1829 rt->setRenderPassDescriptor(renderPassDesc);
1830 isSkyBox ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, renderPassDesc, {})
1831 : renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, renderPassDesc, {});
1832 }
1833
1834 bool needsSetViewport = true;
1835 for (const auto &handle : reflectionPassObjects)
1836 rhiRenderRenderable(rhiCtx, *ps, *handle.obj, &needsSetViewport, face);
1837
1838 cb->endPass();
1839 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1840 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
1841
1842 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1843 break;
1844 }
1845 if (renderPassDesc)
1846 renderPassDesc->deleteLater();
1847
1848 pEntry->renderMips(rhiCtx);
1849
1850 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1851 pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(pEntry->m_timeSliceFace); // Wraps
1852
1853 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
1854 pEntry->m_rendered = true;
1855
1856 reflectionProbes[i]->hasScheduledUpdate = false;
1857 pEntry->m_needsRender = false;
1858 }
1859}
1860
1861bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx,
1862 const QSize &size,
1863 QSSGRhiRenderableTexture *renderableTex,
1864 quint8 viewCount)
1865{
1866 QRhi *rhi = rhiCtx->rhi();
1867 bool needsBuild = false;
1868
1869 if (!renderableTex->texture) {
1870 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1871 // the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
1872 if (viewCount <= 1)
1873 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
1874 else
1875 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
1876 needsBuild = true;
1877 } else if (renderableTex->texture->pixelSize() != size) {
1878 renderableTex->texture->setPixelSize(size);
1879 needsBuild = true;
1880 }
1881
1882 if (needsBuild) {
1883 if (!renderableTex->texture->create()) {
1884 qWarning("Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
1885 renderableTex->reset();
1886 return false;
1887 }
1888 renderableTex->resetRenderTarget();
1889 QRhiTextureRenderTargetDescription desc;
1890 QRhiColorAttachment colorAttachment(renderableTex->texture);
1891 colorAttachment.setMultiViewCount(viewCount);
1892 desc.setColorAttachments({ colorAttachment });
1893 renderableTex->rt = rhi->newTextureRenderTarget(desc);
1894 renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
1895 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
1896 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
1897 if (!renderableTex->rt->create()) {
1898 qWarning("Failed to build render target for ambient occlusion texture");
1899 renderableTex->reset();
1900 return false;
1901 }
1902 }
1903
1904 return true;
1905}
1906
1907void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
1908 QSSGPassKey passKey,
1909 QSSGRenderer &renderer,
1910 QSSGRhiShaderPipeline &shaderPipeline,
1911 QSSGRhiGraphicsPipelineState &ps,
1912 const QSSGAmbientOcclusionSettings &ao,
1913 const QSSGRhiRenderableTexture &rhiAoTexture,
1914 const QSSGRhiRenderableTexture &rhiDepthTexture,
1915 const QSSGRenderCamera &camera)
1916{
1917 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1918
1919 // no texelFetch in GLSL <= 120 and GLSL ES 100
1920 if (!rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
1921 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1922 // just clear and stop there
1923 cb->beginPass(rhiAoTexture.rt, Qt::white, { 1.0f, 0 });
1924 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
1925 cb->endPass();
1926 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1927 return;
1928 }
1929
1930 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, &shaderPipeline);
1931
1932 const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
1933 const QSize textureSize = rhiAoTexture.texture->pixelSize();
1934 const float rw = float(textureSize.width());
1935 const float rh = float(textureSize.height());
1936 const float fov = camera.fov.asVerticalFov(rw / rh).radians();
1937 const float tanHalfFovY = tanf(0.5f * fov * (rh / rw));
1938 const float invFocalLenX = tanHalfFovY * (rw / rh);
1939
1940 const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
1941 const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
1942 const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
1943 const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
1944 const QVector2D cameraProps = camera.clipPlanes;
1945
1946 // layout(std140, binding = 0) uniform buf {
1947 // vec4 aoProperties;
1948 // vec4 aoProperties2;
1949 // vec4 aoScreenConst;
1950 // vec4 uvToEyeConst;
1951 // vec2 cameraProperties;
1952
1953 const int UBUF_SIZE = 72;
1954 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 }));
1955 if (!dcd.ubuf) {
1956 dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
1957 dcd.ubuf->create();
1958 }
1959
1960 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1961 memcpy(ubufData, &aoProps, 16);
1962 memcpy(ubufData + 16, &aoProps2, 16);
1963 memcpy(ubufData + 32, &aoScreenConst, 16);
1964 memcpy(ubufData + 48, &uvToEyeConst, 16);
1965 memcpy(ubufData + 64, &cameraProps, 8);
1966 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1967
1968 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
1969 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1970 QSSGRhiShaderResourceBindingList bindings;
1971 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
1972 // binding 1 is either a sampler2D or sampler2DArray, matching
1973 // rhiDepthTexture.texture, no special casing needed here
1974 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiDepthTexture.texture, sampler);
1975 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1976
1977 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
1978 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &ps, srb, rhiAoTexture.rt, {});
1979}
1980
1981bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx,
1982 const QSize &size,
1983 bool wantsMips,
1984 QSSGRhiRenderableTexture *renderableTex,
1985 quint8 viewCount)
1986{
1987 QRhi *rhi = rhiCtx->rhi();
1988 bool needsBuild = false;
1989 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1990 if (wantsMips)
1991 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
1992
1993 if (!renderableTex->texture) {
1994 // always non-msaa, even if multisampling is used in the main pass
1995 if (viewCount <= 1)
1996 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
1997 else
1998 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
1999 needsBuild = true;
2000 } else if (renderableTex->texture->pixelSize() != size) {
2001 renderableTex->texture->setPixelSize(size);
2002 needsBuild = true;
2003 }
2004
2005 if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
2006 if (viewCount <= 1)
2007 renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
2008 else
2009 renderableTex->depthTexture = rhi->newTextureArray(QRhiTexture::D24S8, viewCount, size, 1, QRhiTexture::RenderTarget);
2010 needsBuild = true;
2011 } else {
2012 if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
2013 renderableTex->depthStencil->setPixelSize(size);
2014 needsBuild = true;
2015 } else if (renderableTex->depthTexture && renderableTex->depthTexture->pixelSize() != size) {
2016 renderableTex->depthTexture->setPixelSize(size);
2017 needsBuild = true;
2018 }
2019 }
2020
2021 if (needsBuild) {
2022 if (!renderableTex->texture->create()) {
2023 qWarning("Failed to build screen texture (size %dx%d)", size.width(), size.height());
2024 renderableTex->reset();
2025 return false;
2026 }
2027 if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
2028 qWarning("Failed to build depth-stencil buffer for screen texture (size %dx%d)",
2029 size.width(), size.height());
2030 renderableTex->reset();
2031 return false;
2032 } else if (renderableTex->depthTexture && !renderableTex->depthTexture->create()) {
2033 qWarning("Failed to build depth-stencil texture array (multiview) for screen texture (size %dx%d)",
2034 size.width(), size.height());
2035 renderableTex->reset();
2036 return false;
2037 }
2038 renderableTex->resetRenderTarget();
2039 QRhiTextureRenderTargetDescription desc;
2040 QRhiColorAttachment colorAttachment(renderableTex->texture);
2041 colorAttachment.setMultiViewCount(viewCount);
2042 desc.setColorAttachments({ colorAttachment });
2043 if (renderableTex->depthStencil)
2044 desc.setDepthStencilBuffer(renderableTex->depthStencil);
2045 else if (renderableTex->depthTexture)
2046 desc.setDepthTexture(renderableTex->depthTexture);
2047 renderableTex->rt = rhi->newTextureRenderTarget(desc);
2048 renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
2049 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2050 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2051 if (!renderableTex->rt->create()) {
2052 qWarning("Failed to build render target for screen texture");
2053 renderableTex->reset();
2054 return false;
2055 }
2056 }
2057
2058 return true;
2059}
2060
2061void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
2062{
2063 QSSG_ASSERT(layer.renderData, return);
2064
2065 const auto *renderData = layer.renderData;
2066
2067 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2068 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2069 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
2070
2071 QSSGRhiShaderResourceBindingList bindings;
2072
2073 int uniformBinding = 0;
2074 const int ubufSize = cameras.count() >= 2 ? 276 : 148;
2075
2076 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 })); // Change to Grid?
2077
2078 QRhi *rhi = rhiCtx->rhi();
2079 if (!dcd.ubuf) {
2080 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2081 dcd.ubuf->create();
2082 }
2083
2084 // Param
2085 const auto clipPlanes = cameras[0]->clipPlanes;
2086 const float scale = layer.gridScale;
2087 const quint32 gridFlags = layer.gridFlags;
2088
2089 const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2090
2091 quint32 ubufOffset = 0;
2092 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2093
2094 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
2095 QMatrix4x4 viewProj(Qt::Uninitialized);
2096 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2097 cameraGlobalTransform = renderData->getGlobalTransform(*cameras[viewIdx]);
2098 cameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProj);
2099 QMatrix4x4 invViewProj = viewProj.inverted();
2100 quint32 viewDataOffset = ubufOffset;
2101 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProj.constData(), 64);
2102 viewDataOffset += 64 * cameras.count();
2103 memcpy(ubufData + viewDataOffset + viewIdx * 64, invViewProj.constData(), 64);
2104 }
2105 ubufOffset += (64 + 64) * cameras.count();
2106
2107 memcpy(ubufData + ubufOffset, &clipPlanes, 8);
2108 ubufOffset += 8;
2109 memcpy(ubufData + ubufOffset, &scale, 4);
2110 ubufOffset += 4;
2111 memcpy(ubufData + ubufOffset, &yFactor, 4);
2112 ubufOffset += 4;
2113 memcpy(ubufData + ubufOffset, &gridFlags, 4);
2114
2115 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2116
2117 bindings.addUniformBuffer(uniformBinding, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2118
2119 layer.gridSrb = rhiCtxD->srb(bindings);
2120 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2121
2122 cb->debugMarkEnd();
2123}
2124
2125static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx,
2126 QSSGPassKey passKey,
2127 QSSGRenderLayer &layer,
2128 QSSGRenderCameraList &cameras,
2129 QSSGRenderer &renderer,
2130 QSSGReflectionMapEntry *entry = nullptr,
2131 QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone)
2132{
2133 QSSG_ASSERT(layer.renderData, return);
2134
2135 const auto *renderData = layer.renderData;
2136
2137 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2138 const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
2139 const QSSGRenderImageTexture lightProbeTexture =
2140 cubeMapMode ? renderer.contextInterface()->bufferManager()->loadRenderImage(layer.skyBoxCubeMap, QSSGBufferManager::MipModeDisable)
2141 : renderer.contextInterface()->bufferManager()->loadRenderImage(layer.lightProbe, QSSGBufferManager::MipModeBsdf);
2142 const bool hasValidTexture = lightProbeTexture.m_texture != nullptr;
2143 if (hasValidTexture) {
2144 if (cubeFace == QSSGRenderTextureCubeFaceNone)
2145 layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
2146
2147 QSSGRhiShaderResourceBindingList bindings;
2148
2149 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
2150 QRhiSampler::Linear,
2151 cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
2152 QRhiSampler::Repeat,
2153 QRhiSampler::ClampToEdge,
2154 QRhiSampler::Repeat });
2155 int samplerBinding = 1; //the shader code is hand-written, so we don't need to look that up
2156 const quint32 ubufSize = cameras.count() >= 2 ? 416 : 240; // same ubuf layout for both skybox and skyboxcube
2157 bindings.addTexture(samplerBinding,
2158 QRhiShaderResourceBinding::FragmentStage,
2159 lightProbeTexture.m_texture, sampler);
2160
2161 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2162 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
2163 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, nullptr, entry, entryIdx });
2164
2165 QRhi *rhi = rhiCtx->rhi();
2166 if (!dcd.ubuf) {
2167 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2168 dcd.ubuf->create();
2169 }
2170
2171 float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2172 const float exposure = layer.lightProbeSettings.probeExposure;
2173 // orientation
2174 const QMatrix3x3 &rotationMatrix(layer.lightProbeSettings.probeOrientation);
2175 const float blurAmount = layer.skyboxBlurAmount;
2176 const float maxMipLevel = float(lightProbeTexture.m_mipmapCount - 2);
2177
2178 const QVector4D skyboxProperties = {
2179 adjustY,
2180 exposure,
2181 blurAmount,
2182 maxMipLevel
2183 };
2184
2185 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2186 quint32 ubufOffset = 0;
2187 // skyboxProperties
2188 memcpy(ubufData + ubufOffset, &skyboxProperties, 16);
2189 ubufOffset += 16;
2190 // orientation
2191 memcpy(ubufData + ubufOffset, rotationMatrix.constData(), 12);
2192 ubufOffset += 16;
2193 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 12, 12);
2194 ubufOffset += 16;
2195 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 24, 12);
2196 ubufOffset += 16;
2197
2198 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2199 const QMatrix4x4 &inverseProjection = cameras[viewIdx]->projection.inverted();
2200 const QMatrix4x4 &viewMatrix = renderData->getGlobalTransform(*cameras[viewIdx]);
2201 QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
2202 cameras[viewIdx]->calculateViewProjectionWithoutTranslation(viewMatrix, 0.1f, 5.0f, viewProjection);
2203
2204 quint32 viewDataOffset = ubufOffset;
2205 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProjection.constData(), 64);
2206 viewDataOffset += cameras.count() * 64;
2207 memcpy(ubufData + viewDataOffset + viewIdx * 64, inverseProjection.constData(), 64);
2208 viewDataOffset += cameras.count() * 64;
2209 memcpy(ubufData + viewDataOffset + viewIdx * 48, viewMatrix.constData(), 48);
2210 }
2211 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2212
2213 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2214
2215 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
2216 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2217 entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtxD->srb(bindings);
2218 } else {
2219 layer.skyBoxSrb = rhiCtxD->srb(bindings);
2220 }
2221
2222 if (cubeMapMode)
2223 renderer.rhiCubeRenderer()->prepareCube(rhiCtx, nullptr);
2224 else
2225 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2226 }
2227}
2228
2229void RenderHelpers::rhiPrepareSkyBox(QSSGRhiContext *rhiCtx,
2230 QSSGPassKey passKey,
2231 QSSGRenderLayer &layer,
2232 QSSGRenderCameraList &cameras,
2233 QSSGRenderer &renderer)
2234{
2235 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2236 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
2237
2238 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer);
2239
2240 cb->debugMarkEnd();
2241}
2242
2243void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(QSSGRhiContext *rhiCtx,
2244 QSSGPassKey passKey,
2245 QSSGRenderLayer &layer,
2246 QSSGRenderCamera &inCamera,
2247 QSSGRenderer &renderer,
2249 QSSGRenderTextureCubeFace cubeFace)
2250{
2251 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2252 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
2253
2254 QSSGRenderCameraList cameras({ &inCamera });
2255 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, entry, cubeFace);
2256
2257 cb->debugMarkEnd();
2258}
2259
2260bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
2261 QSSGPassKey passKey,
2262 const QSSGRhiGraphicsPipelineState &basePipelineState,
2263 QRhiRenderPassDescriptor *rpDesc,
2264 QSSGLayerRenderData &inData,
2265 const QSSGRenderableObjectList &sortedOpaqueObjects,
2266 const QSSGRenderableObjectList &sortedTransparentObjects,
2267 int samples,
2268 int viewCount)
2269{
2270 static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
2271 QSSGPassKey passKey,
2272 QSSGLayerRenderData &inData,
2274 QRhiRenderPassDescriptor *rpDesc,
2275 QSSGRhiGraphicsPipelineState *ps) {
2276 QSSGRhiShaderPipelinePtr shaderPipeline;
2277 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2278
2279 const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
2280 QSSGShaderFeatures featureSet;
2281 featureSet.set(QSSGShaderFeatures::Feature::DepthPass, true);
2282 if (isOpaqueDepthPrePass)
2283 featureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
2284
2285 QSSGRhiDrawCallData *dcd = nullptr;
2286 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2287 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2288 const void *modelNode = &subsetRenderable.modelContext.model;
2289 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2290 }
2291
2292 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2293 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2294 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2295 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2296
2297 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
2298 if (shaderPipeline) {
2299 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2300 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2301 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2302 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2303 } else {
2304 return false;
2305 }
2306 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2307 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2308
2309 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2310
2311 ps->cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2312
2313 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2314 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2315
2316 if (shaderPipeline) {
2317 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2318 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2319 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, customMaterial, subsetRenderable,
2320 inData.renderedCameras, nullptr, nullptr);
2321 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2322 } else {
2323 return false;
2324 }
2325 }
2326
2327 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2328 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2329 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2330 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
2331 ia = subsetRenderable.subset.rhi.ia;
2332
2333 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2334 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2335 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2336
2337 QSSGRhiShaderResourceBindingList bindings;
2338 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2339
2340 // Depth and SSAO textures, in case a custom material's shader code does something with them.
2341 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2342
2343 if (isOpaqueDepthPrePass) {
2344 addOpaqueDepthPrePassBindings(rhiCtx,
2345 shaderPipeline.get(),
2346 subsetRenderable.firstImage,
2347 bindings,
2348 (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
2349 }
2350
2351 // There is no normal texture at this stage. But the shader from a
2352 // custom material may rely on it. Bind a dummy texture then due to
2353 // the lack of other options.
2354 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2355 if (normalTextureBinding >= 0) {
2356 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2357 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2358 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2359 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2360 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2361 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2362 }
2363
2364 // Skinning
2365 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2366 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2367 if (binding >= 0) {
2368 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2369 QRhiSampler::Nearest,
2370 QRhiSampler::None,
2371 QRhiSampler::ClampToEdge,
2372 QRhiSampler::ClampToEdge,
2373 QRhiSampler::Repeat
2374 });
2375 bindings.addTexture(binding,
2376 QRhiShaderResourceBinding::VertexStage,
2377 boneTexture,
2378 boneSampler);
2379 }
2380 }
2381
2382 // Morphing
2383 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2384 if (targetsTexture) {
2385 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2386 if (binding >= 0) {
2387 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2388 QRhiSampler::Nearest,
2389 QRhiSampler::None,
2390 QRhiSampler::ClampToEdge,
2391 QRhiSampler::ClampToEdge,
2392 QRhiSampler::ClampToEdge
2393 });
2394 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2395 }
2396 }
2397
2398 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2399
2400 subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtxD->pipeline(*ps,
2401 rpDesc,
2402 srb);
2403 subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
2404 }
2405
2406 return true;
2407 };
2408
2409 // Phase 1 (prepare) for the Z prepass or the depth texture generation.
2410 // These renders opaque (Z prepass), or opaque and transparent (depth
2411 // texture), objects with depth test/write enabled, and color write
2412 // disabled, using a very simple set of shaders.
2413
2414 QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
2415 // We took a copy of the pipeline state since we do not want to conflict
2416 // with what rhiPrepare() collects for its own use. So here just change
2417 // whatever we need.
2418
2419 ps.samples = samples;
2420 ps.viewCount = viewCount;
2421 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
2422 ps.targetBlend[0].colorWrite = {};
2423
2424 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2425 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2426 return false;
2427 }
2428
2429 for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
2430 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2431 return false;
2432 }
2433
2434 return true;
2435}
2436
2437void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
2438 const QSSGRhiGraphicsPipelineState &pipelineState,
2439 const QSSGRenderableObjectList &sortedOpaqueObjects,
2440 const QSSGRenderableObjectList &sortedTransparentObjects,
2441 bool *needsSetViewport)
2442{
2443 static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
2444 const QSSGRhiGraphicsPipelineState &pipelineState,
2445 const QSSGRenderableObjectList &objects,
2446 bool *needsSetViewport) {
2447 for (const auto &oh : objects) {
2448 QSSGRenderableObject *obj = oh.obj;
2449
2450 // casts to SubsetRenderableBase so it works for both default and custom materials
2451 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2452 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2453 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2454
2455 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2456 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2457 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2458 : nullptr;
2459
2460 QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
2461 if (!ps)
2462 return;
2463
2464 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
2465 if (!srb)
2466 return;
2467
2468 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2469 cb->setGraphicsPipeline(ps);
2470 cb->setShaderResources(srb);
2471
2472 if (*needsSetViewport) {
2473 cb->setViewport(pipelineState.viewport);
2474 *needsSetViewport = false;
2475 }
2476
2477 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2478 int vertexBufferCount = 1;
2479 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2480 quint32 instances = 1;
2481 if (subsetRenderable->modelContext.model.instancing()) {
2482 instances = subsetRenderable->modelContext.model.instanceCount();
2483 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2484 vertexBufferCount = 2;
2485 }
2486
2487 if (indexBuffer) {
2488 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2489 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2490 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2491 } else {
2492 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2493 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2494 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2495 }
2496 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2497 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2498 subsetRenderable->material.profilingId}));
2499 }
2500 }
2501 };
2502
2503 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
2504 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
2505}
2506
2507bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx,
2508 const QSize &size,
2509 QSSGRhiRenderableTexture *renderableTex,
2510 quint8 viewCount,
2511 int samples)
2512{
2513 QRhi *rhi = rhiCtx->rhi();
2514 bool needsBuild = false;
2515
2516 if (!renderableTex->texture) {
2517 QRhiTexture::Format format = QRhiTexture::D32F;
2518 if (!rhi->isTextureFormatSupported(format))
2519 format = QRhiTexture::D16;
2520 if (!rhi->isTextureFormatSupported(format))
2521 qWarning("Depth texture not supported");
2522 if (viewCount <= 1)
2523 renderableTex->texture = rhiCtx->rhi()->newTexture(format, size, samples, QRhiTexture::RenderTarget);
2524 else
2525 renderableTex->texture = rhiCtx->rhi()->newTextureArray(format, viewCount, size, 1, QRhiTexture::RenderTarget);
2526 needsBuild = true;
2527 } else if (renderableTex->texture->pixelSize() != size) {
2528 renderableTex->texture->setPixelSize(size);
2529 needsBuild = true;
2530 }
2531
2532 if (needsBuild) {
2533 if (!renderableTex->texture->create()) {
2534 qWarning("Failed to build depth texture (size %dx%d, format %d)",
2535 size.width(), size.height(), int(renderableTex->texture->format()));
2536 renderableTex->reset();
2537 return false;
2538 }
2539 renderableTex->resetRenderTarget();
2540 QRhiTextureRenderTargetDescription rtDesc;
2541 rtDesc.setDepthTexture(renderableTex->texture);
2542 renderableTex->rt = rhi->newTextureRenderTarget(rtDesc);
2543 renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
2544 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2545 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2546 if (!renderableTex->rt->create()) {
2547 qWarning("Failed to build render target for depth texture");
2548 renderableTex->reset();
2549 return false;
2550 }
2551 }
2552
2553 return true;
2554}
2555
2556bool RenderHelpers::rhiPrepareNormalPass(QSSGRhiContext *rhiCtx,
2557 QSSGPassKey passKey,
2558 const QSSGRhiGraphicsPipelineState &basePipelineState,
2559 QRhiRenderPassDescriptor *rpDesc,
2560 QSSGLayerRenderData &inData,
2561 const QSSGRenderableObjectList &sortedOpaqueObjects)
2562{
2563 QSSGRhiGraphicsPipelineState ps = basePipelineState;
2564 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
2565 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
2566
2567 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2568 QSSGRenderableObject *obj = handle.obj;
2569 QSSGRhiShaderPipelinePtr shaderPipeline;
2570 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2571
2572 QSSGShaderFeatures featureSet;
2573 featureSet.set(QSSGShaderFeatures::Feature::NormalPass, true);
2574
2575 QSSGRhiDrawCallData *dcd = nullptr;
2576 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2577 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2578 const void *modelNode = &subsetRenderable.modelContext.model;
2579 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2580 }
2581
2582 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2583 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2584 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2585 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2586
2587 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
2588 if (shaderPipeline) {
2589 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2590 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2591 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2592 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2593 }
2594 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2595 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2596
2597 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2598
2599 ps.cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2600
2601 const auto &customMaterialSystem = subsetRenderable.renderer->contextInterface()->customMaterialSystem();
2602 shaderPipeline = customMaterialSystem->shadersForCustomMaterial(&ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2603
2604 if (shaderPipeline) {
2605 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2606 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2607 customMaterialSystem->updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, customMaterial, subsetRenderable,
2608 inData.renderedCameras, nullptr, nullptr);
2609 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2610 }
2611 }
2612
2613 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2614 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2615 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2616 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
2617 ia = subsetRenderable.subset.rhi.ia;
2618
2619 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2620 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2621 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2622
2623 QSSGRhiShaderResourceBindingList bindings;
2624 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2625
2626 // Texture maps
2627 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
2628 while (renderableImage) {
2629 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
2630 const int samplerHint = int(renderableImage->m_mapType);
2631 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
2632 if (samplerBinding >= 0) {
2633 QRhiTexture *texture = renderableImage->m_texture.m_texture;
2634 if (samplerBinding >= 0 && texture) {
2635 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
2636 QSSGRhiSamplerDescription samplerDesc = {
2637 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
2638 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
2639 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
2640 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
2641 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
2642 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
2643 };
2644 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
2645 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
2646 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
2647 }
2648 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
2649 renderableImage = renderableImage->m_nextImage;
2650 }
2651
2652 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2653
2654 // There is no normal texture at this stage, obviously.
2655 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2656 if (normalTextureBinding >= 0) {
2657 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2658 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2659 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2660 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2661 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2662 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2663 }
2664
2665 // Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
2666
2667 // Skinning
2668 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2669 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2670 if (binding >= 0) {
2671 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2672 QRhiSampler::Nearest,
2673 QRhiSampler::None,
2674 QRhiSampler::ClampToEdge,
2675 QRhiSampler::ClampToEdge,
2676 QRhiSampler::Repeat
2677 });
2678 bindings.addTexture(binding,
2679 QRhiShaderResourceBinding::VertexStage,
2680 boneTexture,
2681 boneSampler);
2682 }
2683 }
2684
2685 // Morphing
2686 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2687 if (targetsTexture) {
2688 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2689 if (binding >= 0) {
2690 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2691 QRhiSampler::Nearest,
2692 QRhiSampler::None,
2693 QRhiSampler::ClampToEdge,
2694 QRhiSampler::ClampToEdge,
2695 QRhiSampler::ClampToEdge
2696 });
2697 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2698 }
2699 }
2700
2701 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2702
2703 subsetRenderable.rhiRenderData.normalPass.pipeline = rhiCtxD->pipeline(ps,
2704 rpDesc,
2705 srb);
2706 subsetRenderable.rhiRenderData.normalPass.srb = srb;
2707 }
2708 }
2709
2710 return true;
2711}
2712
2713void RenderHelpers::rhiRenderNormalPass(QSSGRhiContext *rhiCtx,
2714 const QSSGRhiGraphicsPipelineState &pipelineState,
2715 const QSSGRenderableObjectList &sortedOpaqueObjects,
2716 bool *needsSetViewport)
2717{
2718 for (const auto &oh : sortedOpaqueObjects) {
2719 QSSGRenderableObject *obj = oh.obj;
2720
2721 // casts to SubsetRenderableBase so it works for both default and custom materials
2722 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2723 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2724 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2725
2726 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2727 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2728 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2729 : nullptr;
2730
2731 QRhiGraphicsPipeline *graphicsPipeline = subsetRenderable->rhiRenderData.normalPass.pipeline;
2732 if (!graphicsPipeline)
2733 return;
2734
2735 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.normalPass.srb;
2736 if (!srb)
2737 return;
2738
2739 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2740 cb->setGraphicsPipeline(graphicsPipeline);
2741 cb->setShaderResources(srb);
2742
2743 if (needsSetViewport) {
2744 cb->setViewport(pipelineState.viewport);
2745 *needsSetViewport = false;
2746 }
2747
2748 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2749 int vertexBufferCount = 1;
2750 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2751 quint32 instances = 1;
2752 if (subsetRenderable->modelContext.model.instancing()) {
2753 instances = subsetRenderable->modelContext.model.instanceCount();
2754 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2755 vertexBufferCount = 2;
2756 }
2757
2758 if (indexBuffer) {
2759 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2760 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2761 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2762 } else {
2763 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2764 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2765 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2766 }
2767 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2768 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2769 subsetRenderable->material.profilingId}));
2770 }
2771 }
2772}
2773
2774QT_END_NAMESPACE
void rhiRenderAoTexture(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderer &renderer, QSSGRhiShaderPipeline &shaderPipeline, QSSGRhiGraphicsPipelineState &ps, const QSSGAmbientOcclusionSettings &ao, const QSSGRhiRenderableTexture &rhiAoTexture, const QSSGRhiRenderableTexture &rhiDepthTexture, const QSSGRenderCamera &camera)
bool rhiPrepareNormalPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, QSSGLayerRenderData &inData, const QSSGRenderableObjectList &sortedOpaqueObjects)
void rhiRenderNormalPass(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &ps, const QSSGRenderableObjectList &sortedOpaqueObjects, bool *needsSetViewport)
Q_QUICK3DRUNTIMERENDER_EXPORT void rhiRenderRenderable(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &state, QSSGRenderableObject &object, bool *needsSetViewport, QSSGRenderTextureCubeFace cubeFace=QSSGRenderTextureCubeFaceNone)
bool rhiPrepareDepthPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, QSSGLayerRenderData &inData, const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects, int samples, int viewCount)
std::pair< QSSGBounds3, QSSGBounds3 > calculateSortedObjectBounds(const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects)
bool rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx, const QSize &size, bool wantsMips, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount)
bool rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount, int samples=1)
void rhiRenderReflectionMap(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGLayerRenderData &inData, QSSGRhiGraphicsPipelineState *ps, QSSGRenderReflectionMap &reflectionMapManager, const QVector< QSSGRenderReflectionProbe * > &reflectionProbes, const QSSGRenderableObjectList &reflectionPassObjects, QSSGRenderer &renderer)
bool rhiPrepareAoTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount)
void rhiRenderDepthPass(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &ps, const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects, bool *needsSetViewport)
void addDebugBox(const QSSGBoxPoints &boxUnsorted, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
void addDirectionalLightDebugBox(const QSSGBoxPoints &box, QSSGDebugDrawSystem *debugDrawSystem)
#define M_PI_2
Definition qmath.h:204
#define M_PI
Definition qmath.h:200
QVarLengthArray< QSSGShaderLight, 16 > QSSGShaderLightList
static void setupCubeShadowCameras(const QSSGLayerRenderData &inData, const QSSGRenderLight *inLight, float shadowMapFar, QSSGRenderCamera inCameras[6])
static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer, QSSGReflectionMapEntry *entry=nullptr, QSSGRenderTextureCubeFace cubeFace=QSSGRenderTextureCubeFaceNone)
static std::unique_ptr< QSSGRenderCamera > computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix, const QMatrix4x4 &lightMatrixInverted, const QVector3D &lightPivot, const QVector3D &lightForward, const QVector3D &lightUp, const float shadowMapResolution, const float pcfRadius, const QSSGBoxPoints &frustumPoints, float frustumStartT, float frustumEndT, float frustumRadius, bool lockShadowmapTexels, const QSSGBounds3 &castingBox, const QSSGBounds3 &receivingBox, QSSGDebugDrawSystem *debugDrawSystem, const bool drawCascades, const bool drawSceneCascadeIntersection)
static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp)
static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGLayerRenderData &inData, QSSGReflectionMapEntry *pEntry, QSSGRhiGraphicsPipelineState *ps, const QSSGRenderableObjectList &sortedOpaqueObjects, QSSGRenderCamera &inCamera, QSSGRenderer &renderer, QSSGRenderTextureCubeFace cubeFace)
static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline, QSSGRhiContext *rhiCtx, const QSSGLayerRenderData &inData, char *ubufData, QSSGRhiGraphicsPipelineState *ps, QSSGSubsetRenderable &subsetRenderable, const QSSGRenderCameraList &cameras, const QVector2D *depthAdjust, const QMatrix4x4 *alteredModelViewProjection)
static QT_BEGIN_NAMESPACE constexpr float QSSG_PI
static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx, const QSSGLayerRenderData &inData, QSSGPassKey passKey, QSSGShadowMapEntry *pEntry, QSSGRhiGraphicsPipelineState *ps, const QVector2D *depthAdjust, const QSSGRenderableObjectList &sortedOpaqueObjects, QSSGRenderCamera &inCamera, bool orthographic, QSSGRenderTextureCubeFace cubeFace, quint32 cascadeIndex)
static void addDepthTextureBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRhiShaderResourceBindingList &bindings)
static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRenderableImage *renderableImage, QSSGRhiShaderResourceBindingList &bindings, bool isCustomMaterialMeshSubset=false)
static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
static void addNormalTextureBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRhiShaderResourceBindingList &bindings)
static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL
static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps, QSSGSubsetRenderable &subsetRenderable, const QSSGShaderFeatures &featureSet)
static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps, QSSGParticlesRenderable &particleRenderable, QSSGRenderLayer::OITMethod method)
static constexpr float QSSG_HALFPI
static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
static void setupCubeReflectionCameras(const QSSGLayerRenderData &inData, const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
static QVarLengthArray< std::unique_ptr< QSSGRenderCamera >, 4 > setupCascadingCamerasForShadowMap(const QSSGLayerRenderData &data, const QSSGRenderCamera &inCamera, const QSSGRenderLight *inLight, const int shadowMapResolution, const float pcfRadius, const float clipNear, const float clipFar, const QSSGBounds3 &castingObjectsBox, const QSSGBounds3 &receivingObjectsBox, bool lockShadowmapTexels, QSSGDebugDrawSystem *debugDrawSystem, bool drawCascades, bool drawSceneCascadeIntersection)