Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrenderhelpers.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
8
13#include "../qssgrendercontextcore.h"
14#include "../qssgrhicustommaterialsystem_p.h"
15#include "../resourcemanager/qssgrenderbuffermanager_p.h"
16#include "../qssgrenderdefaultmaterialshadergenerator_p.h"
17#include "rendererimpl/qssgshadowmaphelpers_p.h"
18#include <QtQuick3DUtils/private/qssgassert_p.h>
19
20#include <QtGui/qquaternion.h>
21
22#include <QtCore/qbitarray.h>
23
24#include <cmath>
25
27
28static constexpr float QSSG_PI = float(M_PI);
29static constexpr float QSSG_HALFPI = float(M_PI_2);
30
32 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
33
34static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps,
35 QSSGSubsetRenderable &subsetRenderable,
36 const QSSGShaderFeatures &featureSet,
37 const QSSGUserShaderAugmentation &shaderAugmentation = {})
38{
39 auto &renderer(subsetRenderable.renderer);
40 const auto &shaderPipeline = QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(*renderer, subsetRenderable, featureSet, shaderAugmentation);
41 if (shaderPipeline) {
42 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
43 // NOTE: It's not just custom materials that can have custom property textures,
44 // augmented default materials can have them too.
45 shaderPipeline->resetExtraTextures();
46 }
47 return shaderPipeline;
48}
49
50static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps,
51 QSSGParticlesRenderable &particleRenderable,
52 const QSSGShaderFeatures &featureSet)
53{
54 auto &renderer(particleRenderable.renderer);
55 const auto &shaderPipeline = QSSGParticleRenderer::getShaderPipelineParticles(*renderer, particleRenderable, featureSet);
56 if (shaderPipeline)
57 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
58 return shaderPipeline;
59}
60
61static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline,
62 QSSGRhiContext *rhiCtx,
63 const QSSGLayerRenderData &inData,
64 char *ubufData,
65 QSSGRhiGraphicsPipelineState *ps,
66 QSSGSubsetRenderable &subsetRenderable,
67 const QSSGRenderCameraList &cameras,
68 const QVector2D *depthAdjust,
69 const QMatrix4x4 *alteredModelViewProjection)
70{
71 const auto &renderer(subsetRenderable.renderer);
72 const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
73 QSSGRenderMvpArray alteredMvpList;
74 if (alteredModelViewProjection)
75 alteredMvpList[0] = *alteredModelViewProjection;
76
77 const auto &modelNode = subsetRenderable.modelContext.model;
78 QRhiTexture *lightmapTexture = inData.getLightmapTexture(subsetRenderable.modelContext);
79
80 const auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(modelNode);
81 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : inData.getGlobalTransform(modelNode));
82
83 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*renderer->contextInterface(),
84 shaderPipeline,
85 ubufData,
86 ps,
87 subsetRenderable.material,
88 subsetRenderable.shaderDescription,
89 inData.getDefaultMaterialPropertyTable(),
90 cameras,
91 alteredModelViewProjection ? alteredMvpList : subsetRenderable.modelContext.modelViewProjections,
92 subsetRenderable.modelContext.normalMatrix,
93 modelMatrix,
94 clipSpaceCorrMatrix,
95 localInstanceTransform,
96 globalInstanceTransform,
97 toDataView(modelNode.morphWeights),
98 subsetRenderable.firstImage,
99 subsetRenderable.opacity,
100 inData,
101 subsetRenderable.lights,
102 subsetRenderable.reflectionProbe,
103 subsetRenderable.renderableFlags.receivesShadows(),
104 subsetRenderable.renderableFlags.receivesReflections(),
105 depthAdjust,
106 lightmapTexture);
107}
108
109std::pair<QSSGBounds3, QSSGBounds3> RenderHelpers::calculateSortedObjectBounds(const QSSGRenderableObjectList &sortedOpaqueObjects,
110 const QSSGRenderableObjectList &sortedTransparentObjects)
111{
112 QSSGBounds3 boundsCasting;
113 QSSGBounds3 boundsReceiving;
114 for (const auto handles : { &sortedOpaqueObjects, &sortedTransparentObjects }) {
115 // Since we may have nodes that are not a child of the camera parent we go through all
116 // the opaque objects and include them in the bounds. Failing to do this can result in
117 // too small bounds.
118 for (const QSSGRenderableObjectHandle &handle : *handles) {
119 const QSSGRenderableObject &obj = *handle.obj;
120 // We skip objects not casting or receiving shadows since they don't influence or need to be covered by the shadow map
121 if (obj.renderableFlags.castsShadows()) {
122 if (!obj.globalBoundsInstancing.isEmpty())
123 boundsCasting.include(obj.globalBoundsInstancing);
124 else
125 boundsCasting.include(obj.globalBounds);
126 }
127 if (obj.renderableFlags.receivesShadows()) {
128 if (!obj.globalBoundsInstancing.isEmpty())
129 boundsReceiving.include(obj.globalBoundsInstancing);
130 else
131 boundsReceiving.include(obj.globalBounds);
132 }
133 }
134 }
135 return { boundsCasting, boundsReceiving };
136}
137
138static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
139{
140 bool invertible = false;
141 QMatrix4x4 inv = projection.inverted(&invertible);
142 Q_ASSERT(invertible);
143
144 // The frustum points will be in this orientation
145 //
146 // bottom top
147 // 4__________5 7__________6
148 // \ / \ /
149 // \ / \ /
150 // \____/ \____/
151 // 0 1 3 2
152 return { inv.map(QVector3D(-1, -1, -1)), inv.map(QVector3D(+1, -1, -1)), inv.map(QVector3D(+1, +1, -1)),
153 inv.map(QVector3D(-1, +1, -1)), inv.map(QVector3D(-1, -1, +1)), inv.map(QVector3D(+1, -1, +1)),
154 inv.map(QVector3D(+1, +1, +1)), inv.map(QVector3D(-1, +1, +1)) };
155}
156
157static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
158{
159 QSSGBoxPoints pts;
160 for (int i = 0; i < 4; ++i) {
161 const QVector3D forward = frustumPoints[i + 4] - frustumPoints[i];
162 pts[i] = frustumPoints[i] + forward * t0;
163 pts[i + 4] = frustumPoints[i] + forward * t1;
164 }
165 return pts;
166}
167
168static std::unique_ptr<QSSGRenderCamera> computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix,
169 const QMatrix4x4 &lightMatrixInverted,
170 const QVector3D &lightPivot,
171 const QVector3D &lightForward,
172 const QVector3D &lightUp,
173 const float shadowMapResolution,
174 const float pcfRadius,
175 const QSSGBoxPoints &frustumPoints,
176 float frustumStartT,
177 float frustumEndT,
178 float frustumRadius,
179 bool lockShadowmapTexels,
180 const QSSGBounds3 &castingBox,
181 const QSSGBounds3 &receivingBox,
182 QSSGDebugDrawSystem *debugDrawSystem,
183 const bool drawCascades,
184 const bool drawSceneCascadeIntersection)
185{
186 if (!castingBox.isFinite() || castingBox.isEmpty() || !receivingBox.isFinite() || receivingBox.isEmpty())
187 return nullptr; // Return early, no casting or receiving objects means no shadows
188
189 Q_ASSERT(frustumStartT <= frustumEndT);
190 Q_ASSERT(frustumStartT >= 0.f);
191 Q_ASSERT(frustumEndT <= 1.0f);
192
193 auto transformPoints = [&](const QSSGBoxPoints &points) {
194 QSSGBoxPoints result;
195 for (int i = 0; i < int(points.size()); ++i) {
196 result[i] = lightMatrix.map(points[i]);
197 }
198 return result;
199 };
200
201 QSSGBoxPoints frustumPointsSliced = sliceFrustum(frustumPoints, frustumStartT, frustumEndT);
202 if (drawCascades)
203 ShadowmapHelpers::addDebugFrustum(frustumPointsSliced, QColorConstants::Black, debugDrawSystem);
204
205 const QList<QVector3D> receivingSliced = ShadowmapHelpers::intersectBoxByFrustum(frustumPointsSliced,
206 receivingBox.toQSSGBoxPoints(),
207 drawSceneCascadeIntersection ? debugDrawSystem : nullptr,
208 QColorConstants::DarkGray);
209 if (receivingSliced.isEmpty())
210 return nullptr;
211
212 QSSGBounds3 receivingFrustumSlicedLightSpace;
213 for (const QVector3D &point : receivingSliced)
214 receivingFrustumSlicedLightSpace.include(lightMatrix.map(point));
215
216 // Slice casting box by frustumBounds' left, right, up, down planes
217 const QList<QVector3D> castingPointsLightSpace = ShadowmapHelpers::intersectBoxByBox(receivingFrustumSlicedLightSpace,
218 transformPoints(castingBox.toQSSGBoxPointsNoEmptyCheck()));
219 if (castingPointsLightSpace.isEmpty())
220 return nullptr;
221
222 // Create box containing casting and receiving from light space:
223 QSSGBounds3 castReceiveBounds;
224 for (const QVector3D &p : castingPointsLightSpace) {
225 castReceiveBounds.include(p);
226 }
227
228 for (const QVector3D &p : receivingFrustumSlicedLightSpace.toQSSGBoxPointsNoEmptyCheck()) {
229 float zMax = qMax(p.z(), castReceiveBounds.maximum.z());
230 float zMin = qMin(p.z(), castReceiveBounds.minimum.z());
231 castReceiveBounds.maximum.setZ(zMax);
232 castReceiveBounds.minimum.setZ(zMin);
233 }
234
235 // Expand to fit pcf radius
236 castReceiveBounds.fatten(pcfRadius);
237
238 QVector3D boundsCenterWorld = lightMatrixInverted.map(castReceiveBounds.center());
239 QVector3D boundsDims = castReceiveBounds.dimensions();
240 boundsDims.setZ(boundsDims.z() * 1.01f); // Expand slightly in z direction to avoid pancaking precision errors
241
242 if (lockShadowmapTexels) {
243 // Calculate center position aligned to texel size to avoid shimmering
244 const float diam = (pcfRadius + frustumRadius) * 2.0f;
245 const float texelsPerUnit = diam / shadowMapResolution;
246 QVector3D centerLight = lightMatrix.map(boundsCenterWorld);
247 float x = centerLight.x();
248 float y = centerLight.y();
249 float z = centerLight.z();
250 centerLight = QVector3D(int(x / texelsPerUnit), int(y / texelsPerUnit), int(z / texelsPerUnit)) * texelsPerUnit;
251 boundsCenterWorld = lightMatrixInverted.map(centerLight);
252 boundsDims.setX(diam);
253 boundsDims.setY(diam);
254 } else {
255 // We expand the shadowmap to cover the bounds with one extra texel on all sides
256 const float texelExpandFactor = shadowMapResolution / (shadowMapResolution - 2);
257 boundsDims.setX(boundsDims.x() * texelExpandFactor);
258 boundsDims.setY(boundsDims.y() * texelExpandFactor);
259 }
260
261 QRectF theViewport(0.0f, 0.0f, boundsDims.x(), boundsDims.y());
262
263 auto camera = std::make_unique<QSSGRenderCamera>(QSSGRenderGraphObject::Type::OrthographicCamera);
264 camera->clipPlanes = QSSGRenderCamera::ClipPlanes{-0.5f * boundsDims.z(), 0.5f * boundsDims.z()};
265 camera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
266 camera->parent = nullptr;
267 camera->localTransform = QSSGRenderNode::calculateTransformMatrix(boundsCenterWorld,
268 QSSGRenderNode::initScale,
269 lightPivot,
270 QQuaternion::fromDirection(lightForward, lightUp));
271 QSSGRenderCamera::calculateProjectionInternal(*camera, theViewport);
272
273 return camera;
274}
275
277 const QSSGRenderCamera &inCamera,
278 const QSSGRenderLight *inLight,
279 const int shadowMapResolution,
280 const float pcfRadius,
281 const float clipNear,
282 const float clipFar,
283 const QSSGBounds3 &castingObjectsBox,
284 const QSSGBounds3 &receivingObjectsBox,
285 bool lockShadowmapTexels,
286 QSSGDebugDrawSystem *debugDrawSystem,
287 bool drawCascades,
288 bool drawSceneCascadeIntersection)
289{
290 Q_ASSERT(inLight->type == QSSGRenderLight::Type::DirectionalLight);
291 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> result;
292
293 if (clipNear >= clipFar || qFuzzyCompare(clipNear, clipFar))
294 return result;
295
296 const QMatrix4x4 lightGlobalTransform = data.getGlobalTransform(*inLight);
297 const QVector3D lightDir = inLight->getDirection(lightGlobalTransform);
298 const QVector3D lightPivot = inLight->pivot;
299
300 const QVector3D forward = lightDir.normalized();
301 const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
302 ? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
303 : QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
304 const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
305
306 QMatrix4x4 lightMatrix;
307 lightMatrix.setRow(0, QVector4D(right, 0.0f));
308 lightMatrix.setRow(1, QVector4D(up, 0.0f));
309 lightMatrix.setRow(2, QVector4D(forward, 0.0f));
310 lightMatrix.setRow(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
311 QMatrix4x4 lightMatrixInverted = lightMatrix.inverted();
312
313 const float farScale = (clipFar - clipNear) / (inCamera.clipPlanes.clipFar() - inCamera.clipPlanes.clipNear());
314
315 const QMatrix4x4 cameraGlobalTransform = data.getGlobalTransform(inCamera);
316 QMatrix4x4 viewProjection(Qt::Uninitialized);
317 inCamera.calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
318 const QSSGBoxPoints frustum = computeFrustumBounds(viewProjection);
319 const QSSGBoxPoints frustumUntransformed = lockShadowmapTexels ? computeFrustumBounds(inCamera.projection) : QSSGBoxPoints();
320
321 // We calculate the radius of the cascade without rotation or translation so we always get
322 // the same floating point value.
323 const auto calcFrustumRadius = [&](float t0, float t1) -> float {
324 const QSSGBoxPoints pts = sliceFrustum(frustumUntransformed, t0 * farScale, t1 * farScale);
325
326 QVector3D center = QVector3D(0.f, 0.f, 0.f);
327 for (QVector3D point : pts) {
328 center += point;
329 }
330 center = center * 0.125f;
331
332 float radiusSquared = 0;
333 for (QVector3D point : pts) {
334 radiusSquared = qMax(radiusSquared, (point - center).lengthSquared());
335 }
336
337 return std::sqrt(radiusSquared);
338 };
339
340 const auto computeSplitRanges = [inLight](const QVarLengthArray<float, 3> &splits) -> QVarLengthArray<QPair<float, float>, 4> {
341 QVarLengthArray<QPair<float, float>, 4> ranges;
342 const float csmBlendRatio = inLight->m_csmBlendRatio;
343 float t0 = 0.f;
344 for (qsizetype i = 0; i < splits.length(); i++) {
345 const float tI = qBound(qMin(t0 + 0.01f, 1.0f), splits[i], 1.0f);
346 ranges.emplace_back(t0, qMin(1.0f, tI + csmBlendRatio));
347 t0 = tI;
348 }
349 ranges.emplace_back(t0, 1.0f);
350 return ranges;
351 };
352
353 const auto computeFrustums = [&](const QVarLengthArray<float, 3> &splits) {
354 for (const auto &range : computeSplitRanges(splits)) {
355 const float frustumRadius = lockShadowmapTexels ? calcFrustumRadius(range.first, range.second) : 0.0f;
356 auto camera = computeShadowCameraFromFrustum(lightMatrix,
357 lightMatrixInverted,
358 lightPivot,
359 forward,
360 up,
361 shadowMapResolution,
362 pcfRadius,
363 frustum,
364 range.first * farScale,
365 range.second * farScale,
366 frustumRadius,
367 lockShadowmapTexels,
368 castingObjectsBox,
369 receivingObjectsBox,
370 debugDrawSystem,
371 drawCascades,
372 drawSceneCascadeIntersection);
373 result.emplace_back(std::move(camera));
374 }
375 };
376
377 switch (inLight->m_csmNumSplits) {
378 case 0: {
379 computeFrustums({});
380 break;
381 }
382 case 1: {
383 computeFrustums({ inLight->m_csmSplit1 });
384 break;
385 }
386 case 2: {
387 computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2 });
388 break;
389 }
390 case 3: {
391 computeFrustums({ inLight->m_csmSplit1, inLight->m_csmSplit2, inLight->m_csmSplit3 });
392 break;
393 }
394 default:
395 Q_UNREACHABLE();
396 break;
397 }
398
399 return result;
400}
401
402static void setupCubeReflectionCameras(const QSSGLayerRenderData &inData, const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
403{
404 Q_ASSERT(inProbe != nullptr);
405
406 // setup light matrix
407 quint32 mapRes = 1 << inProbe->reflectionMapRes;
408 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
409 static const QQuaternion rotOfs[6] {
410 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
411 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
412 QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
413 QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
414 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
415 QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
416 };
417
418 auto inProbeGlobalTranform = inData.getGlobalTransform(*inProbe);
419 const QVector3D inProbePos = QSSGRenderNode::getGlobalPos(inProbeGlobalTranform);
420 const QVector3D inProbePivot = inProbe->pivot;
421
422 for (int i = 0; i < 6; ++i) {
423 inCameras[i].parent = nullptr;
424 inCameras[i].clipPlanes = {1.0f, qMax<float>(2.0f, 10000.0f)};
425 inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
426
427 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inProbePos, QSSGRenderNode::initScale, inProbePivot, rotOfs[i]);
428 QSSGRenderCamera::calculateProjectionInternal(inCameras[i], theViewport);
429 }
430}
431
432static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx,
433 QSSGRhiShaderPipeline *shaderPipeline,
434 QSSGRenderableImage *renderableImage,
435 QSSGRhiShaderResourceBindingList &bindings,
436 bool isCustomMaterialMeshSubset = false)
437{
438 static const auto imageAffectsAlpha = [](QSSGRenderableImage::Type mapType) {
439 return mapType == QSSGRenderableImage::Type::BaseColor ||
440 mapType == QSSGRenderableImage::Type::Diffuse ||
441 mapType == QSSGRenderableImage::Type::Translucency ||
442 mapType == QSSGRenderableImage::Type::Opacity;
443 };
444
445 while (renderableImage) {
446 const auto mapType = renderableImage->m_mapType;
447 if (imageAffectsAlpha(mapType)) {
448 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(mapType);
449 const int samplerHint = int(mapType);
450 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
451 if (samplerBinding >= 0) {
452 QRhiTexture *texture = renderableImage->m_texture.m_texture;
453 if (samplerBinding >= 0 && texture) {
454 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
455 QRhiSampler *sampler = rhiCtx->sampler({ QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
456 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
457 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
458 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
459 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
460 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
461 });
462 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
463 }
464 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
465 }
466 renderableImage = renderableImage->m_nextImage;
467 }
468 // For custom Materials we can't know which maps affect alpha, so map all
469 if (isCustomMaterialMeshSubset) {
470 QVector<QShaderDescription::InOutVariable> samplerVars =
471 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
472 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
473 auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
474 [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
475 if (it == samplerVars.cend())
476 samplerVars.append(var);
477 }
478
479 int maxSamplerBinding = -1;
480 for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
481 maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
482
483 // Will need to set unused image-samplers to something dummy
484 // because the shader code contains all custom property textures,
485 // and not providing a binding for all of them is invalid with some
486 // graphics APIs (and will need a real texture because setting a
487 // null handle or similar is not permitted with some of them so the
488 // srb does not accept null QRhiTextures either; but first let's
489 // figure out what bindings are unused in this frame)
490 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
491
492 if (maxSamplerBinding >= 0) {
493 // custom property textures
494 int customTexCount = shaderPipeline->extraTextureCount();
495 for (int i = 0; i < customTexCount; ++i) {
496 const QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
497 const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
498 if (samplerBinding >= 0) {
499 samplerBindingsSpecified.setBit(samplerBinding);
500 QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
501 bindings.addTexture(samplerBinding,
502 RENDERER_VISIBILITY_ALL,
503 t.texture,
504 sampler);
505 }
506 }
507 }
508
509 // use a dummy texture for the unused samplers in the shader
510 QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
511 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
512 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
513 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
514 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
515 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
516
517 for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars)) {
518 if (!samplerBindingsSpecified.testBit(var.binding)) {
519 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
520 bindings.addTexture(var.binding, RENDERER_VISIBILITY_ALL, t, dummySampler);
521 }
522 }
523 }
524}
525
527 const QSSGRenderLight *inLight,
528 float shadowMapFar,
529 QSSGRenderCamera inCameras[6])
530{
531 Q_ASSERT(inLight != nullptr);
532 Q_ASSERT(inLight->type != QSSGRenderLight::Type::DirectionalLight);
533
534 // setup light matrix
535 quint32 mapRes = inLight->m_shadowMapRes;
536 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
537 static const QQuaternion rotOfs[6] {
538 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(-QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
539 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_HALFPI), qRadiansToDegrees(QSSG_PI)),
540 QQuaternion::fromEulerAngles(qRadiansToDegrees(QSSG_HALFPI), 0.f, 0.f),
541 QQuaternion::fromEulerAngles(qRadiansToDegrees(-QSSG_HALFPI), 0.f, 0.f),
542 QQuaternion::fromEulerAngles(0.f, qRadiansToDegrees(QSSG_PI), qRadiansToDegrees(-QSSG_PI)),
543 QQuaternion::fromEulerAngles(0.f, 0.f, qRadiansToDegrees(QSSG_PI)),
544 };
545
546 const auto gt = inData.getGlobalTransform(*inLight);
547 const QVector3D inLightPos = QSSGRenderNode::getGlobalPos(gt);
548 constexpr QVector3D lightPivot = QVector3D(0, 0, 0);
549
550 for (int i = 0; i < 6; ++i) {
551 inCameras[i].parent = nullptr;
552 inCameras[i].clipPlanes = {1.0f, shadowMapFar};
553 inCameras[i].fov = QSSGRenderCamera::FieldOfView::fromDegrees(90.f);
554 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(inLightPos, QSSGRenderNode::initScale, lightPivot, rotOfs[i]);
555 QSSGRenderCamera::calculateProjectionInternal(inCameras[i], theViewport);
556 }
557
558 /*
559 if ( inLight->type == RenderLightTypes::Point ) return;
560
561 QVector3D viewDirs[6];
562 QVector3D viewUp[6];
563 QMatrix3x3 theDirMatrix( inLight->m_GlobalTransform.getUpper3x3() );
564
565 viewDirs[0] = theDirMatrix.transform( QVector3D( 1.f, 0.f, 0.f ) );
566 viewDirs[2] = theDirMatrix.transform( QVector3D( 0.f, -1.f, 0.f ) );
567 viewDirs[4] = theDirMatrix.transform( QVector3D( 0.f, 0.f, 1.f ) );
568 viewDirs[0].normalize(); viewDirs[2].normalize(); viewDirs[4].normalize();
569 viewDirs[1] = -viewDirs[0];
570 viewDirs[3] = -viewDirs[2];
571 viewDirs[5] = -viewDirs[4];
572
573 viewUp[0] = viewDirs[2];
574 viewUp[1] = viewDirs[2];
575 viewUp[2] = viewDirs[5];
576 viewUp[3] = viewDirs[4];
577 viewUp[4] = viewDirs[2];
578 viewUp[5] = viewDirs[2];
579
580 for (int i = 0; i < 6; ++i)
581 {
582 inCameras[i].LookAt( inLightPos, viewUp[i], inLightPos + viewDirs[i] );
583 inCameras[i].CalculateGlobalVariables( theViewport, QVector2D( theViewport.m_Width,
584 theViewport.m_Height ) );
585 }
586 */
587}
588
589static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
590{
591 // TODO: non-static so it can be used from QSSGCustomMaterialSystem::rhiPrepareRenderable()?
592 const bool instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable, cameraDirection, cameraPosition, renderable->instancingLodMin, renderable->instancingLodMax);
593 int instanceBufferBinding = 0;
594 if (instancing) {
595 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
596 // set up new bindings for instanced buffers
597 const quint32 stride = renderable->modelContext.model.instanceTable->stride();
598 QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
599 std::copy(ia.inputLayout.cbeginBindings(), ia.inputLayout.cendBindings(), std::back_inserter(bindings));
600 bindings.append({ stride, QRhiVertexInputBinding::PerInstance });
601 instanceBufferBinding = bindings.size() - 1;
602 ia.inputLayout.setBindings(bindings.cbegin(), bindings.cend());
603 }
604 return instanceBufferBinding;
605}
606
607static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx,
608 QSSGPassKey passKey,
609 const QSSGLayerRenderData &inData,
611 QSSGRhiGraphicsPipelineState *ps,
612 const QSSGRenderableObjectList &sortedOpaqueObjects,
613 QSSGRenderCamera &inCamera,
614 QSSGRenderer &renderer,
615 QSSGRenderTextureCubeFace cubeFace)
616{
617 using namespace RenderHelpers;
618
619 if ((inData.layer.background == QSSGRenderLayer::Background::SkyBox && inData.layer.lightProbe) ||
620 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
621 rhiPrepareSkyBoxForReflectionMap(rhiCtx, passKey, inData.layer, inCamera, renderer, pEntry, cubeFace);
622
623 QSSGShaderFeatures features = inData.getShaderFeatures();
624 // because of alteredCamera/alteredMvp below
625 features.set(QSSGShaderFeatures::Feature::DisableMultiView, true);
626
627 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
628
629 for (const auto &handle : sortedOpaqueObjects) {
630 QSSGRenderableObject &inObject = *handle.obj;
631
632 QMatrix4x4 modelViewProjection;
633 if (inObject.type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || inObject.type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
634 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(inObject));
635 const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(renderable.shaderDescription) > 0;
636 const QMatrix4x4 &globalTransform = renderable.modelContext.globalTransform;
637 modelViewProjection = hasSkinning ? pEntry->m_viewProjection
638 : pEntry->m_viewProjection * globalTransform;
639 }
640
641 // here we pass on our own alteredCamera and alteredModelViewProjection
642 rhiPrepareRenderable(rhiCtx, passKey, inData, inObject, pEntry->m_rhiRenderPassDesc, ps, features, 1, 1,
643 &inCamera, &modelViewProjection, cubeFace, pEntry);
644 }
645}
646
647static inline void addDepthTextureBindings(QSSGRhiContext *rhiCtx,
648 QSSGRhiShaderPipeline *shaderPipeline,
649 QSSGRhiShaderResourceBindingList &bindings)
650{
651 if (shaderPipeline->depthTexture()) {
652 const int depthTextureBinding = shaderPipeline->bindingForTexture("qt_depthTexture", int(QSSGRhiSamplerBindingHints::DepthTexture));
653 const int depthTextureArrayBinding = shaderPipeline->bindingForTexture("qt_depthTextureArray", int(QSSGRhiSamplerBindingHints::DepthTextureArray));
654 if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
655 // nearest min/mag, no mipmap
656 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
657 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
658 if (depthTextureBinding >= 0)
659 bindings.addTexture(depthTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
660 if (depthTextureArrayBinding >= 0)
661 bindings.addTexture(depthTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->depthTexture(), sampler);
662 } // else ignore, not an error
663 }
664
665 // SSAO texture
666 if (shaderPipeline->ssaoTexture()) {
667 const int ssaoTextureBinding = shaderPipeline->bindingForTexture("qt_aoTexture", int(QSSGRhiSamplerBindingHints::AoTexture));
668 const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture("qt_aoTextureArray", int(QSSGRhiSamplerBindingHints::AoTextureArray));
669 if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
670 // linear min/mag, no mipmap
671 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
672 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
673 if (ssaoTextureBinding >= 0) {
674 bindings.addTexture(ssaoTextureBinding,
675 QRhiShaderResourceBinding::FragmentStage,
676 shaderPipeline->ssaoTexture(), sampler);
677 }
678 if (ssaoTextureArrayBinding >= 0) {
679 bindings.addTexture(ssaoTextureArrayBinding,
680 QRhiShaderResourceBinding::FragmentStage,
681 shaderPipeline->ssaoTexture(), sampler);
682 }
683 } // else ignore, not an error
684 }
685}
686
687static inline void addNormalTextureBindings(QSSGRhiContext *rhiCtx,
688 QSSGRhiShaderPipeline *shaderPipeline,
689 QSSGRhiShaderResourceBindingList &bindings)
690{
691 if (shaderPipeline->normalTexture()) {
692 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
693 if (normalTextureBinding >= 0) {
694 // nearest min/mag, no mipmap
695 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
696 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
697 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, shaderPipeline->normalTexture(), sampler);
698 } // else ignore, not an error
699 }
700}
701
702void RenderHelpers::addAccumulatorImageBindings(QSSGRhiShaderPipeline *shaderPipeline,
703 QSSGRhiShaderResourceBindingList &bindings)
704{
705 const auto images = shaderPipeline->oitImages();
706 if (!images[0])
707 return;
708
709#ifdef QSSG_OIT_USE_BUFFERS
710 bindings.addStorageBuffer(3, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[0]);
711 bindings.addStorageBuffer(4, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[1]);
712 bindings.addStorageBuffer(5, QRhiShaderResourceBinding::FragmentStage, (QRhiBuffer*)images[2]);
713#else
714 int abuf = shaderPipeline->bindingForImage("qt_imgAbuffer");
715 int aux = shaderPipeline->bindingForImage("qt_imgAux");
716 int counter = shaderPipeline->bindingForImage("qt_imgCounter");
717 if (abuf == -1 || aux == -1 || counter == -1) {
718 qWarning()<<"Shader is missing image binding points;";
719 return;
720 }
721 bindings.addImageStore(abuf, QRhiShaderResourceBinding::FragmentStage, images[0], 0);
722 // atomic operations require loadstore
723 bindings.addImageLoadStore(aux, QRhiShaderResourceBinding::FragmentStage, images[1], 0);
724 bindings.addImageLoadStore(counter, QRhiShaderResourceBinding::FragmentStage, images[2], 0);
725#endif
726}
727
728static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx,
729 const QSSGLayerRenderData &inData,
730 QSSGPassKey passKey,
731 QSSGShadowMapEntry *pEntry,
732 QSSGRhiGraphicsPipelineState *ps,
733 const QVector2D *depthAdjust,
734 const QSSGRenderableObjectList &sortedOpaqueObjects,
735 QSSGRenderCamera &inCamera,
736 bool orthographic,
737 QSSGRenderTextureCubeFace cubeFace,
738 quint32 cascadeIndex)
739{
740 QSSGShaderFeatures featureSet;
741 if (orthographic)
742 featureSet.set(QSSGShaderFeatures::Feature::OrthoShadowPass, true);
743 else
744 featureSet.set(QSSGShaderFeatures::Feature::PerspectiveShadowPass, true);
745
746 // Do note how updateUniformsForDefaultMaterial() get a single camera and a
747 // custom mvp; make sure multiview is disabled in the shader generator using
748 // the common flag, instead of it having to write logic for checking for
749 // OrthoShadowPoss || CubeShadowPass.
750 featureSet.set(QSSGShaderFeatures::Feature::DisableMultiView, true);
751
752 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
753 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
754 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
755
756 for (const auto &handle : sortedOpaqueObjects) {
757 QSSGRenderableObject *theObject = handle.obj;
758 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
759
760 QSSGShaderFeatures objectFeatureSet = featureSet;
761 const bool isOpaqueDepthPrePass = theObject->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
762 if (isOpaqueDepthPrePass)
763 objectFeatureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
764
765 QSSGRhiDrawCallData *dcd = nullptr;
766 QMatrix4x4 modelViewProjection;
767 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(*theObject));
768 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
769 const bool hasSkinning = defaultMaterialShaderKeyProperties.m_boneCount.getValue(renderable.shaderDescription) > 0;
770 modelViewProjection = hasSkinning ? pEntry->m_lightViewProjection[cascadeIndex]
771 : pEntry->m_lightViewProjection[cascadeIndex] * renderable.modelContext.globalTransform;
772 // cascadeIndex is 0..3 for directional light and 0 for the pointlight & spotlight
773 // cubeFaceIdx is 0 for directional & spotlight and 0..5 for the pointlight
774 // pEntry is unique per light and a light can only be one of directional, point, or spotlight.
775 const quintptr entryIdx = cascadeIndex + cubeFaceIdx + (quintptr(renderable.subset.offset) << 3);
776 dcd = &rhiCtxD->drawCallData({ passKey, &renderable.modelContext.model, pEntry, entryIdx });
777 }
778
779 QSSGRhiShaderResourceBindingList bindings;
780 QSSGRhiShaderPipelinePtr shaderPipeline;
781 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*theObject));
782 if (theObject->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset) {
783 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
784 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
785 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
786
787 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, objectFeatureSet);
788 if (!shaderPipeline)
789 continue;
790 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
791 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
792 // calls updateUni with an alteredCamera and alteredModelViewProjection
793 QSSGRenderCameraList cameras({ &inCamera });
794 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, depthAdjust, &modelViewProjection);
795 if (blendParticles)
796 QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
797 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
798 if (blendParticles)
799 QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
800 } else if (theObject->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset) {
801 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
802 ps->cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
803
804 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
805 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), objectFeatureSet);
806 if (!shaderPipeline)
807 continue;
808 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
809 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
810 // inCamera is the shadow camera, not the same as inData.renderedCameras
811 QSSGRenderCameraList cameras({ &inCamera });
812 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, material, subsetRenderable,
813 cameras, depthAdjust, &modelViewProjection);
814 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
815 }
816
817 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
818
819 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
820 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
821 ia = subsetRenderable.subset.rhi.ia;
822 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
823 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
824 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
825
826
827 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
828
829 // Depth and SSAO textures, in case a custom material's shader code does something with them.
830 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
831 // and the normal texture, if there is one.
832 addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
833
834 if (isOpaqueDepthPrePass) {
835 addOpaqueDepthPrePassBindings(rhiCtx,
836 shaderPipeline.get(),
837 subsetRenderable.firstImage,
838 bindings,
839 (theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
840 }
841
842 // There is no screen texture at this stage. But the shader from a
843 // custom material may rely on it, and an object with that material
844 // can end up in the shadow map's object list. So bind a dummy
845 // texture then due to the lack of other options.
846 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
847 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
848 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
849 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
850 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
851 if (screenTextureBinding >= 0) {
852 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
853 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
854 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
855 bindings.addTexture(screenTextureBinding,
856 QRhiShaderResourceBinding::FragmentStage,
857 dummyTexture, sampler);
858 }
859 if (screenTextureArrayBinding >= 0) {
860 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
861 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates, QSize(64, 64), Qt::black, inData.layer.viewCount);
862 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
863 bindings.addTexture(screenTextureArrayBinding,
864 QRhiShaderResourceBinding::FragmentStage,
865 dummyTexture, sampler);
866 }
867 }
868
869 // Skinning
870 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
871 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
872 if (binding >= 0) {
873 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
874 QRhiSampler::Nearest,
875 QRhiSampler::None,
876 QRhiSampler::ClampToEdge,
877 QRhiSampler::ClampToEdge,
878 QRhiSampler::Repeat
879 });
880 bindings.addTexture(binding,
881 QRhiShaderResourceBinding::VertexStage,
882 boneTexture,
883 boneSampler);
884 }
885 }
886
887 // Morphing
888 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
889 if (targetsTexture) {
890 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
891 if (binding >= 0) {
892 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
893 QRhiSampler::Nearest,
894 QRhiSampler::None,
895 QRhiSampler::ClampToEdge,
896 QRhiSampler::ClampToEdge,
897 QRhiSampler::ClampToEdge
898 });
899 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
900 }
901 }
902
903 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
904 subsetRenderable.rhiRenderData.shadowPass.pipeline = rhiCtxD->pipeline(*ps, pEntry->m_rhiRenderPassDesc[cascadeIndex], srb);
905 subsetRenderable.rhiRenderData.shadowPass.srb[cubeFaceIdx] = srb;
906 }
907 }
908}
909
910static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
911{
912 // Assuming default values in the other TargetBlend fields
913 switch (materialBlend) {
914 case QSSGRenderDefaultMaterial::MaterialBlendMode::Screen:
915 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
916 targetBlend->dstColor = QRhiGraphicsPipeline::One;
917 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
918 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
919 break;
920 case QSSGRenderDefaultMaterial::MaterialBlendMode::Multiply:
921 targetBlend->srcColor = QRhiGraphicsPipeline::DstColor;
922 targetBlend->dstColor = QRhiGraphicsPipeline::Zero;
923 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
924 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
925 break;
926 default:
927 // Use SourceOver for everything else
928 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
929 targetBlend->dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
930 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
931 targetBlend->dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
932 break;
933 }
934}
935
936void RenderHelpers::rhiPrepareRenderable(QSSGRhiContext *rhiCtx,
937 QSSGPassKey passKey,
938 const QSSGLayerRenderData &inData,
939 QSSGRenderableObject &inObject,
940 QRhiRenderPassDescriptor *renderPassDescriptor,
941 QSSGRhiGraphicsPipelineState *ps,
942 QSSGShaderFeatures featureSet,
943 int samples,
944 int viewCount,
945 QSSGRenderCamera *alteredCamera,
946 QMatrix4x4 *alteredModelViewProjection,
947 QSSGRenderTextureCubeFace cubeFace,
949 bool oit)
950{
951 const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
952
953 switch (inObject.type) {
954 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
955 {
956 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
957
958 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
959 featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
960
961 if ((cubeFace != QSSGRenderTextureCubeFaceNone)) {
962 // Disable tonemapping for the reflection pass
963 featureSet.disableTonemapping();
964 }
965
966 if (subsetRenderable.renderableFlags.rendersWithLightmap())
967 featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
968
969 const auto &shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
970 if (shaderPipeline) {
971 // Unlike the subsetRenderable (which is allocated per frame so is
972 // not persistent in any way), the model reference is persistent in
973 // the sense that it references the model node in the scene graph.
974 // Combined with the layer node (multiple View3Ds may share the
975 // same scene!), this is suitable as a key to get the uniform
976 // buffers that were used with the rendering of the same model in
977 // the previous frame.
978 QSSGRhiShaderResourceBindingList bindings;
979 const auto &modelNode = subsetRenderable.modelContext.model;
980 const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(subsetRenderable.shaderDescription);
981
982
983 // NOTE:
984 // - entryIdx should 0 for QSSGRenderTextureCubeFaceNone.
985 // In all other cases the entryIdx is a combination of the cubeface idx and the subset offset, where the lower bits
986 // are the cubeface idx.
987 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
988 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(subsetRenderable.subset.offset) << 3));
989 // If there's an entry we merge that with the address of the material
990 const auto entryPartA = reinterpret_cast<quintptr>(&subsetRenderable.material);
991 const auto entryPartB = reinterpret_cast<quintptr>(entry);
992 const void *entryId = reinterpret_cast<const void *>(entryPartA ^ entryPartB);
993
994 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
995 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, &modelNode, entryId, entryIdx });
996
997 shaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
998 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
999 if (alteredCamera) {
1000 Q_ASSERT(alteredModelViewProjection);
1001 QSSGRenderCameraList cameras({ alteredCamera });
1002 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, cameras, nullptr, alteredModelViewProjection);
1003 } else {
1004 Q_ASSERT(!alteredModelViewProjection);
1005 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
1006 }
1007
1008 if (blendParticles)
1009 QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &subsetRenderable.modelContext.model, subsetRenderable.subset.offset);
1010 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1011
1012 if (blendParticles)
1013 QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &subsetRenderable.modelContext.model);
1014
1015 // Skinning
1016 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
1017 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
1018 if (binding >= 0) {
1019 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
1020 QRhiSampler::Nearest,
1021 QRhiSampler::None,
1022 QRhiSampler::ClampToEdge,
1023 QRhiSampler::ClampToEdge,
1024 QRhiSampler::Repeat
1025 });
1026 bindings.addTexture(binding,
1027 QRhiShaderResourceBinding::VertexStage,
1028 boneTexture,
1029 boneSampler);
1030 }
1031 }
1032 // Morphing
1033 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
1034 if (targetsTexture) {
1035 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
1036 if (binding >= 0) {
1037 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
1038 QRhiSampler::Nearest,
1039 QRhiSampler::None,
1040 QRhiSampler::ClampToEdge,
1041 QRhiSampler::ClampToEdge,
1042 QRhiSampler::ClampToEdge
1043 });
1044 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
1045 }
1046 }
1047
1048 ps->samples = samples;
1049 ps->viewCount = viewCount;
1050
1051 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
1052 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
1053 if (!oit || (oit && inData.layer.oitMethod == QSSGRenderLayer::OITMethod::None))
1054 fillTargetBlend(&ps->targetBlend[0], material.blendMode);
1055
1056 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
1057
1058 ia = subsetRenderable.subset.rhi.ia;
1059 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
1060 QVector3D cameraDirection = cameraDatas[0].direction;
1061 QVector3D cameraPosition = cameraDatas[0].position;
1062 if (alteredCamera) {
1063 const QMatrix4x4 camGlobalTranform = inData.getGlobalTransform(*alteredCamera);
1064 cameraDirection = QSSGRenderNode::getScalingCorrectDirection(camGlobalTranform);
1065 cameraPosition = QSSGRenderNode::getGlobalPos(camGlobalTranform);
1066 }
1067 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
1068 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
1069
1070 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf, 0, shaderPipeline->ub0Size());
1071
1072 if (shaderPipeline->isLightingEnabled()) {
1073 bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd.ubuf,
1074 shaderPipeline->ub0LightDataOffset(),
1075 sizeof(QSSGShaderLightsUniformData));
1076 bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd.ubuf,
1077 shaderPipeline->ub0DirectionalLightDataOffset(),
1078 sizeof(QSSGShaderDirectionalLightsUniformData));
1079
1080 }
1081
1082 // Texture maps
1083 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
1084 while (renderableImage) {
1085 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
1086 const int samplerHint = int(renderableImage->m_mapType);
1087 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
1088 if (samplerBinding >= 0) {
1089 QRhiTexture *texture = renderableImage->m_texture.m_texture;
1090 if (samplerBinding >= 0 && texture) {
1091 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
1092 QSSGRhiSamplerDescription samplerDesc = {
1093 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
1094 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
1095 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
1096 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
1097 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
1098 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
1099 };
1100 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
1101 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
1102 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
1103 }
1104 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
1105 renderableImage = renderableImage->m_nextImage;
1106 }
1107
1108 if (shaderPipeline->isLightingEnabled()) {
1109 // Shadow map atlas
1110 auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
1111 if (shadowMapAtlas) {
1112 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
1113 if (binding >= 0) {
1114 QRhiTexture *texture = shadowMapAtlas;
1115 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1116 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1117 Q_ASSERT(texture && sampler);
1118 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
1119 }
1120 }
1121
1122 // Shadow map blue noise
1123 if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
1124 if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
1125 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
1126 if (binding >= 0) {
1127 QRhiTexture *texture = shadowMapBlueNoise;
1128 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
1129 QRhiSampler::Linear,
1130 QRhiSampler::None,
1131 QRhiSampler::Repeat,
1132 QRhiSampler::Repeat,
1133 QRhiSampler::Repeat });
1134 Q_ASSERT(texture && sampler);
1135 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
1136 }
1137 } else {
1138 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
1139 QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
1140 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
1141 QRhiSampler::Linear,
1142 QRhiSampler::None,
1143 QRhiSampler::Repeat,
1144 QRhiSampler::Repeat,
1145 QRhiSampler::Repeat });
1146 Q_ASSERT(texture && sampler);
1147 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
1148 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
1149 }
1150 }
1151
1152 // Prioritize reflection texture over Light Probe texture because
1153 // reflection texture also contains the irradiance and pre filtered
1154 // values for the light probe.
1155 if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
1156 int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
1157 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
1158 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1159 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
1160 if (reflectionSampler >= 0 && reflectionTexture)
1161 bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
1162 } else if (shaderPipeline->lightProbeTexture()) {
1163 int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
1164 if (binding >= 0) {
1165 auto tiling = shaderPipeline->lightProbeTiling();
1166 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, // enables mipmapping
1167 QSSGRhiHelpers::toRhi(tiling.first), QSSGRhiHelpers::toRhi(tiling.second), QRhiSampler::Repeat });
1168 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage,
1169 shaderPipeline->lightProbeTexture(), sampler);
1170 } // else ignore, not an error (since non-lighting passes wont need it)
1171 }
1172
1173 // Screen Texture
1174 if (shaderPipeline->screenTexture()) {
1175 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
1176 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
1177 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
1178 // linear min/mag, mipmap filtering depends on the
1179 // texture, with SCREEN_TEXTURE there are no mipmaps, but
1180 // once SCREEN_MIP_TEXTURE is seen the texture (the same
1181 // one) has mipmaps generated.
1182 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
1183 ? QRhiSampler::Linear : QRhiSampler::None;
1184 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
1185 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
1186 if (screenTextureBinding >= 0) {
1187 bindings.addTexture(screenTextureBinding,
1188 QRhiShaderResourceBinding::FragmentStage,
1189 shaderPipeline->screenTexture(), sampler);
1190 }
1191 if (screenTextureArrayBinding >= 0) {
1192 bindings.addTexture(screenTextureArrayBinding,
1193 QRhiShaderResourceBinding::FragmentStage,
1194 shaderPipeline->screenTexture(), sampler);
1195 }
1196 } // else ignore, not an error
1197 }
1198
1199 if (shaderPipeline->lightmapTexture()) {
1200 int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
1201 if (binding >= 0) {
1202 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1203 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1204 bindings.addTexture(binding,
1205 QRhiShaderResourceBinding::FragmentStage,
1206 shaderPipeline->lightmapTexture(), sampler);
1207 } // else ignore, not an error
1208 }
1209 }
1210
1211 // Depth and SSAO textures
1212 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1213
1214 // Normal texture
1215 addNormalTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
1216
1217 if (oit && inData.layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
1218 RenderHelpers::addAccumulatorImageBindings(shaderPipeline.get(), bindings);
1219
1220 // Instead of always doing a QHash find in srb(), store the binding
1221 // list and the srb object in the per-model+material
1222 // QSSGRhiUniformBufferSet. While this still needs comparing the
1223 // binding list, to see if something has changed, it results in
1224 // significant gains with lots of models in the scene (because the
1225 // srb hash table becomes large then, so avoiding the lookup as
1226 // much as possible is helpful)
1227 QRhiShaderResourceBindings *&srb = dcd.srb;
1228 bool srbChanged = false;
1229 if (!srb || bindings != dcd.bindings) {
1230 srb = rhiCtxD->srb(bindings);
1231 rhiCtxD->releaseCachedSrb(dcd.bindings);
1232 dcd.bindings = bindings;
1233 srbChanged = true;
1234 }
1235
1236 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1237 subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
1238 else
1239 subsetRenderable.rhiRenderData.mainPass.srb = srb;
1240
1241 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
1242 if (dcd.pipeline
1243 && !srbChanged
1244 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
1245 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
1246 && dcd.ps == *ps)
1247 {
1248 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1249 subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
1250 else
1251 subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
1252 } else {
1253 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1254 subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1255 renderPassDescriptor,
1256 srb);
1257 dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1258 } else {
1259 subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(pipelineKey,
1260 renderPassDescriptor,
1261 srb);
1262 dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
1263 }
1264 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
1265 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
1266 dcd.ps = *ps;
1267 }
1268 }
1269 break;
1270 }
1271 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1272 {
1273 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1274 const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1275 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1276
1277 featureSet.set(QSSGShaderFeatures::Feature::LightProbe, inData.layer.lightProbe || material.m_iblProbe);
1278
1279 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections))
1280 featureSet.set(QSSGShaderFeatures::Feature::ReflectionProbe, true);
1281
1282 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1283 // Disable tonemapping for the reflection pass
1284 featureSet.disableTonemapping();
1285 }
1286
1287 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1288 featureSet.set(QSSGShaderFeatures::Feature::Lightmap, true);
1289
1290 customMaterialSystem.rhiPrepareRenderable(ps, passKey, subsetRenderable, featureSet,
1291 material, inData, renderPassDescriptor, samples, viewCount,
1292 alteredCamera, cubeFace, alteredModelViewProjection, entry, oit);
1293 break;
1294 }
1295 case QSSGRenderableObject::Type::Particles:
1296 {
1297 QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
1298 const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable, featureSet);
1299 if (shaderPipeline) {
1300 QSSGParticleRenderer::rhiPrepareRenderable(*shaderPipeline, passKey, rhiCtx, ps, particleRenderable, inData, renderPassDescriptor, samples, viewCount,
1301 alteredCamera, cubeFace, entry, oit);
1302 }
1303 break;
1304 }
1305 }
1306}
1307
1308void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
1309 const QSSGRhiGraphicsPipelineState &state,
1310 QSSGRenderableObject &object,
1311 bool *needsSetViewport,
1312 QSSGRenderTextureCubeFace cubeFace,
1313 qsizetype userPassIndex)
1314{
1315 switch (object.type) {
1316 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1317 {
1318 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1319
1320 QRhiGraphicsPipeline *ps = (userPassIndex >= 0) ? subsetRenderable.rhiRenderData.userPassData[userPassIndex].pipeline : subsetRenderable.rhiRenderData.mainPass.pipeline;
1321 QRhiShaderResourceBindings *srb = (userPassIndex >= 0) ? subsetRenderable.rhiRenderData.userPassData[userPassIndex].srb : subsetRenderable.rhiRenderData.mainPass.srb;
1322
1323 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1324 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
1325 ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1326 srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
1327 }
1328
1329 if (!ps || !srb)
1330 return;
1331
1332 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
1333 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
1334
1335 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1336 // QRhi optimizes out unnecessary binding of the same pipline
1337 cb->setGraphicsPipeline(ps);
1338 cb->setShaderResources(srb);
1339
1340 if (*needsSetViewport) {
1341 cb->setViewport(state.viewport);
1342 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
1343 cb->setScissor(state.scissor);
1344 *needsSetViewport = false;
1345 }
1346
1347 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1348 int vertexBufferCount = 1;
1349 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1350 quint32 instances = 1;
1351 if ( subsetRenderable.modelContext.model.instancing()) {
1352 instances = subsetRenderable.modelContext.model.instanceCount();
1353 // If the instance count is 0, the bail out before trying to do any
1354 // draw calls. Making an instanced draw call with a count of 0 is invalid
1355 // for Metal and likely other API's as well.
1356 // It is possible that the particale system may produce 0 instances here
1357 if (instances == 0)
1358 return;
1359 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
1360 vertexBufferCount = 2;
1361 }
1362 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1363 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
1364 cb->setStencilRef(state.stencilRef);
1365 if (indexBuffer) {
1366 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
1367 cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
1368 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
1369 } else {
1370 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1371 cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
1372 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
1373 }
1374 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
1375 QVector<int>({subsetRenderable.modelContext.model.profilingId,
1376 subsetRenderable.material.profilingId}));
1377 break;
1378 }
1379 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1380 {
1381 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1382 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1383 customMaterialSystem.rhiRenderRenderable(rhiCtx, subsetRenderable, needsSetViewport, cubeFace, state, userPassIndex);
1384 break;
1385 }
1386 case QSSGRenderableObject::Type::Particles:
1387 {
1388 QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
1389 QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
1390 break;
1391 }
1392 }
1393}
1394
1395static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp) {
1396 // Convert normalized offsets/scales to actual pixel values
1397 float x = atlasEntry.uOffset * atlasPixelSize.width();
1398 float w = atlasEntry.uvScale * atlasPixelSize.width();
1399 float h = atlasEntry.uvScale * atlasPixelSize.height();
1400
1401 // Y in atlasEntry is top-based, whereas QRhiViewport is bottom-based
1402 // topY in pixel = atlasEntry.vOffset * atlasPixelSize.height()
1403 // so bottom-left Y = totalHeight - topY - h
1404 float y;
1405 if (!yIsUp)
1406 y = atlasPixelSize.height() - (atlasEntry.vOffset * atlasPixelSize.height()) - h;
1407 else
1408 y = atlasEntry.vOffset * atlasPixelSize.height();
1409
1410 return QRhiViewport(x, y, w, h);
1411}
1412
1413void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
1414 QSSGPassKey passKey,
1415 QSSGRhiGraphicsPipelineState &ps,
1416 QSSGRenderShadowMap &shadowMapManager,
1417 const QSSGRenderCamera &camera,
1418 QSSGRenderCamera *debugCamera,
1419 const QSSGShaderLightList &globalLights,
1420 const QSSGRenderableObjectList &sortedOpaqueObjects,
1421 QSSGRenderer &renderer,
1422 const QSSGBounds3 &castingObjectsBox,
1423 const QSSGBounds3 &receivingObjectsBox)
1424{
1425 const QSSGLayerRenderData &layerData = *QSSGLayerRenderData::getCurrent(renderer);
1426 QSSGDebugDrawSystem *debugDrawSystem = renderer.contextInterface()->debugDrawSystem().get();
1427 const bool drawDirectionalLightShadowBoxes = layerData.layer.drawDirectionalLightShadowBoxes;
1428 const bool drawPointLightShadowBoxes = layerData.layer.drawPointLightShadowBoxes;
1429 const bool drawShadowCastingBounds = layerData.layer.drawShadowCastingBounds;
1430 const bool drawShadowReceivingBounds = layerData.layer.drawShadowReceivingBounds;
1431 const bool drawCascades = layerData.layer.drawCascades;
1432 const bool drawSceneCascadeIntersection = layerData.layer.drawSceneCascadeIntersection;
1433 const bool disableShadowCameraUpdate = layerData.layer.disableShadowCameraUpdate;
1434 const bool drawCulledObjects = layerData.layer.drawCulledObjects;
1435 QVector<bool> debugIsObjectCulled = drawCulledObjects ? QVector<bool>(sortedOpaqueObjects.size(), true) : QVector<bool>();
1436
1437 static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
1438 QSSGRhiGraphicsPipelineState *ps,
1439 const QSSGRenderableObjectList &sortedOpaqueObjects,
1440 int cubeFace,
1441 const QSSGBounds3 cameraBounds,
1442 QVector<bool> &debugIsObjectCulled,
1443 bool drawCulledObjects) {
1444 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1445 bool needsSetViewport = true;
1446
1447 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1448 const QSSGRenderableObjectHandle &handle = sortedOpaqueObjects[i];
1449 QSSGRenderableObject *theObject = handle.obj;
1450
1451 // Only attempt to cull models if both of its bounds are valid
1452 if (theObject->globalBoundsInstancing.isFinite() && theObject->globalBounds.isFinite()) {
1453 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1454 : theObject->globalBounds;
1455 if (!globalBounds.isEmpty() && !cameraBounds.intersects(globalBounds)) {
1456 continue;
1457 }
1458 }
1459
1460 if (Q_UNLIKELY(drawCulledObjects))
1461 debugIsObjectCulled[i] = false;
1462
1463 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1464 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1465 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
1466
1467 QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
1468 QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
1469 ? renderable->subset.rhi.indexBuffer->buffer()
1470 : nullptr;
1471
1472 // Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
1473 if (!renderable->rhiRenderData.shadowPass.pipeline)
1474 continue;
1475
1476 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1477
1478 cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
1479
1480 QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
1481 cb->setShaderResources(srb);
1482
1483 if (needsSetViewport) {
1484 cb->setViewport(ps->viewport);
1485 needsSetViewport = false;
1486 }
1487
1488 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1489 int vertexBufferCount = 1;
1490 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1491 quint32 instances = 1;
1492 if (renderable->modelContext.model.instancing()) {
1493 instances = renderable->modelContext.model.instanceCount();
1494 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
1495 vertexBufferCount = 2;
1496 }
1497 if (indexBuffer) {
1498 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, renderable->subset.rhi.indexBuffer->indexFormat());
1499 cb->drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances, renderable->subset.lodOffset(renderable->subsetLevelOfDetail));
1500 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.lodCount(renderable->subsetLevelOfDetail), instances));
1501 } else {
1502 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
1503 cb->draw(renderable->subset.count, instances, renderable->subset.offset);
1504 QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
1505 }
1506 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
1507 QVector<int>({renderable->modelContext.model.profilingId,
1508 renderable->material.profilingId}));
1509 }
1510 }
1511 };
1512
1513 static const auto rhiClearShadowMap = [](QSSGRenderer &renderer, QSSGRenderShadowMap &shadowMapManager, QSSGRhiContext *rhiCtx, QSSGRhiGraphicsPipelineState *ps, QRhiRenderPassDescriptor *renderPassDesc) {
1514 auto clearShadowMapShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiClearShadowMapShader();
1515 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, clearShadowMapShaderPipeline.get());
1516
1517 // Disable Depth Test and Depth Write
1518 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, false);
1519 ps->flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1520 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, shadowMapManager.shadowClearSrb(), renderPassDesc, {});
1521 // Reset
1522 ps->flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1523 };
1524
1525 QRhi *rhi = rhiCtx->rhi();
1526 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1527
1528 // We need to deal with a clip depth range of [0, 1] or
1529 // [-1, 1], depending on the graphics API underneath.
1530 QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
1531 if (rhi->isClipDepthZeroToOne()) {
1532 // d is [0, 1] so no need for any mapping
1533 depthAdjust[0] = 0.0f;
1534 depthAdjust[1] = 1.0f;
1535 } else {
1536 // d is [-1, 1]
1537 depthAdjust[0] = 1.0f;
1538 depthAdjust[1] = 0.5f;
1539 }
1540
1541 if (drawShadowCastingBounds)
1542 ShadowmapHelpers::addDebugBox(castingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Red, debugDrawSystem);
1543 if (drawShadowReceivingBounds)
1544 ShadowmapHelpers::addDebugBox(receivingObjectsBox.toQSSGBoxPointsNoEmptyCheck(), QColorConstants::Green, debugDrawSystem);
1545
1546 // Create shadow map for each light in the scene
1547 const QSize atlasTextureSize = shadowMapManager.shadowMapAtlasTexture()->pixelSize();
1548 // Make sure quad renderer is ready
1549 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
1550 for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
1551 if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
1552 continue;
1553
1554 QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(i);
1555 if (!pEntry)
1556 continue;
1557
1558 const auto &light = globalLights[i].light;
1559
1560 if (!shadowMapManager.shadowMapAtlasTexture())
1561 break;
1562
1563 if (light->type == QSSGRenderLight::Type::DirectionalLight || light->type == QSSGRenderLight::Type::SpotLight) {
1564 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1565
1566 // This is just a way to store the old camera so we can use it for debug
1567 // drawing. There are probably cleaner ways to do this
1568 if (!disableShadowCameraUpdate && debugCamera) {
1569 debugCamera->clipPlanes = camera.clipPlanes;
1570 debugCamera->projection = camera.projection;
1571 // NOTE: Since the debug camera is an internally injected camera, there will only be
1572 // the local transform. Anywhere the global transform is looked up for the debug camera
1573 // it will return the local transform.
1574 debugCamera->localTransform = layerData.getGlobalTransform(camera);
1575 }
1576
1577 QVarLengthArray<std::unique_ptr<QSSGRenderCamera>, 4> cascades;
1578 if (light->type == QSSGRenderLight::Type::DirectionalLight) {
1579 const float pcfRadius = light->m_softShadowQuality == QSSGRenderLight::SoftShadowQuality::Hard ? 0.f : light->m_pcfFactor;
1580 const float clipNear = camera.clipPlanes.clipNear();
1581 const float clipFar = qMin(light->m_shadowMapFar, camera.clipPlanes.clipFar());
1582 const float clipRange = clipFar - clipNear;
1583 cascades = setupCascadingCamerasForShadowMap(layerData,
1584 disableShadowCameraUpdate ? *debugCamera : camera,
1585 light,
1586 size.width(),
1587 pcfRadius,
1588 clipNear,
1589 clipFar,
1590 castingObjectsBox,
1591 receivingObjectsBox,
1592 light->m_lockShadowmapTexels,
1593 debugDrawSystem,
1594 drawCascades,
1595 drawSceneCascadeIntersection);
1596
1597 // Write the split distances from value 0 in the z-axis of the eye view-space
1598 pEntry->m_csmSplits[0] = clipNear + clipRange * (light->m_csmNumSplits > 0 ? light->m_csmSplit1 : 1.0f);
1599 pEntry->m_csmSplits[1] = clipNear + clipRange * (light->m_csmNumSplits > 1 ? light->m_csmSplit2 : 1.0f);
1600 pEntry->m_csmSplits[2] = clipNear + clipRange * (light->m_csmNumSplits > 2 ? light->m_csmSplit3 : 1.0f);
1601 pEntry->m_csmSplits[3] = clipNear + clipRange * 1.0f;
1602 pEntry->m_shadowMapFar = clipFar;
1603 } else if (light->type == QSSGRenderLight::Type::SpotLight) {
1604 auto spotlightCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderCamera::Type::PerspectiveCamera);
1605 spotlightCamera->fov = QSSGRenderCamera::FieldOfView::fromDegrees(light->m_coneAngle * 2.0f);
1606 spotlightCamera->clipPlanes = { 1.0f, light->m_shadowMapFar };
1607 const QMatrix4x4 lightGlobalTransform = layerData.getGlobalTransform(*light);
1608 const QVector3D lightDir = QSSGRenderNode::getDirection(lightGlobalTransform);
1609 const QVector3D lightPos = QSSGRenderNode::getGlobalPos(lightGlobalTransform) - lightDir * spotlightCamera->clipPlanes.clipNear();
1610 const QVector3D lightPivot = light->pivot;
1611 const QVector3D forward = lightDir.normalized();
1612 const QVector3D right = qFuzzyCompare(qAbs(forward.y()), 1.0f)
1613 ? QVector3D::crossProduct(forward, QVector3D(1, 0, 0)).normalized()
1614 : QVector3D::crossProduct(forward, QVector3D(0, 1, 0)).normalized();
1615 const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
1616 spotlightCamera->localTransform = QSSGRenderNode::calculateTransformMatrix(lightPos,
1617 QSSGRenderNode::initScale,
1618 lightPivot,
1619 QQuaternion::fromDirection(forward, up));
1620 QRectF theViewport(0.0f, 0.0f, (float)light->m_shadowMapRes, (float)light->m_shadowMapRes);
1621 QSSGRenderCamera::calculateProjectionInternal(*spotlightCamera, theViewport);
1622 cascades.push_back(std::move(spotlightCamera));
1623 pEntry->m_shadowMapFar = light->m_shadowMapFar;
1624 } else {
1625 Q_UNREACHABLE();
1626 }
1627
1628 memset(pEntry->m_csmActive, 0, sizeof(pEntry->m_csmActive));
1629
1630 QMatrix4x4 cascadeCameraGlobalTransforms(Qt::Uninitialized);
1631 const QMatrix4x4 bias = { 0.5, 0.0, 0.0, 0.5,
1632 0.0, 0.5, 0.0, 0.5,
1633 0.0, 0.0, 0.5, 0.5,
1634 0.0, 0.0, 0.0, 1.0 };
1635
1636 for (int cascadeIndex = 0; cascadeIndex < cascades.length(); cascadeIndex++) {
1637 const auto &cascadeCamera = cascades[cascadeIndex];
1638 if (!cascadeCamera)
1639 continue;
1640
1641 cascadeCameraGlobalTransforms = layerData.getGlobalTransform(*cascadeCamera);
1642 pEntry->m_csmActive[cascadeIndex] = 1.f;
1643 QMatrix4x4 &viewProjection = pEntry->m_lightViewProjection[cascadeIndex];
1644 cascadeCamera->calculateViewProjectionMatrix(cascadeCameraGlobalTransforms, viewProjection);
1645 pEntry->m_lightViewProjection[cascadeIndex] = viewProjection;
1646 pEntry->m_fixedScaleBiasMatrix[cascadeIndex] = bias * viewProjection;
1647 const QMatrix4x4 inverted = viewProjection.inverted();
1648 const float x = 0.5f / (inverted * QVector4D(1, 0, 0, 0)).length();
1649 const float y = 0.5f / (inverted * QVector4D(0, 1, 0, 0)).length();
1650 const float z = 0.5f / (inverted * QVector4D(0, 0, 1, 0)).length();
1651 const QSSGBoxPoints frustumPoints = computeFrustumBounds(viewProjection);
1652 const QSSGBounds3 bounds = QSSGBounds3(frustumPoints);
1653 pEntry->m_dimensionsInverted[cascadeIndex] = QVector4D(x, y, z, 0.0f);
1654 pEntry->m_lightView = cascadeCameraGlobalTransforms.inverted(); // pre-calculate this for the material
1655 const bool isOrtho = cascadeCamera->type == QSSGRenderGraphObject::Type::OrthographicCamera;
1656 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[cascadeIndex], rhi->isYUpInFramebuffer());
1657 rhiPrepareResourcesForShadowMap(rhiCtx, layerData, passKey, pEntry, &ps, &depthAdjust, sortedOpaqueObjects, *cascadeCamera, isOrtho, QSSGRenderTextureCubeFaceNone, cascadeIndex);
1658 // Render into the 2D texture pEntry->m_rhiDepthMap, using
1659 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1660 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[cascadeIndex];
1661 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1662 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1663 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1664 rhiClearShadowMap(renderer, shadowMapManager, rhiCtx, &ps, rt->renderPassDescriptor());
1665 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0, bounds, debugIsObjectCulled, drawCulledObjects);
1666 cb->endPass();
1667 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1668
1669 if (drawDirectionalLightShadowBoxes) {
1670 ShadowmapHelpers::addDirectionalLightDebugBox(frustumPoints, debugDrawSystem);
1671 }
1672 }
1673 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
1674 } else { // Point Light
1675 const QSize size = atlasTextureSize * pEntry->m_atlasInfo[0].uvScale;
1676 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1677
1678 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1679 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1680 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1681 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1682 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1683 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1684 const float shadowMapFar = qMax<float>(2.0f, light->m_shadowMapFar);
1685 setupCubeShadowCameras(layerData, light, shadowMapFar, theCameras);
1686 pEntry->m_lightView = QMatrix4x4();
1687 pEntry->m_shadowMapFar = shadowMapFar;
1688
1689 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1690 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1691 for (const auto face : QSSGRenderTextureCubeFaces) {
1692 cameraGlobalTransform = layerData.getGlobalTransform(theCameras[quint8(face)]);
1693 theCameras[quint8(face)].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_lightViewProjection[0]);
1694 pEntry->m_lightCubeView[quint8(face)] = cameraGlobalTransform.inverted(); // pre-calculate this for the material
1695
1696 rhiPrepareResourcesForShadowMap(rhiCtx,
1697 layerData,
1698 passKey,
1699 pEntry,
1700 &ps,
1701 &depthAdjust,
1702 sortedOpaqueObjects,
1703 theCameras[quint8(face)],
1704 false,
1705 face,
1706 0);
1707 }
1708
1709 // The bounds should be the same for all view projections of the cube
1710 const QVector3D center = QSSGRenderNode::getGlobalPos(layerData.getGlobalTransform(*light));
1711 const QSSGBounds3 bounds = QSSGBounds3(center - QVector3D(shadowMapFar, shadowMapFar, shadowMapFar),
1712 center + QVector3D(shadowMapFar, shadowMapFar, shadowMapFar));
1713
1714 for (const auto face : QSSGRenderTextureCubeFaces) {
1715 // Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
1716 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1717
1718 QSSGRenderTextureCubeFace outFace = face;
1719 // FACE S T GL
1720 // +x -z, -y right
1721 // -x +z, -y left
1722 // +y +x, +z top
1723 // -y +x, -z bottom
1724 // +z +x, -y front
1725 // -z -x, -y back
1726 // FACE S T D3D
1727 // +x -z, +y right
1728 // -x +z, +y left
1729 // +y +x, -z bottom
1730 // -y +x, +z top
1731 // +z +x, +y front
1732 // -z -x, +y back
1733 if (swapYFaces) {
1734 // +Y and -Y faces get swapped (D3D, Vulkan, Metal).
1735 // See shadowMapping.glsllib. This is complemented there by reversing T as well.
1736 if (outFace == QSSGRenderTextureCubeFace::PosY)
1737 outFace = QSSGRenderTextureCubeFace::NegY;
1738 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1739 outFace = QSSGRenderTextureCubeFace::PosY;
1740 }
1741 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargetCube[quint8(outFace)];
1742 cb->beginPass(rt, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1743 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1744 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1745 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face), bounds, debugIsObjectCulled, drawCulledObjects);
1746 cb->endPass();
1747 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1748 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
1749 }
1750
1751 // Render the CubeMap into the shadowMapAtlasTexture
1752 // NOTE: These passes don't require a manual clear because we will always write every pixel in that atlas space
1753
1754 // Render the front hemisphere of the cube map into the shadow map atlas
1755 QRhiTextureRenderTarget *rtFront = pEntry->m_rhiRenderTargets[0]; // A layer in the Atlas
1756 QRhiRenderPassDescriptor *frontDesc = pEntry->m_rhiRenderPassDesc[0];
1757 auto atlasShaderPipeline = renderer.contextInterface()->shaderCache()->getBuiltInRhiShaders().getRhiCubeMapToAtlasShader();
1758 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[0], rhi->isYUpInFramebuffer());
1759 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, atlasShaderPipeline.get());
1760 QRhiShaderResourceBindings *srb = pEntry->m_cubeToAtlasFrontSrb;
1761 cb->beginPass(rtFront, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1762 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtFront));
1763 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1764
1765 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, frontDesc, QSSGRhiQuadRenderer::UvCoords);
1766
1767 cb->endPass();
1768 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1769 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 6, 0));
1770
1771 // Render the back hemisphere of the cube map into the shadow map atlas
1772 QRhiTextureRenderTarget *rtBack = pEntry->m_rhiRenderTargets[1]; // A layer in the Atlas
1773 QRhiRenderPassDescriptor *backDesc = pEntry->m_rhiRenderPassDesc[1];
1774 srb = pEntry->m_cubeToAtlasBackSrb;
1775 ps.viewport = calculateAtlasViewport(atlasTextureSize, pEntry->m_atlasInfo[1], rhi->isYUpInFramebuffer());
1776 cb->beginPass(rtBack, Qt::white, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1777 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rtBack));
1778 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1779
1780 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, backDesc, QSSGRhiQuadRenderer::UvCoords);
1781
1782 cb->endPass();
1783 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1784 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_atlas", 7, 0));
1785
1786
1787 // reset pipeline (This part is necessary, but not ideal)
1788 ps = layerData.getPipelineState();
1789 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
1790 ps.depthBias = 2;
1791 ps.slopeScaledDepthBias = 1.5f;
1792
1793 if (drawPointLightShadowBoxes) {
1794 ShadowmapHelpers::addDebugBox(bounds.toQSSGBoxPoints(), QColorConstants::Yellow, debugDrawSystem);
1795 }
1796 }
1797 }
1798
1799 if (Q_UNLIKELY(drawCulledObjects)) {
1800 for (int i = 0, n = sortedOpaqueObjects.size(); i < n; ++i) {
1801 QSSGRenderableObject *theObject = sortedOpaqueObjects[i].obj;
1802 const QSSGBounds3 &globalBounds = !theObject->globalBoundsInstancing.isEmpty() ? theObject->globalBoundsInstancing
1803 : theObject->globalBounds;
1804 const QColor color = debugIsObjectCulled[i] ? QColorConstants::Red : QColorConstants::Green;
1805 ShadowmapHelpers::addDebugBox(globalBounds.toQSSGBoxPointsNoEmptyCheck(), color, debugDrawSystem);
1806 }
1807 }
1808}
1809
1810void RenderHelpers::rhiRenderReflectionMap(QSSGRhiContext *rhiCtx,
1811 QSSGPassKey passKey,
1812 const QSSGLayerRenderData &inData,
1813 QSSGRhiGraphicsPipelineState *ps,
1814 QSSGRenderReflectionMap &reflectionMapManager,
1815 const QVector<QSSGRenderReflectionProbe *> &reflectionProbes,
1816 const QSSGRenderableObjectList &reflectionPassObjects,
1817 QSSGRenderer &renderer)
1818{
1819 QRhi *rhi = rhiCtx->rhi();
1820 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1821
1822 const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox ||
1823 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1824 && rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch);
1825
1826 for (int i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
1827 QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(i);
1828 if (!pEntry)
1829 continue;
1830
1831 if (!pEntry->m_needsRender)
1832 continue;
1833
1834 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
1835 continue;
1836
1837 if (reflectionProbes[i]->texture)
1838 continue;
1839
1840 Q_ASSERT(pEntry->m_rhiDepthStencil);
1841 Q_ASSERT(pEntry->m_rhiCube);
1842
1843 const QSize size = pEntry->m_rhiCube->pixelSize();
1844 ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1845
1846 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1847 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1848 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1849 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1850 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1851 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1852 setupCubeReflectionCameras(inData, reflectionProbes[i], theCameras);
1853 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1854 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1855 for (const auto face : QSSGRenderTextureCubeFaces) {
1856 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
1857 cameraGlobalTransform = inData.getGlobalTransform(theCameras[cubeFaceIdx]);
1858 theCameras[cubeFaceIdx].calculateViewProjectionMatrix(cameraGlobalTransform, pEntry->m_viewProjection);
1859
1860 rhiPrepareResourcesForReflectionMap(rhiCtx, passKey, inData, pEntry, ps,
1861 reflectionPassObjects, theCameras[cubeFaceIdx], renderer, face);
1862 }
1863 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
1864 for (auto face : QSSGRenderTextureCubeFaces) {
1865 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1866 face = pEntry->m_timeSliceFace;
1867
1868 QSSGRenderTextureCubeFace outFace = face;
1869 // Faces are swapped similarly to shadow maps due to differences in backends
1870 // Prefilter step handles correcting orientation differences in the final render
1871 if (swapYFaces) {
1872 if (outFace == QSSGRenderTextureCubeFace::PosY)
1873 outFace = QSSGRenderTextureCubeFace::NegY;
1874 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1875 outFace = QSSGRenderTextureCubeFace::PosY;
1876 }
1877 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1878 cb->beginPass(rt, reflectionProbes[i]->clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
1879 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1880 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1881
1882 if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
1883 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1884 const bool isSkyBox = inData.layer.background == QSSGRenderLayer::Background::SkyBox;
1885 const auto &shaderPipeline = isSkyBox ? shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(QSSGRenderLayer::TonemapMode::None, inData.layer.skyBoxIsRgbe8, 1)
1886 : shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(QSSGRenderLayer::TonemapMode::None, !inData.layer.skyBoxIsSrgb, 1);
1887 Q_ASSERT(shaderPipeline);
1888 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
1889 QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
1890 if (!renderPassDesc)
1891 renderPassDesc = rt->newCompatibleRenderPassDescriptor();
1892 rt->setRenderPassDescriptor(renderPassDesc);
1893 isSkyBox ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, renderPassDesc, {})
1894 : renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, renderPassDesc, {});
1895 }
1896
1897 bool needsSetViewport = true;
1898 for (const auto &handle : reflectionPassObjects)
1899 rhiRenderRenderable(rhiCtx, *ps, *handle.obj, &needsSetViewport, face);
1900
1901 cb->endPass();
1902 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1903 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
1904
1905 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1906 break;
1907 }
1908 if (renderPassDesc)
1909 renderPassDesc->deleteLater();
1910
1911 pEntry->renderMips(rhiCtx);
1912
1913 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1914 pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(pEntry->m_timeSliceFace); // Wraps
1915
1916 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
1917 pEntry->m_rendered = true;
1918
1919 reflectionProbes[i]->hasScheduledUpdate = false;
1920 pEntry->m_needsRender = false;
1921 }
1922}
1923
1924bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx,
1925 const QSize &size,
1926 QSSGRhiRenderableTexture *renderableTex,
1927 quint8 viewCount)
1928{
1929 QRhi *rhi = rhiCtx->rhi();
1930 bool needsBuild = false;
1931
1932 if (!renderableTex->texture) {
1933 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
1934 // the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
1935 if (viewCount <= 1)
1936 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
1937 else
1938 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
1939 needsBuild = true;
1940 } else if (renderableTex->texture->pixelSize() != size) {
1941 renderableTex->texture->setPixelSize(size);
1942 needsBuild = true;
1943 }
1944
1945 if (needsBuild) {
1946 if (!renderableTex->texture->create()) {
1947 qWarning("Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
1948 renderableTex->reset();
1949 return false;
1950 }
1951 renderableTex->resetRenderTarget();
1952 QRhiTextureRenderTargetDescription desc;
1953 QRhiColorAttachment colorAttachment(renderableTex->texture);
1954 colorAttachment.setMultiViewCount(viewCount);
1955 desc.setColorAttachments({ colorAttachment });
1956 renderableTex->rt = rhi->newTextureRenderTarget(desc);
1957 renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
1958 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
1959 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
1960 if (!renderableTex->rt->create()) {
1961 qWarning("Failed to build render target for ambient occlusion texture");
1962 renderableTex->reset();
1963 return false;
1964 }
1965 }
1966
1967 return true;
1968}
1969
1970void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
1971 QSSGPassKey passKey,
1972 QSSGRenderer &renderer,
1973 QSSGRhiShaderPipeline &shaderPipeline,
1974 QSSGRhiGraphicsPipelineState &ps,
1975 const QSSGAmbientOcclusionSettings &ao,
1976 const QSSGRhiRenderableTexture &rhiAoTexture,
1977 const QSSGRhiRenderableTexture &rhiDepthTexture,
1978 const QSSGRenderCamera &camera)
1979{
1980 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1981
1982 // no texelFetch in GLSL <= 120 and GLSL ES 100
1983 if (!rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
1984 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1985 // just clear and stop there
1986 cb->beginPass(rhiAoTexture.rt, Qt::white, { 1.0f, 0 });
1987 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
1988 cb->endPass();
1989 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1990 return;
1991 }
1992
1993 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, &shaderPipeline);
1994
1995 const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
1996 const QSize textureSize = rhiAoTexture.texture->pixelSize();
1997 const float rw = float(textureSize.width());
1998 const float rh = float(textureSize.height());
1999 const float fov = camera.fov.asVerticalFov(rw / rh).radians();
2000 const float tanHalfFovY = tanf(0.5f * fov * (rh / rw));
2001 const float invFocalLenX = tanHalfFovY * (rw / rh);
2002
2003 const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
2004 const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
2005 const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
2006 const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
2007 const QVector2D cameraProps = camera.clipPlanes;
2008
2009 // layout(std140, binding = 0) uniform buf {
2010 // vec4 aoProperties;
2011 // vec4 aoProperties2;
2012 // vec4 aoScreenConst;
2013 // vec4 uvToEyeConst;
2014 // vec2 cameraProperties;
2015
2016 const int UBUF_SIZE = 72;
2017 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 }));
2018 if (!dcd.ubuf) {
2019 dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
2020 dcd.ubuf->create();
2021 }
2022
2023 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2024 memcpy(ubufData, &aoProps, 16);
2025 memcpy(ubufData + 16, &aoProps2, 16);
2026 memcpy(ubufData + 32, &aoScreenConst, 16);
2027 memcpy(ubufData + 48, &uvToEyeConst, 16);
2028 memcpy(ubufData + 64, &cameraProps, 8);
2029 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2030
2031 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2032 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
2033 QSSGRhiShaderResourceBindingList bindings;
2034 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2035 // binding 1 is either a sampler2D or sampler2DArray, matching
2036 // rhiDepthTexture.texture, no special casing needed here
2037 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiDepthTexture.texture, sampler);
2038 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2039
2040 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2041 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, &ps, srb, rhiAoTexture.rt, {});
2042}
2043
2044bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx,
2045 const QSize &size,
2046 bool wantsMips,
2047 QSSGRhiRenderableTexture *renderableTex,
2048 quint8 viewCount)
2049{
2050 QRhi *rhi = rhiCtx->rhi();
2051 bool needsBuild = false;
2052 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
2053 if (wantsMips)
2054 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
2055
2056 if (!renderableTex->texture) {
2057 // always non-msaa, even if multisampling is used in the main pass
2058 if (viewCount <= 1)
2059 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
2060 else
2061 renderableTex->texture = rhi->newTextureArray(QRhiTexture::RGBA8, viewCount, size, 1, flags);
2062 needsBuild = true;
2063 } else if (renderableTex->texture->pixelSize() != size) {
2064 renderableTex->texture->setPixelSize(size);
2065 needsBuild = true;
2066 }
2067
2068 if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
2069 if (viewCount <= 1)
2070 renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
2071 else
2072 renderableTex->depthTexture = rhi->newTextureArray(QRhiTexture::D24S8, viewCount, size, 1, QRhiTexture::RenderTarget);
2073 needsBuild = true;
2074 } else {
2075 if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
2076 renderableTex->depthStencil->setPixelSize(size);
2077 needsBuild = true;
2078 } else if (renderableTex->depthTexture && renderableTex->depthTexture->pixelSize() != size) {
2079 renderableTex->depthTexture->setPixelSize(size);
2080 needsBuild = true;
2081 }
2082 }
2083
2084 if (needsBuild) {
2085 if (!renderableTex->texture->create()) {
2086 qWarning("Failed to build screen texture (size %dx%d)", size.width(), size.height());
2087 renderableTex->reset();
2088 return false;
2089 }
2090 if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
2091 qWarning("Failed to build depth-stencil buffer for screen texture (size %dx%d)",
2092 size.width(), size.height());
2093 renderableTex->reset();
2094 return false;
2095 } else if (renderableTex->depthTexture && !renderableTex->depthTexture->create()) {
2096 qWarning("Failed to build depth-stencil texture array (multiview) for screen texture (size %dx%d)",
2097 size.width(), size.height());
2098 renderableTex->reset();
2099 return false;
2100 }
2101 renderableTex->resetRenderTarget();
2102 QRhiTextureRenderTargetDescription desc;
2103 QRhiColorAttachment colorAttachment(renderableTex->texture);
2104 colorAttachment.setMultiViewCount(viewCount);
2105 desc.setColorAttachments({ colorAttachment });
2106 if (renderableTex->depthStencil)
2107 desc.setDepthStencilBuffer(renderableTex->depthStencil);
2108 else if (renderableTex->depthTexture)
2109 desc.setDepthTexture(renderableTex->depthTexture);
2110 renderableTex->rt = rhi->newTextureRenderTarget(desc);
2111 renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
2112 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2113 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2114 if (!renderableTex->rt->create()) {
2115 qWarning("Failed to build render target for screen texture");
2116 renderableTex->reset();
2117 return false;
2118 }
2119 }
2120
2121 return true;
2122}
2123
2124void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
2125{
2126 QSSG_ASSERT(layer.renderData, return);
2127
2128 const auto *renderData = layer.renderData;
2129
2130 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2131 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2132 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
2133
2134 QSSGRhiShaderResourceBindingList bindings;
2135
2136 int uniformBinding = 0;
2137 const int ubufSize = cameras.count() >= 2 ? 276 : 148;
2138
2139 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ passKey, nullptr, nullptr, 0 })); // Change to Grid?
2140
2141 QRhi *rhi = rhiCtx->rhi();
2142 if (!dcd.ubuf) {
2143 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2144 dcd.ubuf->create();
2145 }
2146
2147 // Param
2148 const auto clipPlanes = cameras[0]->clipPlanes;
2149 const float scale = layer.gridScale;
2150 const quint32 gridFlags = layer.gridFlags;
2151
2152 const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2153
2154 quint32 ubufOffset = 0;
2155 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2156
2157 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
2158 QMatrix4x4 viewProj(Qt::Uninitialized);
2159 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2160 cameraGlobalTransform = renderData->getGlobalTransform(*cameras[viewIdx]);
2161 cameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProj);
2162 QMatrix4x4 invViewProj = viewProj.inverted();
2163 quint32 viewDataOffset = ubufOffset;
2164 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProj.constData(), 64);
2165 viewDataOffset += 64 * cameras.count();
2166 memcpy(ubufData + viewDataOffset + viewIdx * 64, invViewProj.constData(), 64);
2167 }
2168 ubufOffset += (64 + 64) * cameras.count();
2169
2170 memcpy(ubufData + ubufOffset, &clipPlanes, 8);
2171 ubufOffset += 8;
2172 memcpy(ubufData + ubufOffset, &scale, 4);
2173 ubufOffset += 4;
2174 memcpy(ubufData + ubufOffset, &yFactor, 4);
2175 ubufOffset += 4;
2176 memcpy(ubufData + ubufOffset, &gridFlags, 4);
2177
2178 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2179
2180 bindings.addUniformBuffer(uniformBinding, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2181
2182 layer.gridSrb = rhiCtxD->srb(bindings);
2183 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2184
2185 cb->debugMarkEnd();
2186}
2187
2188static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx,
2189 QSSGPassKey passKey,
2190 QSSGRenderLayer &layer,
2191 QSSGRenderCameraList &cameras,
2192 QSSGRenderer &renderer,
2193 QSSGReflectionMapEntry *entry = nullptr,
2194 QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone,
2195 uint tonemapMode = 0)
2196{
2197 QSSG_ASSERT(layer.renderData, return);
2198
2199 const auto *renderData = layer.renderData;
2200
2201 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2202 const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
2203 const QSSGRenderImageTexture lightProbeTexture =
2204 cubeMapMode ? renderer.contextInterface()->bufferManager()->loadRenderImage(layer.skyBoxCubeMap, QSSGBufferManager::MipModeDisable)
2205 : renderer.contextInterface()->bufferManager()->loadRenderImage(layer.lightProbe, QSSGBufferManager::MipModeBsdf);
2206 const bool hasValidTexture = lightProbeTexture.m_texture != nullptr;
2207 if (hasValidTexture) {
2208 if (cubeFace == QSSGRenderTextureCubeFaceNone)
2209 layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
2210 if (cubeMapMode)
2211 layer.skyBoxIsSrgb = !lightProbeTexture.m_flags.isLinear();
2212
2213 QSSGRhiShaderResourceBindingList bindings;
2214
2215 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
2216 QRhiSampler::Linear,
2217 cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
2218 QRhiSampler::Repeat,
2219 QRhiSampler::ClampToEdge,
2220 QRhiSampler::Repeat });
2221 int samplerBinding = 1; //the shader code is hand-written, so we don't need to look that up
2222 const quint32 ubufSize = cameras.count() >= 2 ? 416 : 240; // same ubuf layout for both skybox and skyboxcube
2223 bindings.addTexture(samplerBinding,
2224 QRhiShaderResourceBinding::FragmentStage,
2225 lightProbeTexture.m_texture, sampler);
2226
2227 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2228 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
2229 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, nullptr, entry, entryIdx });
2230
2231 QRhi *rhi = rhiCtx->rhi();
2232 if (!dcd.ubuf) {
2233 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2234 dcd.ubuf->create();
2235 }
2236
2237 float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2238 const float exposure = layer.lightProbeSettings.probeExposure;
2239 // orientation
2240 const QMatrix3x3 &rotationMatrix(layer.lightProbeSettings.probeOrientation);
2241
2242 // The cubemap shader doesn't use blur or mipmapping, so it uses those for tonemapping and texture color space
2243 const float blurAmountOrSrgb = cubeMapMode ? layer.skyBoxIsSrgb : layer.skyboxBlurAmount;
2244 const float maxMipLevelOrTonemapMode = cubeMapMode ? float(tonemapMode) : float(lightProbeTexture.m_mipmapCount - 2);
2245
2246 const QVector4D skyboxProperties = {
2247 adjustY,
2248 exposure,
2249 blurAmountOrSrgb,
2250 maxMipLevelOrTonemapMode
2251 };
2252
2253 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2254 quint32 ubufOffset = 0;
2255 // skyboxProperties
2256 memcpy(ubufData + ubufOffset, &skyboxProperties, 16);
2257 ubufOffset += 16;
2258 // orientation
2259 memcpy(ubufData + ubufOffset, rotationMatrix.constData(), 12);
2260 ubufOffset += 16;
2261 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 12, 12);
2262 ubufOffset += 16;
2263 memcpy(ubufData + ubufOffset, (char *)rotationMatrix.constData() + 24, 12);
2264 ubufOffset += 16;
2265
2266 for (qsizetype viewIdx = 0; viewIdx < cameras.count(); ++viewIdx) {
2267 const QMatrix4x4 &inverseProjection = cameras[viewIdx]->projection.inverted();
2268 const QMatrix4x4 &viewMatrix = renderData->getGlobalTransform(*cameras[viewIdx]);
2269 QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
2270 cameras[viewIdx]->calculateViewProjectionWithoutTranslation(viewMatrix, 0.1f, 5.0f, viewProjection);
2271
2272 quint32 viewDataOffset = ubufOffset;
2273 memcpy(ubufData + viewDataOffset + viewIdx * 64, viewProjection.constData(), 64);
2274 viewDataOffset += cameras.count() * 64;
2275 memcpy(ubufData + viewDataOffset + viewIdx * 64, inverseProjection.constData(), 64);
2276 viewDataOffset += cameras.count() * 64;
2277 memcpy(ubufData + viewDataOffset + viewIdx * 48, viewMatrix.constData(), 48);
2278 }
2279 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2280
2281 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd.ubuf);
2282
2283 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
2284 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
2285 entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtxD->srb(bindings);
2286 } else {
2287 layer.skyBoxSrb = rhiCtxD->srb(bindings);
2288 }
2289
2290 if (cubeMapMode)
2291 renderer.rhiCubeRenderer()->prepareCube(rhiCtx, nullptr);
2292 else
2293 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, nullptr);
2294 }
2295}
2296
2297void RenderHelpers::rhiPrepareSkyBox(QSSGRhiContext *rhiCtx,
2298 QSSGPassKey passKey,
2299 QSSGRenderLayer &layer,
2300 QSSGRenderCameraList &cameras,
2301 QSSGRenderer &renderer,
2302 uint tonemapMode)
2303{
2304 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2305 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
2306
2307 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, nullptr, QSSGRenderTextureCubeFaceNone, tonemapMode);
2308
2309 cb->debugMarkEnd();
2310}
2311
2312void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(QSSGRhiContext *rhiCtx,
2313 QSSGPassKey passKey,
2314 QSSGRenderLayer &layer,
2315 QSSGRenderCamera &inCamera,
2316 QSSGRenderer &renderer,
2318 QSSGRenderTextureCubeFace cubeFace)
2319{
2320 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2321 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
2322
2323 QSSGRenderCameraList cameras({ &inCamera });
2324 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, cameras, renderer, entry, cubeFace);
2325
2326 cb->debugMarkEnd();
2327}
2328
2329bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
2330 QSSGPassKey passKey,
2331 const QSSGRhiGraphicsPipelineState &basePipelineState,
2332 QRhiRenderPassDescriptor *rpDesc,
2333 QSSGLayerRenderData &inData,
2334 const QSSGRenderableObjectList &sortedOpaqueObjects,
2335 const QSSGRenderableObjectList &sortedTransparentObjects,
2336 int samples,
2337 int viewCount)
2338{
2339 static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
2340 QSSGPassKey passKey,
2341 QSSGLayerRenderData &inData,
2343 QRhiRenderPassDescriptor *rpDesc,
2344 QSSGRhiGraphicsPipelineState *ps) {
2345 QSSGRhiShaderPipelinePtr shaderPipeline;
2346 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2347
2348 const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
2349 QSSGShaderFeatures featureSet;
2350 featureSet.set(QSSGShaderFeatures::Feature::DepthPass, true);
2351 if (isOpaqueDepthPrePass)
2352 featureSet.set(QSSGShaderFeatures::Feature::OpaqueDepthPrePass, true);
2353
2354 QSSGRhiDrawCallData *dcd = nullptr;
2355 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2356 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2357 const void *modelNode = &subsetRenderable.modelContext.model;
2358 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2359 }
2360
2361 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2362 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2363 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2364 ps->cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2365
2366 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
2367 if (shaderPipeline) {
2368 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2369 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2370 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2371 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2372 } else {
2373 return false;
2374 }
2375 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2376 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2377
2378 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2379
2380 ps->cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2381
2382 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2383 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2384
2385 if (shaderPipeline) {
2386 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2387 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2388 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, ps, customMaterial, subsetRenderable,
2389 inData.renderedCameras, nullptr, nullptr);
2390 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2391 } else {
2392 return false;
2393 }
2394 }
2395
2396 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2397 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2398 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2399 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
2400 ia = subsetRenderable.subset.rhi.ia;
2401
2402 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2403 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2404 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2405
2406 QSSGRhiShaderResourceBindingList bindings;
2407 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2408
2409 // Depth and SSAO textures, in case a custom material's shader code does something with them.
2410 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2411
2412 if (isOpaqueDepthPrePass) {
2413 addOpaqueDepthPrePassBindings(rhiCtx,
2414 shaderPipeline.get(),
2415 subsetRenderable.firstImage,
2416 bindings,
2417 (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
2418 }
2419
2420 // There is no normal texture at this stage. But the shader from a
2421 // custom material may rely on it. Bind a dummy texture then due to
2422 // the lack of other options.
2423 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2424 if (normalTextureBinding >= 0) {
2425 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2426 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2427 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2428 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2429 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2430 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2431 }
2432
2433 // Skinning
2434 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2435 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2436 if (binding >= 0) {
2437 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2438 QRhiSampler::Nearest,
2439 QRhiSampler::None,
2440 QRhiSampler::ClampToEdge,
2441 QRhiSampler::ClampToEdge,
2442 QRhiSampler::Repeat
2443 });
2444 bindings.addTexture(binding,
2445 QRhiShaderResourceBinding::VertexStage,
2446 boneTexture,
2447 boneSampler);
2448 }
2449 }
2450
2451 // Morphing
2452 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2453 if (targetsTexture) {
2454 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2455 if (binding >= 0) {
2456 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2457 QRhiSampler::Nearest,
2458 QRhiSampler::None,
2459 QRhiSampler::ClampToEdge,
2460 QRhiSampler::ClampToEdge,
2461 QRhiSampler::ClampToEdge
2462 });
2463 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2464 }
2465 }
2466
2467 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2468
2469 subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtxD->pipeline(*ps,
2470 rpDesc,
2471 srb);
2472 subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
2473 }
2474
2475 return true;
2476 };
2477
2478 // Phase 1 (prepare) for the Z prepass or the depth texture generation.
2479 // These renders opaque (Z prepass), or opaque and transparent (depth
2480 // texture), objects with depth test/write enabled, and color write
2481 // disabled, using a very simple set of shaders.
2482
2483 QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
2484 // We took a copy of the pipeline state since we do not want to conflict
2485 // with what rhiPrepare() collects for its own use. So here just change
2486 // whatever we need.
2487
2488 ps.samples = samples;
2489 ps.viewCount = viewCount;
2490 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
2491 ps.targetBlend[0].colorWrite = {};
2492
2493 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2494 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2495 return false;
2496 }
2497
2498 for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
2499 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2500 return false;
2501 }
2502
2503 return true;
2504}
2505
2506void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
2507 const QSSGRhiGraphicsPipelineState &pipelineState,
2508 const QSSGRenderableObjectList &sortedOpaqueObjects,
2509 const QSSGRenderableObjectList &sortedTransparentObjects,
2510 bool *needsSetViewport)
2511{
2512 static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
2513 const QSSGRhiGraphicsPipelineState &pipelineState,
2514 const QSSGRenderableObjectList &objects,
2515 bool *needsSetViewport) {
2516 for (const auto &oh : objects) {
2517 QSSGRenderableObject *obj = oh.obj;
2518
2519 // casts to SubsetRenderableBase so it works for both default and custom materials
2520 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2521 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2522 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2523
2524 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2525 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2526 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2527 : nullptr;
2528
2529 QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
2530 if (!ps)
2531 return;
2532
2533 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
2534 if (!srb)
2535 return;
2536
2537 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2538 cb->setGraphicsPipeline(ps);
2539 cb->setShaderResources(srb);
2540
2541 if (*needsSetViewport) {
2542 cb->setViewport(pipelineState.viewport);
2543 *needsSetViewport = false;
2544 }
2545
2546 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2547 int vertexBufferCount = 1;
2548 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2549 quint32 instances = 1;
2550 if (subsetRenderable->modelContext.model.instancing()) {
2551 instances = subsetRenderable->modelContext.model.instanceCount();
2552 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2553 vertexBufferCount = 2;
2554 }
2555
2556 if (indexBuffer) {
2557 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2558 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2559 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2560 } else {
2561 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2562 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2563 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2564 }
2565 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2566 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2567 subsetRenderable->material.profilingId}));
2568 }
2569 }
2570 };
2571
2572 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
2573 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
2574}
2575
2576bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx,
2577 const QSize &size,
2578 QSSGRhiRenderableTexture *renderableTex,
2579 quint8 viewCount,
2580 int samples)
2581{
2582 QRhi *rhi = rhiCtx->rhi();
2583 bool needsBuild = false;
2584
2585 if (!renderableTex->texture) {
2586 QRhiTexture::Format format = QRhiTexture::D32F;
2587 if (!rhi->isTextureFormatSupported(format))
2588 format = QRhiTexture::D16;
2589 if (!rhi->isTextureFormatSupported(format))
2590 qWarning("Depth texture not supported");
2591 if (viewCount <= 1)
2592 renderableTex->texture = rhiCtx->rhi()->newTexture(format, size, samples, QRhiTexture::RenderTarget);
2593 else
2594 renderableTex->texture = rhiCtx->rhi()->newTextureArray(format, viewCount, size, 1, QRhiTexture::RenderTarget);
2595 needsBuild = true;
2596 } else if (renderableTex->texture->pixelSize() != size) {
2597 renderableTex->texture->setPixelSize(size);
2598 needsBuild = true;
2599 }
2600
2601 if (needsBuild) {
2602 if (!renderableTex->texture->create()) {
2603 qWarning("Failed to build depth texture (size %dx%d, format %d)",
2604 size.width(), size.height(), int(renderableTex->texture->format()));
2605 renderableTex->reset();
2606 return false;
2607 }
2608 renderableTex->resetRenderTarget();
2609 QRhiTextureRenderTargetDescription rtDesc;
2610 rtDesc.setDepthTexture(renderableTex->texture);
2611 renderableTex->rt = rhi->newTextureRenderTarget(rtDesc);
2612 renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
2613 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2614 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2615 if (!renderableTex->rt->create()) {
2616 qWarning("Failed to build render target for depth texture");
2617 renderableTex->reset();
2618 return false;
2619 }
2620 }
2621
2622 return true;
2623}
2624
2625bool RenderHelpers::rhiPrepareNormalPass(QSSGRhiContext *rhiCtx,
2626 QSSGPassKey passKey,
2627 const QSSGRhiGraphicsPipelineState &basePipelineState,
2628 QRhiRenderPassDescriptor *rpDesc,
2629 QSSGLayerRenderData &inData,
2630 const QSSGRenderableObjectList &sortedOpaqueObjects)
2631{
2632 QSSGRhiGraphicsPipelineState ps = basePipelineState;
2633 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
2634 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
2635
2636 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2637 QSSGRenderableObject *obj = handle.obj;
2638 QSSGRhiShaderPipelinePtr shaderPipeline;
2639 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2640
2641 QSSGShaderFeatures featureSet;
2642 featureSet.set(QSSGShaderFeatures::Feature::NormalPass, true);
2643
2644 QSSGRhiDrawCallData *dcd = nullptr;
2645 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2646 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2647 const void *modelNode = &subsetRenderable.modelContext.model;
2648 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
2649 }
2650
2651 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2652 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2653 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2654 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2655
2656 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
2657 if (shaderPipeline) {
2658 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2659 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2660 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2661 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2662 }
2663 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2664 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2665
2666 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2667
2668 ps.cullMode = QSSGRhiHelpers::toCullMode(customMaterial.m_cullMode);
2669
2670 const auto &customMaterialSystem = subsetRenderable.renderer->contextInterface()->customMaterialSystem();
2671 shaderPipeline = customMaterialSystem->shadersForCustomMaterial(&ps, customMaterial, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
2672
2673 if (shaderPipeline) {
2674 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2675 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2676 customMaterialSystem->updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, customMaterial, subsetRenderable,
2677 inData.renderedCameras, nullptr, nullptr);
2678 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2679 }
2680 }
2681
2682 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2683 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2684 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2685 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
2686 ia = subsetRenderable.subset.rhi.ia;
2687
2688 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2689 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2690 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2691
2692 QSSGRhiShaderResourceBindingList bindings;
2693 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2694
2695 // Texture maps
2696 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
2697 while (renderableImage) {
2698 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
2699 const int samplerHint = int(renderableImage->m_mapType);
2700 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
2701 if (samplerBinding >= 0) {
2702 QRhiTexture *texture = renderableImage->m_texture.m_texture;
2703 if (samplerBinding >= 0 && texture) {
2704 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
2705 QSSGRhiSamplerDescription samplerDesc = {
2706 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
2707 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
2708 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
2709 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
2710 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
2711 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
2712 };
2713 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
2714 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
2715 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
2716 }
2717 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
2718 renderableImage = renderableImage->m_nextImage;
2719 }
2720
2721 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2722
2723 // There is no normal texture at this stage, obviously.
2724 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2725 if (normalTextureBinding >= 0) {
2726 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2727 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2728 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2729 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2730 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2731 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2732 }
2733
2734 // Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
2735
2736 // Skinning
2737 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2738 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2739 if (binding >= 0) {
2740 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2741 QRhiSampler::Nearest,
2742 QRhiSampler::None,
2743 QRhiSampler::ClampToEdge,
2744 QRhiSampler::ClampToEdge,
2745 QRhiSampler::Repeat
2746 });
2747 bindings.addTexture(binding,
2748 QRhiShaderResourceBinding::VertexStage,
2749 boneTexture,
2750 boneSampler);
2751 }
2752 }
2753
2754 // Morphing
2755 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
2756 if (targetsTexture) {
2757 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
2758 if (binding >= 0) {
2759 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2760 QRhiSampler::Nearest,
2761 QRhiSampler::None,
2762 QRhiSampler::ClampToEdge,
2763 QRhiSampler::ClampToEdge,
2764 QRhiSampler::ClampToEdge
2765 });
2766 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
2767 }
2768 }
2769
2770 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
2771
2772 subsetRenderable.rhiRenderData.normalPass.pipeline = rhiCtxD->pipeline(ps,
2773 rpDesc,
2774 srb);
2775 subsetRenderable.rhiRenderData.normalPass.srb = srb;
2776 }
2777 }
2778
2779 return true;
2780}
2781
2782void RenderHelpers::rhiRenderNormalPass(QSSGRhiContext *rhiCtx,
2783 const QSSGRhiGraphicsPipelineState &pipelineState,
2784 const QSSGRenderableObjectList &sortedOpaqueObjects,
2785 bool *needsSetViewport)
2786{
2787 for (const auto &oh : sortedOpaqueObjects) {
2788 QSSGRenderableObject *obj = oh.obj;
2789
2790 // casts to SubsetRenderableBase so it works for both default and custom materials
2791 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2792 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2793 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2794
2795 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2796 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2797 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2798 : nullptr;
2799
2800 QRhiGraphicsPipeline *graphicsPipeline = subsetRenderable->rhiRenderData.normalPass.pipeline;
2801 if (!graphicsPipeline)
2802 return;
2803
2804 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.normalPass.srb;
2805 if (!srb)
2806 return;
2807
2808 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2809 cb->setGraphicsPipeline(graphicsPipeline);
2810 cb->setShaderResources(srb);
2811
2812 if (needsSetViewport) {
2813 cb->setViewport(pipelineState.viewport);
2814 *needsSetViewport = false;
2815 }
2816
2817 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2818 int vertexBufferCount = 1;
2819 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2820 quint32 instances = 1;
2821 if (subsetRenderable->modelContext.model.instancing()) {
2822 instances = subsetRenderable->modelContext.model.instanceCount();
2823 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2824 vertexBufferCount = 2;
2825 }
2826
2827 if (indexBuffer) {
2828 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2829 cb->drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances, subsetRenderable->subset.lodOffset(subsetRenderable->subsetLevelOfDetail));
2830 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.lodCount(subsetRenderable->subsetLevelOfDetail), instances));
2831 } else {
2832 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
2833 cb->draw(subsetRenderable->subset.count, instances, subsetRenderable->subset.offset);
2834 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2835 }
2836 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2837 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2838 subsetRenderable->material.profilingId}));
2839 }
2840 }
2841}
2842
2844 QSSGPassKey passKey,
2845 const QSSGRhiGraphicsPipelineState &basePipelineState,
2846 QRhiRenderPassDescriptor *rpDesc,
2847 QSSGRenderGraphObject *overrideMaterial,
2848 QSSGLayerRenderData &inData,
2849 QSSGRenderableObjectList &inObjects,
2850 QSSGShaderFeatures featureSet,
2851 size_t index)
2852{
2853 if (!overrideMaterial)
2854 return;
2855
2856 QSSGRhiGraphicsPipelineState ps = basePipelineState;
2857 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
2858
2859 const bool isCustomMaterial = (overrideMaterial->type == QSSGRenderGraphObject::Type::CustomMaterial);
2860 const bool isDefaultMaterial = (overrideMaterial->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
2861 overrideMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
2862 overrideMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
2863
2864 if (!isCustomMaterial && !isDefaultMaterial) {
2865 qDebug() << "Override material must be a default or custom material.";
2866 return;
2867 }
2868
2869 for (const QSSGRenderableObjectHandle &handle : inObjects) {
2870 QSSGRenderableObject *obj = handle.obj;
2871
2872 if (obj->type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
2873 obj->type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
2874 continue;
2875
2876 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
2877 const void *modelNode = &subsetRenderable.modelContext.model;
2878 QSSGRhiDrawCallData *dcd = &rhiCtxD->drawCallData({ passKey, modelNode, overrideMaterial, 0 });
2879
2880 // Update the shader key to reflect the override material's properties
2881 // The subsetRenderable.shaderDescription was set during prepareModelsForRender for the original material,
2882 // but we need it to reflect the override material when paired with this model/subset.
2883 {
2884 QSSGRenderableObjectFlags renderableFlags = subsetRenderable.renderableFlags;
2885 float opacity = subsetRenderable.opacity;
2886
2887 // Determine lighting conditions from the subset's lights
2888 const bool hasAnyLights = !subsetRenderable.lights.isEmpty();
2889 const bool anyLightHasShadows = std::any_of(subsetRenderable.lights.begin(),
2890 subsetRenderable.lights.end(),
2891 [](const QSSGShaderLight &light) { return light.shadows; });
2892
2893 // We need a non-const reference to layerPrepResult flags
2894 // Make a copy since the prepare functions might modify it
2895 auto layerPrepFlags = inData.layerPrepResult.getFlags();
2896
2897 if (isCustomMaterial) {
2898 auto &material = static_cast<QSSGRenderCustomMaterial &>(*overrideMaterial);
2899 auto prepResult = inData.prepareCustomMaterialForRender(material, renderableFlags, opacity,
2900 false, hasAnyLights, anyLightHasShadows,
2901 layerPrepFlags);
2902 subsetRenderable.shaderDescription = prepResult.materialKey;
2903 } else {
2904 auto &material = static_cast<QSSGRenderDefaultMaterial &>(*overrideMaterial);
2905 auto prepResult = inData.prepareDefaultMaterialForRender(material, renderableFlags, opacity,
2906 hasAnyLights, anyLightHasShadows,
2907 layerPrepFlags);
2908 subsetRenderable.shaderDescription = prepResult.materialKey;
2909 }
2910 }
2911
2912 QSSGRhiShaderPipelinePtr shaderPipeline;
2913
2914 if (isCustomMaterial) {
2915 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(*overrideMaterial);
2916 ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
2917
2918 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2919 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable,
2920 inData.getDefaultMaterialPropertyTable(), featureSet);
2921 if (shaderPipeline) {
2922 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2923 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2924 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
2925 subsetRenderable, inData.renderedCameras, nullptr, nullptr);
2926 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2927 }
2928 } else {
2929 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(*overrideMaterial);
2930 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
2931
2932 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
2933 if (shaderPipeline) {
2934 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
2935 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2936 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable,
2937 inData.renderedCameras, nullptr, nullptr);
2938 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2939 }
2940 }
2941
2942 if (!shaderPipeline)
2943 continue;
2944
2945 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
2946 ia = subsetRenderable.subset.rhi.ia;
2947
2948 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
2949 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
2950 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
2951
2952 QSSGRhiShaderResourceBindingList bindings;
2953 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
2954
2955 // Texture maps from the renderable
2956 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
2957 while (renderableImage) {
2958 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
2959 const int samplerHint = int(renderableImage->m_mapType);
2960 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
2961 if (samplerBinding >= 0) {
2962 QRhiTexture *texture = renderableImage->m_texture.m_texture;
2963 if (texture) {
2964 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
2965 QSSGRhiSamplerDescription samplerDesc = {
2966 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
2967 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
2968 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
2969 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
2970 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
2971 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
2972 };
2973 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
2974 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
2975 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
2976 }
2977 }
2978 renderableImage = renderableImage->m_nextImage;
2979 }
2980
2981 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
2982
2983 // Dummy normal texture
2984 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
2985 if (normalTextureBinding >= 0) {
2986 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
2987 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
2988 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
2989 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
2990 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
2991 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
2992 }
2993
2994 // Skinning
2995 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
2996 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
2997 if (binding >= 0) {
2998 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2999 QRhiSampler::Nearest,
3000 QRhiSampler::None,
3001 QRhiSampler::ClampToEdge,
3002 QRhiSampler::ClampToEdge,
3003 QRhiSampler::Repeat
3004 });
3005 bindings.addTexture(binding,
3006 QRhiShaderResourceBinding::VertexStage,
3007 boneTexture,
3008 boneSampler);
3009 }
3010 }
3011
3012 // Morphing
3013 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
3014 if (targetsTexture) {
3015 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
3016 if (binding >= 0) {
3017 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3018 QRhiSampler::Nearest,
3019 QRhiSampler::None,
3020 QRhiSampler::ClampToEdge,
3021 QRhiSampler::ClampToEdge,
3022 QRhiSampler::ClampToEdge
3023 });
3024 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
3025 }
3026 }
3027
3028 // Lighting
3029 if (shaderPipeline->isLightingEnabled()) {
3030 bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3031 shaderPipeline->ub0LightDataOffset(),
3032 sizeof(QSSGShaderLightsUniformData));
3033 bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3034 shaderPipeline->ub0DirectionalLightDataOffset(),
3035 sizeof(QSSGShaderDirectionalLightsUniformData));
3036
3037 // Shadow map atlas
3038 auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
3039 if (shadowMapAtlas) {
3040 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
3041 if (binding >= 0) {
3042 QRhiTexture *texture = shadowMapAtlas;
3043 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
3044 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3045 Q_ASSERT(texture && sampler);
3046 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3047 }
3048 }
3049
3050 // Light probe
3051 if (shaderPipeline->lightProbeTexture()) {
3052 int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
3053 if (binding >= 0) {
3054 auto tiling = shaderPipeline->lightProbeTiling();
3055 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
3056 QSSGRhiHelpers::toRhi(tiling.first), QSSGRhiHelpers::toRhi(tiling.second), QRhiSampler::Repeat });
3057 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage,
3058 shaderPipeline->lightProbeTexture(), sampler);
3059 }
3060 }
3061 }
3062
3063 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
3064
3065 QSSG_ASSERT(srb, continue);
3066 QSSG_ASSERT(std::size(subsetRenderable.rhiRenderData.userPassData) > index, continue);
3067
3068 auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
3069 rhiPassData.pipeline = rhiCtxD->pipeline(ps, rpDesc, srb);
3070 rhiPassData.srb = srb;
3071 }
3072}
3073
3075 QSSGPassKey passKey,
3076 const QSSGRhiGraphicsPipelineState &basePipelineState,
3077 QRhiRenderPassDescriptor *rpDesc,
3078 const QSSGLayerRenderData &inData,
3079 QSSGRenderableObjectList &inObjects,
3080 QSSGShaderFeatures featureSet,
3081 size_t index)
3082{
3083 QSSGRhiGraphicsPipelineState ps = basePipelineState;
3084 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3085
3086 for (const QSSGRenderableObjectHandle &handle : inObjects) {
3087 QSSGRenderableObject *obj = handle.obj;
3088 QSSGRhiShaderPipelinePtr shaderPipeline;
3089
3090 QSSGRhiDrawCallData *dcd = nullptr;
3091 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
3092 obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3093 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3094 const void *modelNode = &subsetRenderable.modelContext.model;
3095 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
3096 }
3097
3098 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
3099 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3100 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
3101 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
3102
3103 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet);
3104 if (shaderPipeline) {
3105 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
3106 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3107 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable,
3108 inData.renderedCameras, nullptr, nullptr);
3109 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3110 }
3111 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3112 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3113 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
3114 ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
3115
3116 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
3117 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable,
3118 inData.getDefaultMaterialPropertyTable(), featureSet);
3119 if (shaderPipeline) {
3120 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
3121 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3122 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
3123 subsetRenderable, inData.renderedCameras, nullptr, nullptr);
3124 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3125 }
3126 }
3127
3128 if (!shaderPipeline)
3129 continue;
3130
3131 // The rest is common for both default and custom materials
3132 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
3133 obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3134 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
3135 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
3136 ia = subsetRenderable.subset.rhi.ia;
3137
3138 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
3139 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
3140 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
3141
3142 QSSGRhiShaderResourceBindingList bindings;
3143 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
3144
3145 // Texture maps
3146 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
3147 while (renderableImage) {
3148 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
3149 const int samplerHint = int(renderableImage->m_mapType);
3150 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
3151 if (samplerBinding >= 0) {
3152 QRhiTexture *texture = renderableImage->m_texture.m_texture;
3153 if (samplerBinding >= 0 && texture) {
3154 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
3155 QSSGRhiSamplerDescription samplerDesc = {
3156 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
3157 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
3158 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
3159 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
3160 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
3161 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
3162 };
3163 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
3164 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
3165 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
3166 }
3167 }
3168 renderableImage = renderableImage->m_nextImage;
3169 }
3170
3171 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
3172
3173 // Dummy normal texture
3174 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
3175 if (normalTextureBinding >= 0) {
3176 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
3177 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
3178 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3179 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
3180 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3181 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
3182 }
3183
3184 // Skinning
3185 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
3186 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
3187 if (binding >= 0) {
3188 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3189 QRhiSampler::Nearest,
3190 QRhiSampler::None,
3191 QRhiSampler::ClampToEdge,
3192 QRhiSampler::ClampToEdge,
3193 QRhiSampler::Repeat
3194 });
3195 bindings.addTexture(binding,
3196 QRhiShaderResourceBinding::VertexStage,
3197 boneTexture,
3198 boneSampler);
3199 }
3200 }
3201
3202 // Morphing
3203 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
3204 if (targetsTexture) {
3205 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
3206 if (binding >= 0) {
3207 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3208 QRhiSampler::Nearest,
3209 QRhiSampler::None,
3210 QRhiSampler::ClampToEdge,
3211 QRhiSampler::ClampToEdge,
3212 QRhiSampler::ClampToEdge
3213 });
3214 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
3215 }
3216 }
3217
3218 // Lighting
3219 if (shaderPipeline->isLightingEnabled()) {
3220 bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3221 shaderPipeline->ub0LightDataOffset(),
3222 sizeof(QSSGShaderLightsUniformData));
3223 bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3224 shaderPipeline->ub0DirectionalLightDataOffset(),
3225 sizeof(QSSGShaderDirectionalLightsUniformData));
3226
3227 // Shadow map atlas
3228 auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
3229 if (shadowMapAtlas) {
3230 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
3231 if (binding >= 0) {
3232 QRhiTexture *texture = shadowMapAtlas;
3233 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
3234 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3235 Q_ASSERT(texture && sampler);
3236 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3237 }
3238 }
3239
3240 // Shadow map blue noise
3241 if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
3242 if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
3243 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
3244 if (binding >= 0) {
3245 QRhiTexture *texture = shadowMapBlueNoise;
3246 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
3247 QRhiSampler::Linear,
3248 QRhiSampler::None,
3249 QRhiSampler::Repeat,
3250 QRhiSampler::Repeat,
3251 QRhiSampler::Repeat });
3252 Q_ASSERT(texture && sampler);
3253 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3254 }
3255 } else {
3256 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3257 QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
3258 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
3259 QRhiSampler::Linear,
3260 QRhiSampler::None,
3261 QRhiSampler::Repeat,
3262 QRhiSampler::Repeat,
3263 QRhiSampler::Repeat });
3264 Q_ASSERT(texture && sampler);
3265 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3266 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3267 }
3268 }
3269
3270 // Reflection probe or light probe
3271 if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
3272 int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
3273 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
3274 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3275 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
3276 if (reflectionSampler >= 0 && reflectionTexture)
3277 bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
3278 } else if (shaderPipeline->lightProbeTexture()) {
3279 int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
3280 if (binding >= 0) {
3281 auto tiling = shaderPipeline->lightProbeTiling();
3282 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
3283 QSSGRhiHelpers::toRhi(tiling.first), QSSGRhiHelpers::toRhi(tiling.second), QRhiSampler::Repeat });
3284 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage,
3285 shaderPipeline->lightProbeTexture(), sampler);
3286 }
3287 }
3288
3289 // Screen Texture
3290 if (shaderPipeline->screenTexture()) {
3291 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
3292 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
3293 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
3294 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
3295 ? QRhiSampler::Linear : QRhiSampler::None;
3296 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
3297 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
3298 if (screenTextureBinding >= 0) {
3299 bindings.addTexture(screenTextureBinding,
3300 QRhiShaderResourceBinding::FragmentStage,
3301 shaderPipeline->screenTexture(), sampler);
3302 }
3303 if (screenTextureArrayBinding >= 0) {
3304 bindings.addTexture(screenTextureArrayBinding,
3305 QRhiShaderResourceBinding::FragmentStage,
3306 shaderPipeline->screenTexture(), sampler);
3307 }
3308 }
3309 }
3310
3311 if (shaderPipeline->lightmapTexture()) {
3312 int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
3313 if (binding >= 0) {
3314 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
3315 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3316 bindings.addTexture(binding,
3317 QRhiShaderResourceBinding::FragmentStage,
3318 shaderPipeline->lightmapTexture(), sampler);
3319 }
3320 }
3321 }
3322
3323 // Custom property textures
3324 {
3325 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3326 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3327
3328 int maxSamplerBinding = -1;
3329 QVector<QShaderDescription::InOutVariable> samplerVars =
3330 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
3331 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
3332 auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
3333 [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
3334 if (it == samplerVars.cend())
3335 samplerVars.append(var);
3336 }
3337
3338 for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
3339 maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
3340
3341 if (maxSamplerBinding >= 0) {
3342 int extraTexCount = shaderPipeline->extraTextureCount();
3343 for (int i = 0; i < extraTexCount; ++i) {
3344 QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
3345 const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
3346 if (samplerBinding >= 0) {
3347 rhiCtx->checkAndAdjustForNPoT(t.texture, &t.samplerDesc);
3348 QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
3349 bindings.addTexture(samplerBinding,
3350 QRhiShaderResourceBinding::FragmentStage,
3351 t.texture,
3352 sampler);
3353 }
3354 }
3355 }
3356 }
3357
3358 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
3359
3360 QSSG_ASSERT(srb, continue);
3361 QSSG_ASSERT(std::size(subsetRenderable.rhiRenderData.userPassData) > index, continue);
3362
3363 auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
3364 rhiPassData.pipeline = rhiCtxD->pipeline(ps, rpDesc, srb);
3365 rhiPassData.srb = srb;
3366 }
3367 }
3368}
3369
3370
3371void RenderHelpers::rhiPrepareAugmentedUserPass(QSSGRhiContext *rhiCtx,
3372 QSSGPassKey passKey,
3373 const QSSGRhiGraphicsPipelineState &basePipelineState,
3374 QRhiRenderPassDescriptor *rpDesc,
3375 const QSSGUserShaderAugmentation &shaderAugmentation,
3376 const QSSGLayerRenderData &inData,
3377 QSSGRenderableObjectList &inObjects,
3378 QSSGShaderFeatures featureSet,
3379 size_t index)
3380{
3381
3382 QSSGRhiGraphicsPipelineState ps = basePipelineState;
3383 for (const QSSGRenderableObjectHandle &handle : inObjects) {
3384 QSSGRenderableObject *obj = handle.obj;
3385 QSSGRhiShaderPipelinePtr shaderPipeline;
3386 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3387 const auto &bufferManager = inData.contextInterface()->bufferManager();
3388
3389 featureSet.set(QSSGShaderFeatures::Feature::UserRenderPass, true);
3390
3391 QSSGRhiDrawCallData *dcd = nullptr;
3392 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3393 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3394 const void *modelNode = &subsetRenderable.modelContext.model;
3395 dcd = &rhiCtxD->drawCallData({ passKey, modelNode, &subsetRenderable.material, 0 });
3396 }
3397
3398 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
3399 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3400 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
3401 ps.cullMode = QSSGRhiHelpers::toCullMode(material.cullMode);
3402
3403 shaderPipeline = shadersForDefaultMaterial(&ps, subsetRenderable, featureSet, shaderAugmentation);
3404 if (shaderPipeline) {
3405 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
3406 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3407 // Now do user augmentation specific uniform updates
3408 for (const auto &u : shaderAugmentation.propertyUniforms)
3409 shaderPipeline->setShaderResources(ubufData, *bufferManager, u.name, u.value, u.shaderDataType);
3410 updateUniformsForDefaultMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, subsetRenderable, inData.renderedCameras, nullptr, nullptr);
3411 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3412 }
3413 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3414 QSSGSubsetRenderable &subsetRenderable(*static_cast<QSSGSubsetRenderable *>(obj));
3415 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
3416 ps.cullMode = QSSGRhiHelpers::toCullMode(material.m_cullMode);
3417
3418 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
3419 // Don't apply a shader augmentation to an unshaded custom material (they should do their own augmentations with preprocessor conditionals)
3420 if (material.m_shadingMode == QSSGRenderCustomMaterial::ShadingMode::Unshaded)
3421 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet);
3422 else
3423 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(&ps, material, subsetRenderable, inData.getDefaultMaterialPropertyTable(), featureSet, shaderAugmentation);
3424
3425 if (shaderPipeline) {
3426 shaderPipeline->ensureCombinedUniformBuffer(&dcd->ubuf);
3427 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3428 // Apply user augmentation specific uniform updates
3429 for (const auto &u : shaderAugmentation.propertyUniforms)
3430 shaderPipeline->setShaderResources(ubufData, *bufferManager, u.name, u.value, u.shaderDataType);
3431 customMaterialSystem.updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, inData, ubufData, &ps, material,
3432 subsetRenderable, inData.renderedCameras, nullptr, nullptr);
3433 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3434 }
3435 }
3436
3437 if (!shaderPipeline) {
3438 // bail
3439 qDebug() << "Failed to prepare user augmented pass for object.";
3440 return;
3441 }
3442
3443 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
3444 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
3445 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
3446 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(ps);
3447 ia = subsetRenderable.subset.rhi.ia;
3448
3449 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
3450 int instanceBufferBinding = setupInstancing(&subsetRenderable, &ps, rhiCtx, cameraDatas[0].direction, cameraDatas[0].position);
3451 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
3452
3453 QSSGRhiShaderResourceBindingList bindings;
3454 bindings.addUniformBuffer(0, RENDERER_VISIBILITY_ALL, dcd->ubuf);
3455
3456 // Texture maps
3457 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
3458 while (renderableImage) {
3459 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
3460 const int samplerHint = int(renderableImage->m_mapType);
3461 int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
3462 if (samplerBinding >= 0) {
3463 QRhiTexture *texture = renderableImage->m_texture.m_texture;
3464 if (samplerBinding >= 0 && texture) {
3465 const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
3466 QSSGRhiSamplerDescription samplerDesc = {
3467 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
3468 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
3469 mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
3470 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
3471 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
3472 QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
3473 };
3474 rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
3475 QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
3476 bindings.addTexture(samplerBinding, RENDERER_VISIBILITY_ALL, texture, sampler);
3477 }
3478 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
3479 renderableImage = renderableImage->m_nextImage;
3480 }
3481
3482 addDepthTextureBindings(rhiCtx, shaderPipeline.get(), bindings);
3483
3484 // There is no normal texture at this stage, obviously.
3485 const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
3486 if (normalTextureBinding >= 0) {
3487 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
3488 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
3489 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3490 QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
3491 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3492 bindings.addTexture(normalTextureBinding, RENDERER_VISIBILITY_ALL, dummyTexture, sampler);
3493 }
3494
3495 // Shadow maps are not needed since lighting-related shading is mostly skipped in the normal texture's pass
3496
3497 // Skinning
3498 if (QRhiTexture *boneTexture = inData.getBonemapTexture(subsetRenderable.modelContext)) {
3499 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
3500 if (binding >= 0) {
3501 QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3502 QRhiSampler::Nearest,
3503 QRhiSampler::None,
3504 QRhiSampler::ClampToEdge,
3505 QRhiSampler::ClampToEdge,
3506 QRhiSampler::Repeat
3507 });
3508 bindings.addTexture(binding,
3509 QRhiShaderResourceBinding::VertexStage,
3510 boneTexture,
3511 boneSampler);
3512 }
3513 }
3514
3515 // Morphing
3516 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
3517 if (targetsTexture) {
3518 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
3519 if (binding >= 0) {
3520 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3521 QRhiSampler::Nearest,
3522 QRhiSampler::None,
3523 QRhiSampler::ClampToEdge,
3524 QRhiSampler::ClampToEdge,
3525 QRhiSampler::ClampToEdge
3526 });
3527 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
3528 }
3529 }
3530
3531 // Lighting
3532 if (shaderPipeline->isLightingEnabled()) {
3533 bindings.addUniformBuffer(1, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3534 shaderPipeline->ub0LightDataOffset(),
3535 sizeof(QSSGShaderLightsUniformData));
3536 bindings.addUniformBuffer(2, RENDERER_VISIBILITY_ALL, dcd->ubuf,
3537 shaderPipeline->ub0DirectionalLightDataOffset(),
3538 sizeof(QSSGShaderDirectionalLightsUniformData));
3539
3540 // Shadow map atlas
3541 auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
3542 if (shadowMapAtlas) {
3543 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
3544 if (binding >= 0) {
3545 QRhiTexture *texture = shadowMapAtlas;
3546 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
3547 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3548 Q_ASSERT(texture && sampler);
3549 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3550 }
3551 }
3552
3553 // Shadow map blue noise
3554 if (int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture"); binding >= 0) {
3555 if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
3556 int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
3557 if (binding >= 0) {
3558 QRhiTexture *texture = shadowMapBlueNoise;
3559 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
3560 QRhiSampler::Linear,
3561 QRhiSampler::None,
3562 QRhiSampler::Repeat,
3563 QRhiSampler::Repeat,
3564 QRhiSampler::Repeat });
3565 Q_ASSERT(texture && sampler);
3566 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3567 }
3568 } else {
3569 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3570 QRhiTexture *texture = rhiCtx->dummyTexture({}, resourceUpdates);
3571 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
3572 QRhiSampler::Linear,
3573 QRhiSampler::None,
3574 QRhiSampler::Repeat,
3575 QRhiSampler::Repeat,
3576 QRhiSampler::Repeat });
3577 Q_ASSERT(texture && sampler);
3578 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
3579 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3580 }
3581 }
3582
3583 // Prioritize reflection texture over Light Probe texture because
3584 // reflection texture also contains the irradiance and pre filtered
3585 // values for the light probe.
3586 if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
3587 int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
3588 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
3589 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3590 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
3591 if (reflectionSampler >= 0 && reflectionTexture)
3592 bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
3593 } else if (shaderPipeline->lightProbeTexture()) {
3594 int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
3595 if (binding >= 0) {
3596 auto tiling = shaderPipeline->lightProbeTiling();
3597 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear, // enables mipmapping
3598 QSSGRhiHelpers::toRhi(tiling.first), QSSGRhiHelpers::toRhi(tiling.second), QRhiSampler::Repeat });
3599 bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage,
3600 shaderPipeline->lightProbeTexture(), sampler);
3601 } // else ignore, not an error (since non-lighting passes wont need it)
3602 }
3603
3604 // Screen Texture
3605 if (shaderPipeline->screenTexture()) {
3606 const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
3607 const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
3608 if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
3609 // linear min/mag, mipmap filtering depends on the
3610 // texture, with SCREEN_TEXTURE there are no mipmaps, but
3611 // once SCREEN_MIP_TEXTURE is seen the texture (the same
3612 // one) has mipmaps generated.
3613 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
3614 ? QRhiSampler::Linear : QRhiSampler::None;
3615 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
3616 QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
3617 if (screenTextureBinding >= 0) {
3618 bindings.addTexture(screenTextureBinding,
3619 QRhiShaderResourceBinding::FragmentStage,
3620 shaderPipeline->screenTexture(), sampler);
3621 }
3622 if (screenTextureArrayBinding >= 0) {
3623 bindings.addTexture(screenTextureArrayBinding,
3624 QRhiShaderResourceBinding::FragmentStage,
3625 shaderPipeline->screenTexture(), sampler);
3626 }
3627 } // else ignore, not an error
3628 }
3629
3630 if (shaderPipeline->lightmapTexture()) {
3631 int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
3632 if (binding >= 0) {
3633 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
3634 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
3635 bindings.addTexture(binding,
3636 QRhiShaderResourceBinding::FragmentStage,
3637 shaderPipeline->lightmapTexture(), sampler);
3638 } // else ignore, not an error
3639 }
3640 }
3641
3642
3643 { // START
3644 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
3645 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
3646
3647 QVector<QShaderDescription::InOutVariable> samplerVars =
3648 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
3649 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
3650 auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
3651 [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
3652 if (it == samplerVars.cend())
3653 samplerVars.append(var);
3654 }
3655
3656 int maxSamplerBinding = -1;
3657 for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
3658 maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
3659
3660 // Will need to set unused image-samplers to something dummy
3661 // because the shader code contains all custom property textures,
3662 // and not providing a binding for all of them is invalid with some
3663 // graphics APIs (and will need a real texture because setting a
3664 // null handle or similar is not permitted with some of them so the
3665 // srb does not accept null QRhiTextures either; but first let's
3666 // figure out what bindings are unused in this frame)
3667 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
3668
3669 if (maxSamplerBinding >= 0) {
3670 // custom property textures
3671 int extraTexCount = shaderPipeline->extraTextureCount();
3672 for (int i = 0; i < extraTexCount; ++i) {
3673 QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
3674 const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
3675 if (samplerBinding >= 0) {
3676 samplerBindingsSpecified.setBit(samplerBinding);
3677 rhiCtx->checkAndAdjustForNPoT(t.texture, &t.samplerDesc);
3678 QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
3679 bindings.addTexture(samplerBinding,
3680 QRhiShaderResourceBinding::FragmentStage,
3681 t.texture,
3682 sampler);
3683 }
3684 }
3685 }
3686 } // END
3687
3688 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
3689
3690 QSSG_ASSERT(srb, return);
3691
3692 QSSG_ASSERT(std::size(subsetRenderable.rhiRenderData.userPassData) > index, return);
3693
3694 auto &rhiPassData = subsetRenderable.rhiRenderData.userPassData[index];
3695 rhiPassData.pipeline = rhiCtxD->pipeline(ps,
3696 rpDesc,
3697 srb);
3698 rhiPassData.srb = srb;
3699
3700 }
3701 }
3702}
3703
3705 const QSize &size,
3706 QSSGRhiRenderableTexture *renderableTex){
3707 QRhi *rhi = rhiCtx->rhi();
3708 bool needsBuild = false;
3709 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
3710 if (!renderableTex->texture) {
3711 renderableTex->texture = rhi->newTexture(QRhiTexture::RGBA16F, size, 1, flags);
3712 needsBuild = true;
3713 } else if (renderableTex->texture->pixelSize() != size) {
3714 renderableTex->texture->setPixelSize(size);
3715 needsBuild = true;
3716 }
3717
3718 if (!renderableTex->depthStencil && !renderableTex->depthTexture) {
3719 renderableTex->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
3720 needsBuild = true;
3721 } else {
3722 if (renderableTex->depthStencil && renderableTex->depthStencil->pixelSize() != size) {
3723 renderableTex->depthStencil->setPixelSize(size);
3724 needsBuild = true;
3725 }
3726 }
3727
3728 if (needsBuild) {
3729 if (!renderableTex->texture->create()) {
3730 qWarning("Failed to build motionvector texture (size %dx%d)", size.width(), size.height());
3731 renderableTex->reset();
3732 return false;
3733 }
3734 if (renderableTex->depthStencil && !renderableTex->depthStencil->create()) {
3735 qWarning("Failed to build depth-stencil buffer for motionvector texture (size %dx%d)",
3736 size.width(), size.height());
3737 renderableTex->reset();
3738 return false;
3739 }
3740 renderableTex->resetRenderTarget();
3741 QRhiTextureRenderTargetDescription desc;
3742 QRhiColorAttachment colorAttachment(renderableTex->texture);
3743 desc.setColorAttachments({ colorAttachment });
3744 if (renderableTex->depthStencil)
3745 desc.setDepthStencilBuffer(renderableTex->depthStencil);
3746 renderableTex->rt = rhi->newTextureRenderTarget(desc);
3747 renderableTex->rt->setName(QByteArrayLiteral("Motionvector texture"));
3748 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
3749 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
3750 if (!renderableTex->rt->create()) {
3751 qWarning("Failed to build render target for motionvector texture");
3752 renderableTex->reset();
3753 return false;
3754 }
3755 }
3756
3757 return true;
3758}
3759
3760void RenderHelpers::rhiPrepareMotionVectorRenderable(QSSGRhiContext *rhiCtx,
3761 QSSGPassKey passKey,
3762 const QSSGLayerRenderData &inData,
3763 const QMatrix4x4 &viewProjection,
3764 QSSGRenderableObject &inObject,
3765 QRhiRenderPassDescriptor *renderPassDescriptor,
3766 QSSGRhiGraphicsPipelineState *ps,
3767 QSSGRenderMotionVectorMap &motionVectorMapManager)
3768{
3769 if (inObject.type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
3770 inObject.type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
3771 return;
3772
3773 QSSGSubsetRenderable &subsetRenderable = static_cast<QSSGSubsetRenderable &>(inObject);
3774 auto &modelNode = subsetRenderable.modelContext.model;
3775
3776 bool skin = modelNode.usesBoneTexture();
3777 bool instance = modelNode.instanceCount() > 0;
3778 bool morph = modelNode.morphTargets.size() > 0;
3779
3780 QSSGRhiShaderPipelinePtr shaderPipeline = inData.contextInterface()->shaderCache()->
3781 getBuiltInRhiShaders().getRhiMotionVectorShader(skin, instance, morph);
3782
3783 if (!shaderPipeline)
3784 return;
3785
3786 QSSGRhiShaderResourceBindingList bindings;
3787
3788 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3789 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ passKey, &modelNode, nullptr, 0 });
3790
3791
3792 QSSGRenderTextureData *boneTextureData = nullptr;
3793 if (modelNode.skin)
3794 boneTextureData = modelNode.skin;
3795 else if (modelNode.skeleton)
3796 boneTextureData = &modelNode.skeleton->boneTexData;
3797
3798 QMatrix4x4 modelViewProjection = subsetRenderable.modelContext.modelViewProjections[0];
3799 QMatrix4x4 instanceLocal;
3800 QMatrix4x4 instanceGlobal;
3801
3802 if (instance) {
3803 instanceLocal = inData.getInstanceTransforms(modelNode).local;
3804 instanceGlobal = inData.getInstanceTransforms(modelNode).global;
3805 if (!skin)
3806 modelViewProjection = viewProjection;
3807 }
3808 auto motionData = motionVectorMapManager.trackMotionData(&modelNode,
3809 modelViewProjection,
3810 instanceLocal,
3811 instanceGlobal,
3812 boneTextureData,
3813 modelNode.instanceTable,
3814 modelNode.morphWeights);
3815 float velocityAmount = subsetRenderable.modelContext.model.motionVectorScale;
3816 int morphTargetCount = modelNode.morphWeights.count();
3817
3818 // mat4 mvp; // 64
3819 // mat4 prevMVP; // 64
3820 // mat4 instanceLocal; // 64
3821 // mat4 instanceGlobal; // 64
3822 // mat4 prevInstanceLocal; // 64
3823 // mat4 prevInstanceGlobal; // 64
3824 // vec4 currentAndLastJitter; // 16
3825 // float velocityAmount; // 4
3826 // int morphTargetCount; // 4
3827 // vec2 padding; // 8
3828 const int ubufSize = 416;
3829
3830 if (!dcd.ubuf) {
3831 dcd.ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic,
3832 QRhiBuffer::UniformBuffer,
3833 ubufSize);
3834 dcd.ubuf->create();
3835 }
3836
3837 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
3838 int ubufOffset = 0;
3839 memcpy(ubufData, modelViewProjection.constData(), 64);
3840 ubufOffset += 64;
3841 memcpy(ubufData + ubufOffset, motionData.prevModelViewProjection.constData(), 64);
3842 ubufOffset += 64;
3843 memcpy(ubufData + ubufOffset, instanceLocal.constData(), 64);
3844 ubufOffset += 64;
3845 memcpy(ubufData + ubufOffset, instanceGlobal.constData(), 64);
3846 ubufOffset += 64;
3847 memcpy(ubufData + ubufOffset, motionData.prevInstanceLocal.constData(), 64);
3848 ubufOffset += 64;
3849 memcpy(ubufData + ubufOffset, motionData.prevInstanceGlobal.constData(), 64);
3850 ubufOffset += 64;
3851 memcpy(ubufData + ubufOffset, &inData.layer.currentAndLastJitter, 16);
3852 ubufOffset += 16;
3853 memcpy(ubufData + ubufOffset, &velocityAmount, 4);
3854 ubufOffset += 4;
3855 memcpy(ubufData + ubufOffset, &morphTargetCount, 4);
3856 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
3857
3858 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage |
3859 QRhiShaderResourceBinding::FragmentStage, dcd.ubuf);
3860
3861 QRhiSampler *nearestSampler = nullptr;
3862
3863 if (skin || instance) {
3864 nearestSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3865 QRhiSampler::Nearest,
3866 QRhiSampler::None,
3867 QRhiSampler::ClampToEdge,
3868 QRhiSampler::ClampToEdge,
3869 QRhiSampler::Repeat
3870 });
3871 }
3872
3873 if (skin) {
3874 int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
3875 if (binding >= 0) {
3876
3877 bindings.addTexture(binding,
3878 QRhiShaderResourceBinding::VertexStage,
3879 inData.getBonemapTexture(subsetRenderable.modelContext),
3880 nearestSampler);
3881 binding = shaderPipeline->bindingForTexture("lastBoneTexture");
3882 if (binding >= 0) {
3883 bindings.addTexture(binding,
3884 QRhiShaderResourceBinding::VertexStage,
3885 motionData.prevBoneTexture.m_texture,
3886 nearestSampler);
3887 }
3888 }
3889 }
3890
3891 if (instance) {
3892 int binding = shaderPipeline->bindingForTexture("lastInstanceTexture");
3893 if (binding >= 0) {
3894 bindings.addTexture(binding,
3895 QRhiShaderResourceBinding::VertexStage,
3896 motionData.prevInstanceTexture.m_texture,
3897 nearestSampler);
3898 }
3899 }
3900
3901 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
3902 if (targetsTexture) {
3903 int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
3904 if (binding >= 0) {
3905 QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
3906 QRhiSampler::Nearest,
3907 QRhiSampler::None,
3908 QRhiSampler::ClampToEdge,
3909 QRhiSampler::ClampToEdge,
3910 QRhiSampler::ClampToEdge
3911 });
3912 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, subsetRenderable.subset.rhi.targetsTexture, targetsSampler);
3913
3914 if ((binding = shaderPipeline->bindingForTexture("morphWeightTexture")) >= 0)
3915 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, motionData.currentMorphWeightTexture.m_texture, targetsSampler);
3916
3917 if ((binding = shaderPipeline->bindingForTexture("lastMorphWeightTexture")) >= 0)
3918 bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, motionData.prevMorphWeightTexture.m_texture, targetsSampler);
3919 }
3920 }
3921
3922 auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
3923 ia = subsetRenderable.subset.rhi.ia;
3924 const QSSGRenderCameraDataList &cameraDatas(*inData.renderedCameraData);
3925 QVector3D cameraDirection = cameraDatas[0].direction;
3926 QVector3D cameraPosition = cameraDatas[0].position;
3927 int instanceBufferBinding = setupInstancing(&subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
3928 QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
3929 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
3930
3931 QRhiShaderResourceBindings *&srb = dcd.srb;
3932 bool srbChanged = false;
3933 if (!srb || bindings != dcd.bindings) {
3934 srb = rhiCtxD->srb(bindings);
3935 rhiCtxD->releaseCachedSrb(dcd.bindings);
3936 dcd.bindings = bindings;
3937 srbChanged = true;
3938 }
3939
3940 subsetRenderable.rhiRenderData.motionVectorPass.srb = srb;
3941
3942 const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
3943 if (dcd.pipeline
3944 && !srbChanged
3945 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
3946 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
3947 && dcd.ps == *ps)
3948 {
3949 subsetRenderable.rhiRenderData.motionVectorPass.pipeline = dcd.pipeline;
3950 } else {
3951
3952 subsetRenderable.rhiRenderData.motionVectorPass.pipeline = rhiCtxD->pipeline(pipelineKey,
3953 renderPassDescriptor,
3954 srb);
3955 dcd.pipeline = subsetRenderable.rhiRenderData.motionVectorPass.pipeline;
3956 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
3957 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
3958 dcd.ps = *ps;
3959 }
3960}
3961
3962void RenderHelpers::rhiRenderMotionVector(QSSGRhiContext *rhiCtx,
3963 const QSSGRhiGraphicsPipelineState &state,
3964 const QSSGRenderableObjectList *motionVectorPassObjects,
3965 int bucketsCount)
3966{
3967 bool needsSetViewport = true;
3968
3969 auto renderObjectList = [&](const QSSGRenderableObjectList &objects) {
3970 for (const auto &handle : objects) {
3971 QSSGRenderableObject &object = *handle.obj;
3972
3973 // Only handle mesh subsets
3974 if (object.type != QSSGRenderableObject::Type::DefaultMaterialMeshSubset &&
3975 object.type != QSSGRenderableObject::Type::CustomMaterialMeshSubset)
3976 continue;
3977
3978 QSSGSubsetRenderable &subsetRenderable = static_cast<QSSGSubsetRenderable &>(object);
3979
3980 QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.motionVectorPass.pipeline;
3981 QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.motionVectorPass.srb;
3982
3983 if (!ps || !srb)
3984 return;
3985
3986 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
3987 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
3988
3989 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
3990 // QRhi optimizes out unnecessary binding of the same pipline
3991 cb->setGraphicsPipeline(ps);
3992 cb->setShaderResources(srb);
3993
3994 if (needsSetViewport) {
3995 cb->setViewport(state.viewport);
3996 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
3997 cb->setScissor(state.scissor);
3998 needsSetViewport = false;
3999 }
4000
4001 QRhiCommandBuffer::VertexInput vertexBuffers[2];
4002 int vertexBufferCount = 1;
4003 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
4004 quint32 instances = 1;
4005 if ( subsetRenderable.modelContext.model.instancing()) {
4006 instances = subsetRenderable.modelContext.model.instanceCount();
4007 // If the instance count is 0, bail out before trying to do any
4008 // draw calls. Making an instanced draw call with a count of 0 is invalid
4009 // for Metal and likely other API's as well.
4010 // It is possible that the particle systems may produce 0 instances here
4011 if (instances == 0)
4012 return;
4013 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
4014 vertexBufferCount = 2;
4015 }
4016 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
4017 if (state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef))
4018 cb->setStencilRef(state.stencilRef);
4019 if (indexBuffer) {
4020 cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, subsetRenderable.subset.rhi.indexBuffer->indexFormat());
4021 cb->drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances, subsetRenderable.subset.lodOffset(subsetRenderable.subsetLevelOfDetail));
4022 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
4023 } else {
4024 cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
4025 cb->draw(subsetRenderable.subset.count, instances, subsetRenderable.subset.offset);
4026 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
4027 }
4028 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
4029 QVector<int>({subsetRenderable.modelContext.model.profilingId,
4030 subsetRenderable.material.profilingId}));
4031 }
4032 };
4033
4034 for (int i = 0; i < bucketsCount; ++i)
4035 renderObjectList(motionVectorPassObjects[i]);
4036}
4037
4038QT_END_NAMESPACE
QSSGRenderableObjectFlags renderableFlags
void rhiRenderAoTexture(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderer &renderer, QSSGRhiShaderPipeline &shaderPipeline, QSSGRhiGraphicsPipelineState &ps, const QSSGAmbientOcclusionSettings &ao, const QSSGRhiRenderableTexture &rhiAoTexture, const QSSGRhiRenderableTexture &rhiDepthTexture, const QSSGRenderCamera &camera)
bool rhiPrepareNormalPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, QSSGLayerRenderData &inData, const QSSGRenderableObjectList &sortedOpaqueObjects)
bool rhiPrepareMotionVectorTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex)
void rhiRenderNormalPass(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &ps, const QSSGRenderableObjectList &sortedOpaqueObjects, bool *needsSetViewport)
Q_QUICK3DRUNTIMERENDER_EXPORT void rhiRenderRenderable(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &state, QSSGRenderableObject &object, bool *needsSetViewport, QSSGRenderTextureCubeFace cubeFace=QSSGRenderTextureCubeFaceNone, qsizetype userPassIndex=-1)
void rhiPrepareOverrideMaterialUserPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, QSSGRenderGraphObject *overrideMaterial, QSSGLayerRenderData &inData, QSSGRenderableObjectList &inObjects, QSSGShaderFeatures featureSet, size_t index)
void rhiPrepareAugmentedUserPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, const QSSGUserShaderAugmentation &shaderAugmentation, const QSSGLayerRenderData &inData, QSSGRenderableObjectList &inObjects, QSSGShaderFeatures featureSet, size_t index)
bool rhiPrepareDepthPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, QSSGLayerRenderData &inData, const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects, int samples, int viewCount)
std::pair< QSSGBounds3, QSSGBounds3 > calculateSortedObjectBounds(const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects)
bool rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx, const QSize &size, bool wantsMips, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount)
void addAccumulatorImageBindings(QSSGRhiShaderPipeline *shaderPipeline, QSSGRhiShaderResourceBindingList &bindings)
bool rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount, int samples=1)
void rhiRenderMotionVector(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &state, const QSSGRenderableObjectList *motionVectorPassObjects, int bucketsCount)
void rhiPrepareOriginalMaterialUserPass(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGRhiGraphicsPipelineState &basePipelineState, QRhiRenderPassDescriptor *rpDesc, const QSSGLayerRenderData &inData, QSSGRenderableObjectList &inObjects, QSSGShaderFeatures featureSet, size_t index)
void rhiRenderReflectionMap(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGLayerRenderData &inData, QSSGRhiGraphicsPipelineState *ps, QSSGRenderReflectionMap &reflectionMapManager, const QVector< QSSGRenderReflectionProbe * > &reflectionProbes, const QSSGRenderableObjectList &reflectionPassObjects, QSSGRenderer &renderer)
bool rhiPrepareAoTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex, quint8 viewCount)
void rhiRenderDepthPass(QSSGRhiContext *rhiCtx, const QSSGRhiGraphicsPipelineState &ps, const QSSGRenderableObjectList &sortedOpaqueObjects, const QSSGRenderableObjectList &sortedTransparentObjects, bool *needsSetViewport)
void addDebugFrustum(const QSSGBoxPoints &frustumPoints, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
void addDebugBox(const QSSGBoxPoints &boxUnsorted, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
void addDirectionalLightDebugBox(const QSSGBoxPoints &box, QSSGDebugDrawSystem *debugDrawSystem)
#define M_PI_2
Definition qmath.h:204
#define M_PI
Definition qmath.h:200
QVarLengthArray< QSSGShaderLight, 16 > QSSGShaderLightList
static void setupCubeShadowCameras(const QSSGLayerRenderData &inData, const QSSGRenderLight *inLight, float shadowMapFar, QSSGRenderCamera inCameras[6])
static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps, QSSGParticlesRenderable &particleRenderable, const QSSGShaderFeatures &featureSet)
static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps, QSSGSubsetRenderable &subsetRenderable, const QSSGShaderFeatures &featureSet, const QSSGUserShaderAugmentation &shaderAugmentation={})
static std::unique_ptr< QSSGRenderCamera > computeShadowCameraFromFrustum(const QMatrix4x4 &lightMatrix, const QMatrix4x4 &lightMatrixInverted, const QVector3D &lightPivot, const QVector3D &lightForward, const QVector3D &lightUp, const float shadowMapResolution, const float pcfRadius, const QSSGBoxPoints &frustumPoints, float frustumStartT, float frustumEndT, float frustumRadius, bool lockShadowmapTexels, const QSSGBounds3 &castingBox, const QSSGBounds3 &receivingBox, QSSGDebugDrawSystem *debugDrawSystem, const bool drawCascades, const bool drawSceneCascadeIntersection)
static QRhiViewport calculateAtlasViewport(QSize atlasPixelSize, const QSSGShadowMapEntry::AtlasEntry &atlasEntry, bool yIsUp)
static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, const QSSGLayerRenderData &inData, QSSGReflectionMapEntry *pEntry, QSSGRhiGraphicsPipelineState *ps, const QSSGRenderableObjectList &sortedOpaqueObjects, QSSGRenderCamera &inCamera, QSSGRenderer &renderer, QSSGRenderTextureCubeFace cubeFace)
static QSSGBoxPoints sliceFrustum(const QSSGBoxPoints &frustumPoints, float t0, float t1)
static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline, QSSGRhiContext *rhiCtx, const QSSGLayerRenderData &inData, char *ubufData, QSSGRhiGraphicsPipelineState *ps, QSSGSubsetRenderable &subsetRenderable, const QSSGRenderCameraList &cameras, const QVector2D *depthAdjust, const QMatrix4x4 *alteredModelViewProjection)
static void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer, QSSGReflectionMapEntry *entry=nullptr, QSSGRenderTextureCubeFace cubeFace=QSSGRenderTextureCubeFaceNone, uint tonemapMode=0)
static QT_BEGIN_NAMESPACE constexpr float QSSG_PI
static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx, const QSSGLayerRenderData &inData, QSSGPassKey passKey, QSSGShadowMapEntry *pEntry, QSSGRhiGraphicsPipelineState *ps, const QVector2D *depthAdjust, const QSSGRenderableObjectList &sortedOpaqueObjects, QSSGRenderCamera &inCamera, bool orthographic, QSSGRenderTextureCubeFace cubeFace, quint32 cascadeIndex)
static void addDepthTextureBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRhiShaderResourceBindingList &bindings)
static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRenderableImage *renderableImage, QSSGRhiShaderResourceBindingList &bindings, bool isCustomMaterialMeshSubset=false)
static QSSGBoxPoints computeFrustumBounds(const QMatrix4x4 &projection)
static void addNormalTextureBindings(QSSGRhiContext *rhiCtx, QSSGRhiShaderPipeline *shaderPipeline, QSSGRhiShaderResourceBindingList &bindings)
static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL
static constexpr float QSSG_HALFPI
static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
static void setupCubeReflectionCameras(const QSSGLayerRenderData &inData, const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
static QVarLengthArray< std::unique_ptr< QSSGRenderCamera >, 4 > setupCascadingCamerasForShadowMap(const QSSGLayerRenderData &data, const QSSGRenderCamera &inCamera, const QSSGRenderLight *inLight, const int shadowMapResolution, const float pcfRadius, const float clipNear, const float clipFar, const QSSGBounds3 &castingObjectsBox, const QSSGBounds3 &receivingObjectsBox, bool lockShadowmapTexels, QSSGDebugDrawSystem *debugDrawSystem, bool drawCascades, bool drawSceneCascadeIntersection)