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