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