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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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