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