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 ignore, not an error (since non-lighting passes wont need it)
1110 }
1111
1112 // Screen Texture
1113 if (shaderPipeline->screenTexture()) {
1114 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
1115 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
1116 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
1117 // linear min/mag, mipmap filtering depends on the
1118 // texture, with SCREEN_TEXTURE there are no mipmaps, but
1119 // once SCREEN_MIP_TEXTURE is seen the texture (the same
1120 // one) has mipmaps generated.
1121 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
1122 ? QRhiSampler::Linear : QRhiSampler::None;
1123 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
1124 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
1125 if (screenTextureBinding >= 0) {
1126 bindings.addTexture(screenTextureBinding,
1127 QRhiShaderResourceBinding::FragmentStage,
1128 shaderPipeline->screenTexture(), sampler);
1129 }
1130 if (screenTextureArrayBinding >= 0) {
1131 bindings.addTexture(screenTextureArrayBinding,
1132 QRhiShaderResourceBinding::FragmentStage,
1133 shaderPipeline->screenTexture(), sampler);
1134 }
1135 } // else ignore, not an error
1136 }
1137
1138 if (shaderPipeline->lightmapTexture()) {
1139 int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
1140 if (binding >= 0) {
1141 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1142 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1143 bindings.addTexture(binding,
1144 QRhiShaderResourceBinding::FragmentStage,
1145 shaderPipeline->lightmapTexture(), sampler);
1146 } // else ignore, not an error
1147 }
1148 }
1149
1150 // Depth and SSAO textures
1151 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1152
1153 // Normal texture
1154 addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1155
1156 // Instead of always doing a QHash find in srb(), store the binding
1157 // list and the srb object in the per-model+material
1158 // QSSGRhiUniformBufferSet. While this still needs comparing the
1159 // binding list, to see if something has changed, it results in
1160 // significant gains with lots of models in the scene (because the
1161 // srb hash table becomes large then, so avoiding the lookup as
1162 // much as possible is helpful)
1163 QRhiShaderResourceBindings *&srb = dcd.srb;
1164 bool srbChanged = false;
1165 if (!srb || bindings != dcd.bindings) {
1166 srb = rhiCtxD->srb(bindings);
1167 rhiCtxD->releaseCachedSrb(dcd.bindings);
1168 dcd.bindings = bindings;
1169 srbChanged = true;
1170 }
1171
1172 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1173 subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
1174 else
1175 subsetRenderable.rhiRenderData.mainPass.srb = srb;
1176
1177 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
1178 if (dcd.pipeline
1179 && !srbChanged
1180 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
1181 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
1182 && dcd.ps == *ps)
1183 {
1184 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1185 subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
1186 else
1187 subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
1188 } else {
1189 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1190 subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1191 renderPassDescriptor,
1192 srb);
1193 dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1194 } else {
1195 subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1196 renderPassDescriptor,
1197 srb);
1198 dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
1199 }
1200 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
1201 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
1202 dcd.ps = *ps;
1203 }
1204 }
1205 break;
1206 }
1207 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1208 {
1209 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1210 const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1211 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1212
1213 featureSet.set(QSSGShaderFeatures::Feature::LightProbe, inData.layer.lightProbe || material.m_iblProbe);
1214
1215 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
1216 featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
1217
1218 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1219 // Disable tonemapping for the reflection pass
1220 featureSet.disableTonemapping();
1221 }
1222
1223 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1224 featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
1225
1226 customMaterialSystem.rhiPrepareRenderable(ps, passKey, subsetRenderable, featureSet,
1227 material, inData, renderPassDescriptor, samples, viewCount,
1228 alteredCamera, cubeFace, alteredModelViewProjection, entry, oit);
1229 break;
1230 }
1231 case QSSGRenderableObject::Type::Particles:
1232 {
1233 QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
1234 const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable, oit ? inData.layer.oitMethod : QSSGRenderLayer::OITMethod::None);
1235 if (shaderPipeline) {
1236 QSSGParticleRenderer::rhiPrepareRenderable(*shaderPipeline, passKey, rhiCtx, ps, particleRenderable, inData, renderPassDescriptor, samples, viewCount,
1237 alteredCamera, cubeFace, entry);
1238 }
1239 break;
1240 }
1241 }
1242}
1243
1244void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
1245 const QSSGRhiGraphicsPipelineState &state,
1246 QSSGRenderableObject &object,
1247 bool *needsSetViewport,
1248 QSSGRenderTextureCubeFace cubeFace)
1249{
1250 switch (object.type) {
1251 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1252 {
1253 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1254
1255 QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.mainPass.pipeline;
1256 QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.mainPass.srb;
1257
1258 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1259 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
1260 ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1261 srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
1262 }
1263
1264 if (!ps || !srb)
1265 return;
1266
1267 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
1268 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
1269
1270 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1271 // QRhi optimizes out unnecessary binding of the same pipline
1272 cb->setGraphicsPipeline(ps);
1273 cb->setShaderResources(srb);
1274
1275 if (*needsSetViewport) {
1276 cb->setViewport(state.viewport);
1277 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
1278 cb->setScissor(state.scissor);
1279 *needsSetViewport = false;
1280 }
1281
1282 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1283 int vertexBufferCount = 1;
1284 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1285 quint32 instances = 1;
1286 if ( subsetRenderable.modelContext.model.instancing()) {
1287 instances = subsetRenderable.modelContext.model.instanceCount();
1288 // If the instance count is 0, the bail out before trying to do any
1289 // draw calls. Making an instanced draw call with a count of 0 is invalid
1290 // for Metal and likely other API's as well.
1291 // It is possible that the particale system may produce 0 instances here
1292 if (instances == 0)
1293 return;
1294 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
1295 vertexBufferCount = 2;
1296 }
1297 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1298 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
1299 cb->setStencilRef(state.stencilRef);
1300 if (indexBuffer) {
1301 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
1302 cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
1303 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
1304 } else {
1305 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1306 cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
1307 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
1308 }
1309 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
1310 QVector<int>({subsetRenderable.modelContext.model.profilingId,
1311 subsetRenderable.material.profilingId}));
1312 break;
1313 }
1314 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1315 {
1316 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1317 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1318 customMaterialSystem.rhiRenderRenderable(rhiCtx, subsetRenderable, needsSetViewport, cubeFace, state);
1319 break;
1320 }
1321 case QSSGRenderableObject::Type::Particles:
1322 {
1323 QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
1324 QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
1325 break;
1326 }
1327 }
1328}
1329
1330static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp) {
1331 // Convert normalized offsets/scales to actual pixel values
1332 float x = atlasEntry.uOffset * atlasPixelSize.width();
1333 float w = atlasEntry.uvScale * atlasPixelSize.width();
1334 float h = atlasEntry.uvScale * atlasPixelSize.height();
1335
1336 // Y in atlasEntry is top-based, whereas QRhiViewport is bottom-based
1337 // topY in pixel = atlasEntry.vOffset * atlasPixelSize.height()
1338 // so bottom-left Y = totalHeight - topY - h
1339 float y;
1340 if (!yIsUp)
1341 y = atlasPixelSize.height() - (atlasEntry.vOffset * atlasPixelSize.height()) - h;
1342 else
1343 y = atlasEntry.vOffset * atlasPixelSize.height();
1344
1345 return QRhiViewport(x, y, w, h);
1346}
1347
1348void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
1349 QSSGPassKey passKey,
1350 QSSGRhiGraphicsPipelineState &ps,
1351 QSSGRenderShadowMap &shadowMapManager,
1352 const QSSGRenderCamera &camera,
1353 QSSGRenderCamera *debugCamera,
1354 const QSSGShaderLightList &globalLights,
1355 const QSSGRenderableObjectList &sortedOpaqueObjects,
1356 QSSGRenderer &renderer,
1357 const QSSGBounds3 &castingObjectsBox,
1358 const QSSGBounds3 &receivingObjectsBox)
1359{
1360 const QSSGLayerRenderData &layerData = *QSSGLayerRenderData::getCurrent(renderer);
1361 QSSGDebugDrawSystem *debugDrawSystem = renderer.contextInterface()->debugDrawSystem().get();
1362 const bool drawDirectionalLightShadowBoxes = layerData.layer.drawDirectionalLightShadowBoxes;
1363 const bool drawPointLightShadowBoxes = layerData.layer.drawPointLightShadowBoxes;
1364 const bool drawShadowCastingBounds = layerData.layer.drawShadowCastingBounds;
1365 const bool drawShadowReceivingBounds = layerData.layer.drawShadowReceivingBounds;
1366 const bool drawCascades = layerData.layer.drawCascades;
1367 const bool drawSceneCascadeIntersection = layerData.layer.drawSceneCascadeIntersection;
1368 const bool disableShadowCameraUpdate = layerData.layer.disableShadowCameraUpdate;
1369 const bool drawCulledObjects = layerData.layer.drawCulledObjects;
1370 QVector<bool> debugIsObjectCulled = drawCulledObjects ? QVector<bool>(sortedOpaqueObjects.size(), true) : QVector<bool>();
1371
1372 static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
1373 QSSGRhiGraphicsPipelineState *ps,
1374 const QSSGRenderableObjectList &sortedOpaqueObjects,
1375 int cubeFace,
1376 const QSSGBounds3 cameraBounds,
1377 QVector<bool> &debugIsObjectCulled,
1378 bool drawCulledObjects) {
1379 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1380 bool needsSetViewport = true;
1381
1382 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1383 const QSSGRenderableObjectHandle &handle = sortedOpaqueObjects[i];
1384 QSSGRenderableObject *theObject = handle.obj;
1385
1386 // Only attempt to cull models if both of its bounds are valid
1387 if (theObject->globalBoundsInstancing.isFinite() && theObject->globalBounds.isFinite()) {
1388 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1389 : theObject->globalBounds;
1390 if (!globalBounds.isEmpty() && !cameraBounds.intersects(globalBounds)) {
1391 continue;
1392 }
1393 }
1394
1395 if (Q_UNLIKELY(drawCulledObjects))
1396 debugIsObjectCulled[i] = false;
1397
1398 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1399 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1400 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
1401
1402 QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
1403 QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
1404 ? renderable->subset.rhi.indexBuffer->buffer()
1405 : nullptr;
1406
1407 // Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
1408 if (!renderable->rhiRenderData.shadowPass.pipeline)
1409 continue;
1410
1411 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1412
1413 cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
1414
1415 QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
1416 cb->setShaderResources(srb);
1417
1418 if (needsSetViewport) {
1419 cb->setViewport(ps->viewport);
1420 needsSetViewport = false;
1421 }
1422
1423 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1424 int vertexBufferCount = 1;
1425 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1426 quint32 instances = 1;
1427 if (renderable->modelContext.model.instancing()) {
1428 instances = renderable->modelContext.model.instanceCount();
1429 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
1430 vertexBufferCount = 2;
1431 }
1432 if (indexBuffer) {
1433 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, renderable->subset.rhi.indexBuffer->indexFormat());
1434 cb->drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances, renderable->subset.lodOffset(renderable->subsetLevelOfDetail));
1435 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances));
1436 } else {
1437 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1438 cb->draw(renderable->subset.count, instances, renderable->subset.offset);
1439 QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
1440 }
1441 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
1442 QVector<int>({renderable->modelContext.model.profilingId,
1443 renderable->material.profilingId}));
1444 }
1445 }
1446 };
1447
1448 static const auto rhiClearShadowMap = [](QSSGRenderer &renderer, QSSGRenderShadowMap &shadowMapManager, QSSGRhiContext *rhiCtx, QSSGRhiGraphicsPipelineState *ps, QRhiRenderPassDescriptor *renderPassDesc) {
1449 auto clearShadowMapShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiClearShadowMapShader();
1450 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, clearShadowMapShaderPipeline.get());
1451
1452 // Disable Depth Test and Depth Write
1453 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, false);
1454 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1455 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, shadowMapManager.shadowClearSrb(), renderPassDesc, {});
1456 // Reset
1457 ps->flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1458 };
1459
1460 QRhi *rhi = rhiCtx->rhi();
1461 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1462
1463 // We need to deal with a clip depth range of [0, 1] or
1464 // [-1, 1], depending on the graphics API underneath.
1465 QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
1466 if (rhi->isClipDepthZeroToOne()) {
1467 // d is [0, 1] so no need for any mapping
1468 depthAdjust[0] = 0.0f;
1469 depthAdjust[1] = 1.0f;
1470 } else {
1471 // d is [-1, 1]
1472 depthAdjust[0] = 1.0f;
1473 depthAdjust[1] = 0.5f;
1474 }
1475
1476 if (drawShadowCastingBounds)
1477 ShadowmapHelpers::addDebugBox(castingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Red, debugDrawSystem);
1478 if (drawShadowReceivingBounds)
1479 ShadowmapHelpers::addDebugBox(receivingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Green, debugDrawSystem);
1480
1481 // Create shadow map for each light in the scene
1482 const QSize atlasTextureSize = shadowMapManager.shadowMapAtlasTexture()->pixelSize();
1483 // Make sure quad renderer is ready
1484 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
1485 for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
1486 if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
1487 continue;
1488
1489 QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(i);
1490 if (!pEntry)
1491 continue;
1492
1493 const auto &light = globalLights[i].light;
1494
1495 if (!shadowMapManager.shadowMapAtlasTexture())
1496 break;
1497
1498 if (light->type == QSSGRenderLight::Type::DirectionalLight || light->type == QSSGRenderLight::Type::SpotLight) {
1499 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1500
1501 // This is just a way to store the old camera so we can use it for debug
1502 // drawing. There are probably cleaner ways to do this
1503 if (!disableShadowCameraUpdate && debugCamera) {
1504 debugCamera->clipPlanes = camera.clipPlanes;
1505 debugCamera->projection = camera.projection;
1506 // NOTE: Since the debug camera is an internally injected camera, there will only be
1507 // the local transform. Anywhere the global transform is looked up for the debug camera
1508 // it will return the local transform.
1509 debugCamera->localTransform = layerData.getGlobalTransform(camera);
1510 }
1511
1512 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
1513 if (light->type == QSSGRenderLight::Type::DirectionalLight) {
1514 const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
1515 const float clipNear = camera.clipPlanes.clipNear();
1516 const float clipFar = qMin(light->m_shadowMapFar, camera.clipPlanes.clipFar());
1517 const float clipRange = clipFar - clipNear;
1518 cascades = setupCascadingCamerasForShadowMap(layerData,
1519 disableShadowCameraUpdate ? *debugCamera : camera,
1520 light,
1521 size.width(),
1522 pcfRadius,
1523 clipNear,
1524 clipFar,
1525 castingObjectsBox,
1526 receivingObjectsBox,
1527 light->m_lockShadowmapTexels,
1528 debugDrawSystem,
1529 drawCascades,
1530 drawSceneCascadeIntersection);
1531
1532 // Write the split distances from value 0 in the z-axis of the eye view-space
1533 pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
1534 pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
1535 pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
1536 pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
1537 pEntry->m_shadowMapFar = clipFar;
1538 } else if (light->type == QSSGRenderLight::Type::SpotLight) {
1539 auto spotlightCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderCamera::Type::PerspectiveCamera);
1540 spotlightCamera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(light->m_coneAngle * 2.0f);
1541 spotlightCamera->clipPlanes = { 1.0f, light->m_shadowMapFar };
1542 const QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(*light);
1543 const QVector3D lightDir = QSSGRenderNode::getDirection(lightGlobalTransform);
1544 const QVector3D lightPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform) - lightDir * spotlightCamera->clipPlanes.clipNear();
1545 const QVector3D lightPivot = light->pivot;
1546 const QVector3D forward = lightDir.normalized();
1547 const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
1548 ? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
1549 : QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
1550 const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
1551 spotlightCamera->localTransform = QSSGRenderNode::calculateTransformMatrix(lightPos,
1552 QSSGRenderNode::initScale,
1553 lightPivot,
1554 QQuaternion::fromDirection(forward, up));
1555 QRectF theViewport(0.0f, 0.0f, (float)light->m_shadowMapRes, (float)light->m_shadowMapRes);
1556 QSSGRenderCamera::calculateProjectionInternal(*spotlightCamera, theViewport);
1557 cascades.push_back(std::move(spotlightCamera));
1558 pEntry->m_shadowMapFar = light->m_shadowMapFar;
1559 } else {
1560 Q_UNREACHABLE();
1561 }
1562
1563 memset(pEntry->m_csmActive, 0, sizeof(pEntry->m_csmActive));
1564
1565 QMatrix4x4 cascadeCameraGlobalTransforms(Qt::Uninitialized);
1566 const QMatrix4x4 bias = { 0.5, 0.0, 0.0, 0.5,
1567 0.0, 0.5, 0.0, 0.5,
1568 0.0, 0.0, 0.5, 0.5,
1569 0.0, 0.0, 0.0, 1.0 };
1570
1571 for (int cascadeIndex = 0; cascadeIndex < cascades.length(); cascadeIndex++) {
1572 const auto &cascadeCamera = cascades[cascadeIndex];
1573 if (!cascadeCamera)
1574 continue;
1575
1576 cascadeCameraGlobalTransforms = layerData.getGlobalTransform(*cascadeCamera);
1577 pEntry->m_csmActive[cascadeIndex] = 1.f;
1578 QMatrix4x4 &viewProjection = pEntry->m_lightViewProjection[cascadeIndex];
1579 cascadeCamera->calculateViewProjectionMatrix(cascadeCameraGlobalTransforms, viewProjection);
1580 pEntry->m_lightViewProjection[cascadeIndex] = viewProjection;
1581 pEntry->m_fixedScaleBiasMatrix[cascadeIndex] = bias * viewProjection;
1582 const QMatrix4x4 inverted = viewProjection.inverted();
1583 const float x = 0.5f / (inverted * QVector4D(1, 0, 0, 0)).length();
1584 const float y = 0.5f / (inverted * QVector4D(0, 1, 0, 0)).length();
1585 const float z = 0.5f / (inverted * QVector4D(0, 0, 1, 0)).length();
1586 const QSSGBoxPoints frustumPoints = computeFrustumBounds(viewProjection);
1587 const QSSGBounds3 bounds = QSSGBounds3(frustumPoints);
1588 pEntry->m_dimensionsInverted[cascadeIndex] = QVector4D(x, y, z, 0.0f);
1589 pEntry->m_lightView = cascadeCameraGlobalTransforms.inverted(); // pre-calculate this for the material
1590 const bool isOrtho = cascadeCamera->type == QSSGRenderGraphObject::Type::OrthographicCamera;
1591 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[cascadeIndex], rhi->isYUpInFramebuffer());
1592 rhiPrepareResourcesForShadowMap(rhiCtx, layerData, passKey, pEntry, &ps, &depthAdjust, sortedOpaqueObjects, *cascadeCamera, isOrtho, QSSGRenderTextureCubeFaceNone, cascadeIndex);
1593 // Render into the 2D texture pEntry->m_rhiDepthMap, using
1594 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1595 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[cascadeIndex];
1596 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1597 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1598 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1599 rhiClearShadowMap(renderer, shadowMapManager, rhiCtx, &ps, rt->renderPassDescriptor());
1600 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0, bounds, debugIsObjectCulled, drawCulledObjects);
1601 cb->endPass();
1602 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1603
1604 if (drawDirectionalLightShadowBoxes) {
1605 ShadowmapHelpers::addDirectionalLightDebugBox(frustumPoints, debugDrawSystem);
1606 }
1607 }
1608 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
1609 } else { // Point Light
1610 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1611 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1612
1613 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1614 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1615 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1616 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1617 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1618 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1619 const float shadowMapFar = qMax<float>(2.0f, light->m_shadowMapFar);
1620 setupCubeShadowCameras(layerData, light, shadowMapFar, theCameras);
1621 pEntry->m_lightView = QMatrix4x4();
1622 pEntry->m_shadowMapFar = shadowMapFar;
1623
1624 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1625 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1626 for (const auto face : QSSGRenderTextureCubeFaces) {
1627 cameraGlobalTransform = layerData.getGlobalTransform(theCameras[quint8(face)]);
1628 theCameras[quint8(face)].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_lightViewProjection[0]);
1629 pEntry->m_lightCubeView[quint8(face)] = cameraGlobalTransform.inverted(); // pre-calculate this for the material
1630
1631 rhiPrepareResourcesForShadowMap(rhiCtx,
1632 layerData,
1633 passKey,
1634 pEntry,
1635 &ps,
1636 &depthAdjust,
1637 sortedOpaqueObjects,
1638 theCameras[quint8(face)],
1639 false,
1640 face,
1641 0);
1642 }
1643
1644 // The bounds should be the same for all view projections of the cube
1645 const QVector3D center = QSSGRenderNode::getGlobalPos(layerData.getGlobalTransform(*light));
1646 const QSSGBounds3 bounds = QSSGBounds3(center - QVector3D(shadowMapFar, shadowMapFar, shadowMapFar),
1647 center + QVector3D(shadowMapFar, shadowMapFar, shadowMapFar));
1648
1649 for (const auto face : QSSGRenderTextureCubeFaces) {
1650 // Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
1651 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1652
1653 QSSGRenderTextureCubeFace outFace = face;
1654 // FACE S T GL
1655 // +x -z, -y right
1656 // -x +z, -y left
1657 // +y +x, +z top
1658 // -y +x, -z bottom
1659 // +z +x, -y front
1660 // -z -x, -y back
1661 // FACE S T D3D
1662 // +x -z, +y right
1663 // -x +z, +y left
1664 // +y +x, -z bottom
1665 // -y +x, +z top
1666 // +z +x, +y front
1667 // -z -x, +y back
1668 if (swapYFaces) {
1669 // +Y and -Y faces get swapped (D3D, Vulkan, Metal).
1670 // See shadowMapping.glsllib. This is complemented there by reversing T as well.
1671 if (outFace == QSSGRenderTextureCubeFace::PosY)
1672 outFace = QSSGRenderTextureCubeFace::NegY;
1673 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1674 outFace = QSSGRenderTextureCubeFace::PosY;
1675 }
1676 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargetCube[quint8(outFace)];
1677 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1678 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1679 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1680 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face), bounds, debugIsObjectCulled, drawCulledObjects);
1681 cb->endPass();
1682 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1683 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
1684 }
1685
1686 // Render the CubeMap into the shadowMapAtlasTexture
1687 // NOTE: These passes don't require a manual clear because we will always write every pixel in that atlas space
1688
1689 // Render the front hemisphere of the cube map into the shadow map atlas
1690 QRhiTextureRenderTarget *rtFront = pEntry->m_rhiRenderTargets[0]; // A layer in the Atlas
1691 QRhiRenderPassDescriptor *frontDesc = pEntry->m_rhiRenderPassDesc[0];
1692 auto atlasShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiCubeMapToAtlasShader();
1693 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[0], rhi->isYUpInFramebuffer());
1694 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, atlasShaderPipeline.get());
1695 QRhiShaderResourceBindings *srb = pEntry->m_cubeToAtlasFrontSrb;
1696 cb->beginPass(rtFront, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1697 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtFront));
1698 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1699
1700 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, frontDesc, QSSGRhiQuadRenderer::UvCoords);
1701
1702 cb->endPass();
1703 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1704 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 6, 0));
1705
1706 // Render the back hemisphere of the cube map into the shadow map atlas
1707 QRhiTextureRenderTarget *rtBack = pEntry->m_rhiRenderTargets[1]; // A layer in the Atlas
1708 QRhiRenderPassDescriptor *backDesc = pEntry->m_rhiRenderPassDesc[1];
1709 srb = pEntry->m_cubeToAtlasBackSrb;
1710 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[1], rhi->isYUpInFramebuffer());
1711 cb->beginPass(rtBack, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1712 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtBack));
1713 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1714
1715 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, backDesc, QSSGRhiQuadRenderer::UvCoords);
1716
1717 cb->endPass();
1718 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1719 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 7, 0));
1720
1721
1722 // reset pipeline (This part is necessary, but not ideal)
1723 ps = layerData.getPipelineState();
1724 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1725 ps.depthBias = 2;
1726 ps.slopeScaledDepthBias = 1.5f;
1727
1728 if (drawPointLightShadowBoxes) {
1729 ShadowmapHelpers::addDebugBox(bounds.toQSSGBoxPoints(), QColorConstants::Yellow, debugDrawSystem);
1730 }
1731 }
1732 }
1733
1734 if (Q_UNLIKELY(drawCulledObjects)) {
1735 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1736 QSSGRenderableObject *theObject = sortedOpaqueObjects[i].obj;
1737 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1738 : theObject->globalBounds;
1739 const QColor color = debugIsObjectCulled[i] ? QColorConstants::Red : QColorConstants::Green;
1740 ShadowmapHelpers::addDebugBox(globalBounds.toQSSGBoxPointsNoEmptyCheck(), color, debugDrawSystem);
1741 }
1742 }
1743}
1744
1745void RenderHelpers::rhiRenderReflectionMap(QSSGRhiContext *rhiCtx,
1746 QSSGPassKey passKey,
1747 const QSSGLayerRenderData &inData,
1748 QSSGRhiGraphicsPipelineState *ps,
1749 QSSGRenderReflectionMap &reflectionMapManager,
1750 const QVector<QSSGRenderReflectionProbe *> &reflectionProbes,
1751 const QSSGRenderableObjectList &reflectionPassObjects,
1752 QSSGRenderer &renderer)
1753{
1754 QRhi *rhi = rhiCtx->rhi();
1755 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1756
1757 const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox ||
1758 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1759 && rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch);
1760
1761 for (int i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
1762 QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(i);
1763 if (!pEntry)
1764 continue;
1765
1766 if (!pEntry->m_needsRender)
1767 continue;
1768
1769 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
1770 continue;
1771
1772 if (reflectionProbes[i]->texture)
1773 continue;
1774
1775 Q_ASSERT(pEntry->m_rhiDepthStencil);
1776 Q_ASSERT(pEntry->m_rhiCube);
1777
1778 const QSize size = pEntry->m_rhiCube->pixelSize();
1779 ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1780
1781 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1782 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1783 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1784 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1785 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1786 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1787 setupCubeReflectionCameras(inData, reflectionProbes[i], theCameras);
1788 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1789 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1790 for (const auto face : QSSGRenderTextureCubeFaces) {
1791 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
1792 cameraGlobalTransform = inData.getGlobalTransform(theCameras[cubeFaceIdx]);
1793 theCameras[cubeFaceIdx].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_viewProjection);
1794
1795 rhiPrepareResourcesForReflectionMap(rhiCtx, passKey, inData, pEntry, ps,
1796 reflectionPassObjects, theCameras[cubeFaceIdx], renderer, face);
1797 }
1798 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
1799 for (auto face : QSSGRenderTextureCubeFaces) {
1800 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1801 face = pEntry->m_timeSliceFace;
1802
1803 QSSGRenderTextureCubeFace outFace = face;
1804 // Faces are swapped similarly to shadow maps due to differences in backends
1805 // Prefilter step handles correcting orientation differences in the final render
1806 if (swapYFaces) {
1807 if (outFace == QSSGRenderTextureCubeFace::PosY)
1808 outFace = QSSGRenderTextureCubeFace::NegY;
1809 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1810 outFace = QSSGRenderTextureCubeFace::PosY;
1811 }
1812 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1813 cb->beginPass(rt, reflectionProbes[i]->clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1814 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1815 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1816
1817 if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
1818 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1819 const bool isSkyBox = inData.layer.background == QSSGRenderLayer::Background::SkyBox;
1820 const auto &shaderPipeline = isSkyBox ? shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(QSSGRenderLayer::TonemapMode::None, inData.layer.skyBoxIsRgbe8, 1)
1821 : shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(QSSGRenderLayer::TonemapMode::None, !inData.layer.skyBoxIsSrgb, 1);
1822 Q_ASSERT(shaderPipeline);
1823 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
1824 QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
1825 if (!renderPassDesc)
1826 renderPassDesc = rt->newCompatibleRenderPassDescriptor();
1827 rt->setRenderPassDescriptor(renderPassDesc);
1828 isSkyBox ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, renderPassDesc, {})
1829 : renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, renderPassDesc, {});
1830 }
1831
1832 bool needsSetViewport = true;
1833 for (const auto &handle : reflectionPassObjects)
1834 rhiRenderRenderable(rhiCtx, *ps, *handle.obj, &needsSetViewport, face);
1835
1836 cb->endPass();
1837 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1838 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
1839
1840 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1841 break;
1842 }
1843 if (renderPassDesc)
1844 renderPassDesc->deleteLater();
1845
1846 pEntry->renderMips(rhiCtx);
1847
1848 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1849 pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(pEntry->m_timeSliceFace); // Wraps
1850
1851 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
1852 pEntry->m_rendered = true;
1853
1854 reflectionProbes[i]->hasScheduledUpdate = false;
1855 pEntry->m_needsRender = false;
1856 }
1857}
1858
1859bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx,
1860 const QSize &size,
1861 QSSGRhiRenderableTexture *renderableTex,
1862 quint8 viewCount)
1863{
1864 QRhi *rhi = rhiCtx->rhi();
1865 bool needsBuild = false;
1866
1867 if (!renderableTex->texture) {
1868 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1869 // the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
1870 if (viewCount <= 1)
1871 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
1872 else
1873 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
1874 needsBuild = true;
1875 } else if (renderableTex->texture->pixelSize() != size) {
1876 renderableTex->texture->setPixelSize(size);
1877 needsBuild = true;
1878 }
1879
1880 if (needsBuild) {
1881 if (!renderableTex->texture->create()) {
1882 qWarning("Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
1883 renderableTex->reset();
1884 return false;
1885 }
1886 renderableTex->resetRenderTarget();
1887 QRhiTextureRenderTargetDescription desc;
1888 QRhiColorAttachment colorAttachment(renderableTex->texture);
1889 colorAttachment.setMultiViewCount(viewCount);
1890 desc.setColorAttachments({ colorAttachment });
1891 renderableTex->rt = rhi->newTextureRenderTarget(desc);
1892 renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
1893 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
1894 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
1895 if (!renderableTex->rt->create()) {
1896 qWarning("Failed to build render target for ambient occlusion texture");
1897 renderableTex->reset();
1898 return false;
1899 }
1900 }
1901
1902 return true;
1903}
1904
1905void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
1906 QSSGPassKey passKey,
1907 QSSGRenderer &renderer,
1908 QSSGRhiShaderPipeline &shaderPipeline,
1909 QSSGRhiGraphicsPipelineState &ps,
1910 const QSSGAmbientOcclusionSettings &ao,
1911 const QSSGRhiRenderableTexture &rhiAoTexture,
1912 const QSSGRhiRenderableTexture &rhiDepthTexture,
1913 const QSSGRenderCamera &camera)
1914{
1915 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1916
1917 // no texelFetch in GLSL <= 120 and GLSL ES 100
1918 if (!rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
1919 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1920 // just clear and stop there
1921 cb->beginPass(rhiAoTexture.rt, Qt::white, { 1.0f, 0 });
1922 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
1923 cb->endPass();
1924 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1925 return;
1926 }
1927
1928 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, &shaderPipeline);
1929
1930 const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
1931 const QSize textureSize = rhiAoTexture.texture->pixelSize();
1932 const float rw = float(textureSize.width());
1933 const float rh = float(textureSize.height());
1934 const float fov = camera.fov.asVerticalFov(rw / rh).radians();
1935 const float tanHalfFovY = tanf(0.5f * fov * (rh / rw));
1936 const float invFocalLenX = tanHalfFovY * (rw / rh);
1937
1938 const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
1939 const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
1940 const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
1941 const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
1942 const QVector2D cameraProps = camera.clipPlanes;
1943
1944 // layout(std140, binding = 0) uniform buf {
1945 // vec4 aoProperties;
1946 // vec4 aoProperties2;
1947 // vec4 aoScreenConst;
1948 // vec4 uvToEyeConst;
1949 // vec2 cameraProperties;
1950
1951 const int UBUF_SIZE = 72;
1952 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 }));
1953 if (!dcd.ubuf) {
1954 dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
1955 dcd.ubuf->create();
1956 }
1957
1958 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1959 memcpy(ubufData, &aoProps, 16);
1960 memcpy(ubufData + 16, &aoProps2, 16);
1961 memcpy(ubufData + 32, &aoScreenConst, 16);
1962 memcpy(ubufData + 48, &uvToEyeConst, 16);
1963 memcpy(ubufData + 64, &cameraProps, 8);
1964 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1965
1966 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
1967 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1968 QSSGRhiShaderResourceBindingList bindings;
1969 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
1970 // binding 1 is either a sampler2D or sampler2DArray, matching
1971 // rhiDepthTexture.texture, no special casing needed here
1972 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiDepthTexture.texture, sampler);
1973 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1974
1975 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
1976 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &ps, srb, rhiAoTexture.rt, {});
1977}
1978
1979bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx,
1980 const QSize &size,
1981 bool wantsMips,
1982 QSSGRhiRenderableTexture *renderableTex,
1983 quint8 viewCount)
1984{
1985 QRhi *rhi = rhiCtx->rhi();
1986 bool needsBuild = false;
1987 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1988 if (wantsMips)
1989 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
1990
1991 if (!renderableTex->texture) {
1992 // always non-msaa, even if multisampling is used in the main pass
1993 if (viewCount <= 1)
1994 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
1995 else
1996 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
1997 needsBuild = true;
1998 } else if (renderableTex->texture->pixelSize() != size) {
1999 renderableTex->texture->setPixelSize(size);
2000 needsBuild = true;
2001 }
2002
2003 if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
2004 if (viewCount <= 1)
2005 renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
2006 else
2007 renderableTex->depthTexture = rhi->newTextureArray(QRhiTexture::D24S8, viewCount, size, 1, QRhiTexture::RenderTarget);
2008 needsBuild = true;
2009 } else {
2010 if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
2011 renderableTex->depthStencil->setPixelSize(size);
2012 needsBuild = true;
2013 } else if (renderableTex->depthTexture && renderableTex->depthTexture->pixelSize() != size) {
2014 renderableTex->depthTexture->setPixelSize(size);
2015 needsBuild = true;
2016 }
2017 }
2018
2019 if (needsBuild) {
2020 if (!renderableTex->texture->create()) {
2021 qWarning("Failed to build screen texture (size %dx%d)", size.width(), size.height());
2022 renderableTex->reset();
2023 return false;
2024 }
2025 if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
2026 qWarning("Failed to build depth-stencil buffer for screen texture (size %dx%d)",
2027 size.width(), size.height());
2028 renderableTex->reset();
2029 return false;
2030 } else if (renderableTex->depthTexture && !renderableTex->depthTexture->create()) {
2031 qWarning("Failed to build depth-stencil texture array (multiview) for screen texture (size %dx%d)",
2032 size.width(), size.height());
2033 renderableTex->reset();
2034 return false;
2035 }
2036 renderableTex->resetRenderTarget();
2037 QRhiTextureRenderTargetDescription desc;
2038 QRhiColorAttachment colorAttachment(renderableTex->texture);
2039 colorAttachment.setMultiViewCount(viewCount);
2040 desc.setColorAttachments({ colorAttachment });
2041 if (renderableTex->depthStencil)
2042 desc.setDepthStencilBuffer(renderableTex->depthStencil);
2043 else if (renderableTex->depthTexture)
2044 desc.setDepthTexture(renderableTex->depthTexture);
2045 renderableTex->rt = rhi->newTextureRenderTarget(desc);
2046 renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
2047 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2048 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2049 if (!renderableTex->rt->create()) {
2050 qWarning("Failed to build render target for screen texture");
2051 renderableTex->reset();
2052 return false;
2053 }
2054 }
2055
2056 return true;
2057}
2058
2059void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
2060{
2061 QSSG_ASSERT(layer.renderData, return);
2062
2063 const auto *renderData = layer.renderData;
2064
2065 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2066 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2067 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
2068
2069 QSSGRhiShaderResourceBindingList bindings;
2070
2071 int uniformBinding = 0;
2072 const int ubufSize = cameras.count() >= 2 ? 276 : 148;
2073
2074 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 })); // Change to Grid?
2075
2076 QRhi *rhi = rhiCtx->rhi();
2077 if (!dcd.ubuf) {
2078 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2079 dcd.ubuf->create();
2080 }
2081
2082 // Param
2083 const auto clipPlanes = cameras[0]->clipPlanes;
2084 const float scale = layer.gridScale;
2085 const quint32 gridFlags = layer.gridFlags;
2086
2087 const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2088
2089 quint32 ubufOffset = 0;
2090 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2091
2092 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
2093 QMatrix4x4 viewProj(Qt::Uninitialized);
2094 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2095 cameraGlobalTransform = renderData->getGlobalTransform(*cameras[viewIdx]);
2096 cameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProj);
2097 QMatrix4x4 invViewProj = viewProj.inverted();
2098 quint32 viewDataOffset = ubufOffset;
2099 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProj.constData(), 64);
2100 viewDataOffset += 64 * cameras.count();
2101 memcpy(ubufData + viewDataOffset + viewIdx * 64, invViewProj.constData(), 64);
2102 }
2103 ubufOffset += (64 + 64) * cameras.count();
2104
2105 memcpy(ubufData + ubufOffset, &clipPlanes, 8);
2106 ubufOffset += 8;
2107 memcpy(ubufData + ubufOffset, &scale, 4);
2108 ubufOffset += 4;
2109 memcpy(ubufData + ubufOffset, &yFactor, 4);
2110 ubufOffset += 4;
2111 memcpy(ubufData + ubufOffset, &gridFlags, 4);
2112
2113 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2114
2115 bindings.addUniformBuffer(uniformBinding, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2116
2117 layer.gridSrb = rhiCtxD->srb(bindings);
2118 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2119
2120 cb->debugMarkEnd();
2121}
2122
2123static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx,
2124 QSSGPassKey passKey,
2125 QSSGRenderLayer &layer,
2126 QSSGRenderCameraList &cameras,
2127 QSSGRenderer &renderer,
2128 QSSGReflectionMapEntry *entry = nullptr,
2129 QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone,
2130 uint tonemapMode = 0)
2131{
2132 QSSG_ASSERT(layer.renderData, return);
2133
2134 const auto *renderData = layer.renderData;
2135
2136 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2137 const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
2138 const QSSGRenderImageTexture lightProbeTexture =
2139 cubeMapMode ? renderer.contextInterface()->bufferManager()->loadRenderImage(layer.skyBoxCubeMap, QSSGBufferManager::MipModeDisable)
2140 : renderer.contextInterface()->bufferManager()->loadRenderImage(layer.lightProbe, QSSGBufferManager::MipModeBsdf);
2141 const bool hasValidTexture = lightProbeTexture.m_texture != nullptr;
2142 if (hasValidTexture) {
2143 if (cubeFace == QSSGRenderTextureCubeFaceNone)
2144 layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
2145 if (cubeMapMode)
2146 layer.skyBoxIsSrgb = !lightProbeTexture.m_flags.isLinear();
2147
2148 QSSGRhiShaderResourceBindingList bindings;
2149
2150 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
2151 QRhiSampler::Linear,
2152 cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
2153 QRhiSampler::Repeat,
2154 QRhiSampler::ClampToEdge,
2155 QRhiSampler::Repeat });
2156 int samplerBinding = 1; //the shader code is hand-written, so we don't need to look that up
2157 const quint32 ubufSize = cameras.count() >= 2 ? 416 : 240; // same ubuf layout for both skybox and skyboxcube
2158 bindings.addTexture(samplerBinding,
2159 QRhiShaderResourceBinding::FragmentStage,
2160 lightProbeTexture.m_texture, sampler);
2161
2162 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2163 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
2164 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, nullptr, entry, entryIdx });
2165
2166 QRhi *rhi = rhiCtx->rhi();
2167 if (!dcd.ubuf) {
2168 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2169 dcd.ubuf->create();
2170 }
2171
2172 float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2173 const float exposure = layer.lightProbeSettings.probeExposure;
2174 // orientation
2175 const QMatrix3x3 &rotationMatrix(layer.lightProbeSettings.probeOrientation);
2176
2177 // The cubemap shader doesn't use blur or mipmapping, so it uses those for tonemapping and texture color space
2178 const float blurAmountOrSrgb = cubeMapMode ? layer.skyBoxIsSrgb : layer.skyboxBlurAmount;
2179 const float maxMipLevelOrTonemapMode = cubeMapMode ? float(tonemapMode) : float(lightProbeTexture.m_mipmapCount - 2);
2180
2181 const QVector4D skyboxProperties = {
2182 adjustY,
2183 exposure,
2184 blurAmountOrSrgb,
2185 maxMipLevelOrTonemapMode
2186 };
2187
2188 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2189 quint32 ubufOffset = 0;
2190 // skyboxProperties
2191 memcpy(ubufData + ubufOffset, &skyboxProperties, 16);
2192 ubufOffset += 16;
2193 // orientation
2194 memcpy(ubufData + ubufOffset, rotationMatrix.constData(), 12);
2195 ubufOffset += 16;
2196 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 12, 12);
2197 ubufOffset += 16;
2198 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 24, 12);
2199 ubufOffset += 16;
2200
2201 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2202 const QMatrix4x4 &inverseProjection = cameras[viewIdx]->projection.inverted();
2203 const QMatrix4x4 &viewMatrix = renderData->getGlobalTransform(*cameras[viewIdx]);
2204 QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
2205 cameras[viewIdx]->calculateViewProjectionWithoutTranslation(viewMatrix, 0.1f, 5.0f, viewProjection);
2206
2207 quint32 viewDataOffset = ubufOffset;
2208 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProjection.constData(), 64);
2209 viewDataOffset += cameras.count() * 64;
2210 memcpy(ubufData + viewDataOffset + viewIdx * 64, inverseProjection.constData(), 64);
2211 viewDataOffset += cameras.count() * 64;
2212 memcpy(ubufData + viewDataOffset + viewIdx * 48, viewMatrix.constData(), 48);
2213 }
2214 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2215
2216 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2217
2218 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
2219 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2220 entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtxD->srb(bindings);
2221 } else {
2222 layer.skyBoxSrb = rhiCtxD->srb(bindings);
2223 }
2224
2225 if (cubeMapMode)
2226 renderer.rhiCubeRenderer()->prepareCube(rhiCtx, nullptr);
2227 else
2228 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2229 }
2230}
2231
2232void RenderHelpers::rhiPrepareSkyBox(QSSGRhiContext *rhiCtx,
2233 QSSGPassKey passKey,
2234 QSSGRenderLayer &layer,
2235 QSSGRenderCameraList &cameras,
2236 QSSGRenderer &renderer,
2237 uint tonemapMode)
2238{
2239 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2240 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
2241
2242 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, nullptr, QSSGRenderTextureCubeFaceNone, tonemapMode);
2243
2244 cb->debugMarkEnd();
2245}
2246
2247void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(QSSGRhiContext *rhiCtx,
2248 QSSGPassKey passKey,
2249 QSSGRenderLayer &layer,
2250 QSSGRenderCamera &inCamera,
2251 QSSGRenderer &renderer,
2253 QSSGRenderTextureCubeFace cubeFace)
2254{
2255 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2256 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
2257
2258 QSSGRenderCameraList cameras({ &inCamera });
2259 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, entry, cubeFace);
2260
2261 cb->debugMarkEnd();
2262}
2263
2264bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
2265 QSSGPassKey passKey,
2266 const QSSGRhiGraphicsPipelineState &basePipelineState,
2267 QRhiRenderPassDescriptor *rpDesc,
2268 QSSGLayerRenderData &inData,
2269 const QSSGRenderableObjectList &sortedOpaqueObjects,
2270 const QSSGRenderableObjectList &sortedTransparentObjects,
2271 int samples,
2272 int viewCount)
2273{
2274 static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
2275 QSSGPassKey passKey,
2276 QSSGLayerRenderData &inData,
2278 QRhiRenderPassDescriptor *rpDesc,
2279 QSSGRhiGraphicsPipelineState *ps) {
2280 QSSGRhiShaderPipelinePtr shaderPipeline;
2281 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2282
2283 const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
2284 QSSGShaderFeatures featureSet;
2285 featureSet.set(QSSGShaderFeatures::Feature::DepthPass, true);
2286 if (isOpaqueDepthPrePass)
2287 featureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
2288
2289 QSSGRhiDrawCallData *dcd = nullptr;
2290 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2291 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2292 const void *modelNode = &subsetRenderable.modelContext.model;
2293 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2294 }
2295
2296 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2297 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2298 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2299 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2300
2301 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
2302 if (shaderPipeline) {
2303 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2304 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2305 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2306 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2307 } else {
2308 return false;
2309 }
2310 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2311 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2312
2313 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2314
2315 ps->cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2316
2317 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2318 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2319
2320 if (shaderPipeline) {
2321 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2322 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2323 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, customMaterial, subsetRenderable,
2324 inData.renderedCameras, nullptr, nullptr);
2325 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2326 } else {
2327 return false;
2328 }
2329 }
2330
2331 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2332 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2333 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2334 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
2335 ia = subsetRenderable.subset.rhi.ia;
2336
2337 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2338 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2339 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2340
2341 QSSGRhiShaderResourceBindingList bindings;
2342 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2343
2344 // Depth and SSAO textures, in case a custom material's shader code does something with them.
2345 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2346
2347 if (isOpaqueDepthPrePass) {
2348 addOpaqueDepthPrePassBindings(rhiCtx,
2349 shaderPipeline.get(),
2350 subsetRenderable.firstImage,
2351 bindings,
2352 (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
2353 }
2354
2355 // There is no normal texture at this stage. But the shader from a
2356 // custom material may rely on it. Bind a dummy texture then due to
2357 // the lack of other options.
2358 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2359 if (normalTextureBinding >= 0) {
2360 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2361 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2362 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2363 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2364 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2365 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2366 }
2367
2368 // Skinning
2369 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2370 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2371 if (binding >= 0) {
2372 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2373 QRhiSampler::Nearest,
2374 QRhiSampler::None,
2375 QRhiSampler::ClampToEdge,
2376 QRhiSampler::ClampToEdge,
2377 QRhiSampler::Repeat
2378 });
2379 bindings.addTexture(binding,
2380 QRhiShaderResourceBinding::VertexStage,
2381 boneTexture,
2382 boneSampler);
2383 }
2384 }
2385
2386 // Morphing
2387 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2388 if (targetsTexture) {
2389 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2390 if (binding >= 0) {
2391 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2392 QRhiSampler::Nearest,
2393 QRhiSampler::None,
2394 QRhiSampler::ClampToEdge,
2395 QRhiSampler::ClampToEdge,
2396 QRhiSampler::ClampToEdge
2397 });
2398 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2399 }
2400 }
2401
2402 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2403
2404 subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtxD->pipeline(*ps,
2405 rpDesc,
2406 srb);
2407 subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
2408 }
2409
2410 return true;
2411 };
2412
2413 // Phase 1 (prepare) for the Z prepass or the depth texture generation.
2414 // These renders opaque (Z prepass), or opaque and transparent (depth
2415 // texture), objects with depth test/write enabled, and color write
2416 // disabled, using a very simple set of shaders.
2417
2418 QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
2419 // We took a copy of the pipeline state since we do not want to conflict
2420 // with what rhiPrepare() collects for its own use. So here just change
2421 // whatever we need.
2422
2423 ps.samples = samples;
2424 ps.viewCount = viewCount;
2425 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
2426 ps.targetBlend[0].colorWrite = {};
2427
2428 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2429 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2430 return false;
2431 }
2432
2433 for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
2434 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2435 return false;
2436 }
2437
2438 return true;
2439}
2440
2441void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
2442 const QSSGRhiGraphicsPipelineState &pipelineState,
2443 const QSSGRenderableObjectList &sortedOpaqueObjects,
2444 const QSSGRenderableObjectList &sortedTransparentObjects,
2445 bool *needsSetViewport)
2446{
2447 static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
2448 const QSSGRhiGraphicsPipelineState &pipelineState,
2449 const QSSGRenderableObjectList &objects,
2450 bool *needsSetViewport) {
2451 for (const auto &oh : objects) {
2452 QSSGRenderableObject *obj = oh.obj;
2453
2454 // casts to SubsetRenderableBase so it works for both default and custom materials
2455 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2456 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2457 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2458
2459 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2460 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2461 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2462 : nullptr;
2463
2464 QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
2465 if (!ps)
2466 return;
2467
2468 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
2469 if (!srb)
2470 return;
2471
2472 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2473 cb->setGraphicsPipeline(ps);
2474 cb->setShaderResources(srb);
2475
2476 if (*needsSetViewport) {
2477 cb->setViewport(pipelineState.viewport);
2478 *needsSetViewport = false;
2479 }
2480
2481 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2482 int vertexBufferCount = 1;
2483 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2484 quint32 instances = 1;
2485 if (subsetRenderable->modelContext.model.instancing()) {
2486 instances = subsetRenderable->modelContext.model.instanceCount();
2487 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2488 vertexBufferCount = 2;
2489 }
2490
2491 if (indexBuffer) {
2492 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2493 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2494 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2495 } else {
2496 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2497 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2498 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2499 }
2500 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2501 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2502 subsetRenderable->material.profilingId}));
2503 }
2504 }
2505 };
2506
2507 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
2508 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
2509}
2510
2511bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx,
2512 const QSize &size,
2513 QSSGRhiRenderableTexture *renderableTex,
2514 quint8 viewCount,
2515 int samples)
2516{
2517 QRhi *rhi = rhiCtx->rhi();
2518 bool needsBuild = false;
2519
2520 if (!renderableTex->texture) {
2521 QRhiTexture::Format format = QRhiTexture::D32F;
2522 if (!rhi->isTextureFormatSupported(format))
2523 format = QRhiTexture::D16;
2524 if (!rhi->isTextureFormatSupported(format))
2525 qWarning("Depth texture not supported");
2526 if (viewCount <= 1)
2527 renderableTex->texture = rhiCtx->rhi()->newTexture(format, size, samples, QRhiTexture::RenderTarget);
2528 else
2529 renderableTex->texture = rhiCtx->rhi()->newTextureArray(format, viewCount, size, 1, QRhiTexture::RenderTarget);
2530 needsBuild = true;
2531 } else if (renderableTex->texture->pixelSize() != size) {
2532 renderableTex->texture->setPixelSize(size);
2533 needsBuild = true;
2534 }
2535
2536 if (needsBuild) {
2537 if (!renderableTex->texture->create()) {
2538 qWarning("Failed to build depth texture (size %dx%d, format %d)",
2539 size.width(), size.height(), int(renderableTex->texture->format()));
2540 renderableTex->reset();
2541 return false;
2542 }
2543 renderableTex->resetRenderTarget();
2544 QRhiTextureRenderTargetDescription rtDesc;
2545 rtDesc.setDepthTexture(renderableTex->texture);
2546 renderableTex->rt = rhi->newTextureRenderTarget(rtDesc);
2547 renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
2548 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2549 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2550 if (!renderableTex->rt->create()) {
2551 qWarning("Failed to build render target for depth texture");
2552 renderableTex->reset();
2553 return false;
2554 }
2555 }
2556
2557 return true;
2558}
2559
2560bool RenderHelpers::rhiPrepareNormalPass(QSSGRhiContext *rhiCtx,
2561 QSSGPassKey passKey,
2562 const QSSGRhiGraphicsPipelineState &basePipelineState,
2563 QRhiRenderPassDescriptor *rpDesc,
2564 QSSGLayerRenderData &inData,
2565 const QSSGRenderableObjectList &sortedOpaqueObjects)
2566{
2567 QSSGRhiGraphicsPipelineState ps = basePipelineState;
2568 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
2569 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
2570
2571 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2572 QSSGRenderableObject *obj = handle.obj;
2573 QSSGRhiShaderPipelinePtr shaderPipeline;
2574 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2575
2576 QSSGShaderFeatures featureSet;
2577 featureSet.set(QSSGShaderFeatures::Feature::NormalPass, true);
2578
2579 QSSGRhiDrawCallData *dcd = nullptr;
2580 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2581 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2582 const void *modelNode = &subsetRenderable.modelContext.model;
2583 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2584 }
2585
2586 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2587 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2588 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2589 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2590
2591 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
2592 if (shaderPipeline) {
2593 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2594 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2595 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2596 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2597 }
2598 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2599 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2600
2601 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2602
2603 ps.cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2604
2605 const auto &customMaterialSystem = subsetRenderable.renderer->contextInterface()->customMaterialSystem();
2606 shaderPipeline = customMaterialSystem->shadersForCustomMaterial(&ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2607
2608 if (shaderPipeline) {
2609 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2610 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2611 customMaterialSystem->updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, customMaterial, subsetRenderable,
2612 inData.renderedCameras, nullptr, nullptr);
2613 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2614 }
2615 }
2616
2617 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2618 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2619 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2620 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
2621 ia = subsetRenderable.subset.rhi.ia;
2622
2623 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2624 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2625 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2626
2627 QSSGRhiShaderResourceBindingList bindings;
2628 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2629
2630 // Texture maps
2631 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
2632 while (renderableImage) {
2633 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
2634 const int samplerHint = int(renderableImage->m_mapType);
2635 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
2636 if (samplerBinding >= 0) {
2637 QRhiTexture *texture = renderableImage->m_texture.m_texture;
2638 if (samplerBinding >= 0 && texture) {
2639 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
2640 QSSGRhiSamplerDescription samplerDesc = {
2641 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
2642 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
2643 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
2644 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
2645 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
2646 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
2647 };
2648 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
2649 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
2650 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
2651 }
2652 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
2653 renderableImage = renderableImage->m_nextImage;
2654 }
2655
2656 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2657
2658 // There is no normal texture at this stage, obviously.
2659 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2660 if (normalTextureBinding >= 0) {
2661 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2662 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2663 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2664 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2665 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2666 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2667 }
2668
2669 // Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
2670
2671 // Skinning
2672 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2673 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2674 if (binding >= 0) {
2675 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2676 QRhiSampler::Nearest,
2677 QRhiSampler::None,
2678 QRhiSampler::ClampToEdge,
2679 QRhiSampler::ClampToEdge,
2680 QRhiSampler::Repeat
2681 });
2682 bindings.addTexture(binding,
2683 QRhiShaderResourceBinding::VertexStage,
2684 boneTexture,
2685 boneSampler);
2686 }
2687 }
2688
2689 // Morphing
2690 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2691 if (targetsTexture) {
2692 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2693 if (binding >= 0) {
2694 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2695 QRhiSampler::Nearest,
2696 QRhiSampler::None,
2697 QRhiSampler::ClampToEdge,
2698 QRhiSampler::ClampToEdge,
2699 QRhiSampler::ClampToEdge
2700 });
2701 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2702 }
2703 }
2704
2705 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2706
2707 subsetRenderable.rhiRenderData.normalPass.pipeline = rhiCtxD->pipeline(ps,
2708 rpDesc,
2709 srb);
2710 subsetRenderable.rhiRenderData.normalPass.srb = srb;
2711 }
2712 }
2713
2714 return true;
2715}
2716
2717void RenderHelpers::rhiRenderNormalPass(QSSGRhiContext *rhiCtx,
2718 const QSSGRhiGraphicsPipelineState &pipelineState,
2719 const QSSGRenderableObjectList &sortedOpaqueObjects,
2720 bool *needsSetViewport)
2721{
2722 for (const auto &oh : sortedOpaqueObjects) {
2723 QSSGRenderableObject *obj = oh.obj;
2724
2725 // casts to SubsetRenderableBase so it works for both default and custom materials
2726 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2727 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2728 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2729
2730 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2731 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2732 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2733 : nullptr;
2734
2735 QRhiGraphicsPipeline *graphicsPipeline = subsetRenderable->rhiRenderData.normalPass.pipeline;
2736 if (!graphicsPipeline)
2737 return;
2738
2739 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.normalPass.srb;
2740 if (!srb)
2741 return;
2742
2743 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2744 cb->setGraphicsPipeline(graphicsPipeline);
2745 cb->setShaderResources(srb);
2746
2747 if (needsSetViewport) {
2748 cb->setViewport(pipelineState.viewport);
2749 *needsSetViewport = false;
2750 }
2751
2752 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2753 int vertexBufferCount = 1;
2754 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2755 quint32 instances = 1;
2756 if (subsetRenderable->modelContext.model.instancing()) {
2757 instances = subsetRenderable->modelContext.model.instanceCount();
2758 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2759 vertexBufferCount = 2;
2760 }
2761
2762 if (indexBuffer) {
2763 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2764 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2765 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2766 } else {
2767 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2768 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2769 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2770 }
2771 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2772 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2773 subsetRenderable->material.profilingId}));
2774 }
2775 }
2776}
2777
2778QT_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 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 void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer, QSSGReflectionMapEntry *entry=nullptr, QSSGRenderTextureCubeFace cubeFace=QSSGRenderTextureCubeFaceNone, uint tonemapMode=0)
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)