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