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