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