1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include <QtQuick3DRuntimeRender/private/qssgrenderitem2d_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
9#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
10#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
11#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
14#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
15#include <QtQuick3DRuntimeRender/private/qssgrendershadercodegenerator_p.h>
16#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
20#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
21#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
22
23#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
24#include <QtQuick3DUtils/private/qssgdataref_p.h>
25#include <QtQuick3DUtils/private/qssgutils_p.h>
26#include <QtQuick3DUtils/private/qssgassert_p.h>
27#include <qtquick3d_tracepoints_p.h>
28
29#include <QtCore/QMutexLocker>
30#include <QtCore/QBitArray>
31
32#include <cstdlib>
33#include <algorithm>
34#include <limits>
35
36QT_BEGIN_NAMESPACE
37
38struct QSSGRenderableImage;
39struct QSSGSubsetRenderable;
40
41static constexpr float QSSG_PI = float(M_PI);
42static constexpr float QSSG_HALFPI = float(M_PI_2);
43
44static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL =
45 QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
46
47static QSSGRhiShaderPipelinePtr shadersForDefaultMaterial(QSSGRhiGraphicsPipelineState *ps,
48 QSSGSubsetRenderable &subsetRenderable,
49 const QSSGShaderFeatures &featureSet)
50{
51 const auto &renderer(subsetRenderable.renderer);
52 const auto &shaderPipeline = renderer->getShaderPipelineForDefaultMaterial(inRenderable&: subsetRenderable, inFeatureSet: featureSet);
53 if (shaderPipeline)
54 ps->shaderPipeline = shaderPipeline.get();
55 return shaderPipeline;
56}
57
58static QSSGRhiShaderPipelinePtr shadersForParticleMaterial(QSSGRhiGraphicsPipelineState *ps,
59 QSSGParticlesRenderable &particleRenderable)
60{
61 const auto &renderer(particleRenderable.renderer);
62 auto featureLevel = particleRenderable.particles.m_featureLevel;
63 const auto &shaderPipeline = renderer->getRhiParticleShader(featureLevel);
64 if (shaderPipeline)
65 ps->shaderPipeline = shaderPipeline.get();
66 return shaderPipeline;
67}
68
69static void updateUniformsForDefaultMaterial(QSSGRhiShaderPipeline &shaderPipeline,
70 QSSGRhiContext *rhiCtx,
71 const QSSGLayerRenderData &inData,
72 char *ubufData,
73 QSSGRhiGraphicsPipelineState *ps,
74 QSSGSubsetRenderable &subsetRenderable,
75 const QSSGRenderCamera &camera,
76 const QVector2D *depthAdjust,
77 const QMatrix4x4 *alteredModelViewProjection)
78{
79 const auto &renderer(subsetRenderable.renderer);
80 const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
81 const QMatrix4x4 &mvp(alteredModelViewProjection ? *alteredModelViewProjection
82 : subsetRenderable.modelContext.modelViewProjection);
83
84 const auto &modelNode = subsetRenderable.modelContext.model;
85 QRhiTexture *lightmapTexture = inData.getLightmapTexture(modelContext: subsetRenderable.modelContext);
86
87 const QMatrix4x4 &localInstanceTransform(modelNode.localInstanceTransform);
88 const QMatrix4x4 &globalInstanceTransform(modelNode.globalInstanceTransform);
89 const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : subsetRenderable.globalTransform);
90
91 QSSGMaterialShaderGenerator::setRhiMaterialProperties(*renderer->contextInterface(),
92 shaders&: shaderPipeline,
93 ubufData,
94 inPipelineState: ps,
95 inMaterial: subsetRenderable.material,
96 inKey: subsetRenderable.shaderDescription,
97 inProperties&: renderer->defaultMaterialShaderKeyProperties(),
98 inCamera: camera,
99 inModelViewProjection: mvp,
100 inNormalMatrix: subsetRenderable.modelContext.normalMatrix,
101 inGlobalTransform: modelMatrix,
102 clipSpaceCorrMatrix,
103 localInstanceTransform,
104 globalInstanceTransform,
105 inMorphWeights: toDataView(type: modelNode.morphWeights),
106 inFirstImage: subsetRenderable.firstImage,
107 inOpacity: subsetRenderable.opacity,
108 inRenderProperties: renderer->getLayerGlobalRenderProperties(),
109 inLights: subsetRenderable.lights,
110 reflectionProbe: subsetRenderable.reflectionProbe,
111 receivesShadows: subsetRenderable.renderableFlags.receivesShadows(),
112 receivesReflections: subsetRenderable.renderableFlags.receivesReflections(),
113 shadowDepthAdjust: depthAdjust,
114 lightmapTexture);
115}
116
117void QSSGRenderer::releaseCachedResources()
118{
119 delete m_rhiQuadRenderer;
120 m_rhiQuadRenderer = nullptr;
121 delete m_rhiCubeRenderer;
122 m_rhiCubeRenderer = nullptr;
123}
124
125QSSGRenderer::QSSGRenderer() = default;
126
127QSSGRenderer::~QSSGRenderer()
128{
129 m_contextInterface = nullptr;
130 releaseCachedResources();
131}
132
133void QSSGRenderer::setRenderContextInterface(QSSGRenderContextInterface *ctx)
134{
135 m_contextInterface = ctx;
136}
137
138bool QSSGRenderer::prepareLayerForRender(QSSGRenderLayer &inLayer)
139{
140 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
141 Q_ASSERT(theRenderData);
142 beginLayerRender(inLayer&: *theRenderData);
143 theRenderData->resetForFrame();
144 theRenderData->prepareForRender();
145 endLayerRender();
146 return theRenderData->layerPrepResult->flags.wasDirty();
147}
148
149// Phase 1: prepare. Called when the renderpass is not yet started on the command buffer.
150void QSSGRenderer::rhiPrepare(QSSGRenderLayer &inLayer)
151{
152 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
153 QSSG_ASSERT(theRenderData && theRenderData->camera, return);
154
155 const auto layerPrepResult = theRenderData->layerPrepResult;
156 if (layerPrepResult->isLayerVisible()) {
157 ///
158 QSSGRhiContext *rhiCtx = contextInterface()->rhiContext().get();
159 QSSG_ASSERT(rhiCtx->isValid() && rhiCtx->rhi()->isRecordingFrame(), return);
160 theRenderData->maybeBakeLightmap();
161 beginLayerRender(inLayer&: *theRenderData);
162 // Process active passes. "PreMain" passes are individual passes
163 // that does can and should be done in the rhi prepare phase.
164 // It is assumed that passes are sorted in the list with regards to
165 // execution order.
166 const auto &activePasses = theRenderData->activePasses;
167 for (const auto &pass : activePasses) {
168 pass->renderPrep(renderer&: *this, data&: *theRenderData);
169 if (pass->passType() == QSSGRenderPass::Type::Standalone)
170 pass->renderPass(renderer&: *this);
171 }
172
173 endLayerRender();
174 }
175}
176
177// Phase 2: render. Called within an active renderpass on the command buffer.
178void QSSGRenderer::rhiRender(QSSGRenderLayer &inLayer)
179{
180 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
181 QSSG_ASSERT(theRenderData && theRenderData->camera, return);
182 if (theRenderData->layerPrepResult->isLayerVisible()) {
183 QSSG_ASSERT(theRenderData->camera, return);
184 beginLayerRender(inLayer&: *theRenderData);
185 const auto &activePasses = theRenderData->activePasses;
186 for (const auto &pass : activePasses) {
187 if (pass->passType() == QSSGRenderPass::Type::Main || pass->passType() == QSSGRenderPass::Type::Extension)
188 pass->renderPass(renderer&: *this);
189 }
190 endLayerRender();
191 }
192}
193
194template<typename Container>
195static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
196{
197 const auto &rhi = rci.rhiContext();
198 if (!rhi->isValid())
199 return;
200
201 const auto &bufferManager = rci.bufferManager();
202
203 for (const auto &resource : resources) {
204 if (resource->type == QSSGRenderGraphObject::Type::Geometry) {
205 auto geometry = static_cast<QSSGRenderGeometry*>(resource);
206 bufferManager->releaseGeometry(geometry);
207 } else if (resource->type == QSSGRenderGraphObject::Type::Model) {
208 auto model = static_cast<QSSGRenderModel*>(resource);
209 rhi->cleanupDrawCallData(model);
210 } else if (resource->type == QSSGRenderGraphObject::Type::TextureData) {
211 auto textureData = static_cast<QSSGRenderTextureData *>(resource);
212 bufferManager->releaseTextureData(data: textureData);
213 }
214
215 // ### There might be more types that need to be supported
216
217 delete resource;
218 }
219}
220
221void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources)
222{
223 cleanupResourcesImpl(rci: *m_contextInterface, resources);
224 resources.clear();
225}
226
227void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources)
228{
229 cleanupResourcesImpl(rci: *m_contextInterface, resources);
230 resources.clear();
231}
232
233QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer)
234{
235 if (layer.renderData == nullptr)
236 layer.renderData = new QSSGLayerRenderData(layer, *this);
237
238 return layer.renderData;
239}
240
241void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material)
242{
243 m_materialClearDirty.insert(value: material);
244}
245
246static QByteArray logPrefix() { return QByteArrayLiteral("mesh default material pipeline-- "); }
247
248
249QSSGRhiShaderPipelinePtr QSSGRenderer::generateRhiShaderPipelineImpl(QSSGSubsetRenderable &renderable,
250 QSSGShaderLibraryManager &shaderLibraryManager,
251 QSSGShaderCache &shaderCache,
252 QSSGProgramGenerator &shaderProgramGenerator,
253 QSSGShaderDefaultMaterialKeyProperties &shaderKeyProperties,
254 const QSSGShaderFeatures &featureSet,
255 QByteArray &shaderString)
256{
257 shaderString = logPrefix();
258 QSSGShaderDefaultMaterialKey theKey(renderable.shaderDescription);
259
260 // This is not a cheap operation. This function assumes that it will not be
261 // hit for every material for every model in every frame (except of course
262 // for materials that got changed). In practice this is ensured by the
263 // cheaper-to-lookup cache in getShaderPipelineForDefaultMaterial().
264 theKey.toString(ioString&: shaderString, inProperties: shaderKeyProperties);
265
266 // Check the in-memory, per-QSSGShaderCache (and so per-QQuickWindow)
267 // runtime cache. That may get cleared upon an explicit call to
268 // QQuickWindow::releaseResources(), but will otherwise store all
269 // encountered shader pipelines in any View3D in the window.
270 if (const auto &maybePipeline = shaderCache.tryGetRhiShaderPipeline(inKey: shaderString, inFeatures: featureSet))
271 return maybePipeline;
272
273 // Check if there's a pre-built (offline generated) shader for available.
274 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: featureSet));
275 const QQsbCollection::EntryMap &pregenEntries = shaderLibraryManager.m_preGeneratedShaderEntries;
276 if (!pregenEntries.isEmpty()) {
277 const auto foundIt = pregenEntries.constFind(value: QQsbCollection::Entry(qsbcKey));
278 if (foundIt != pregenEntries.cend())
279 return shaderCache.newPipelineFromPregenerated(inKey: shaderString, inFeatures: featureSet, entry: *foundIt, obj: renderable.material);
280 }
281
282 // Try the persistent (disk-based) cache then.
283 if (const auto &maybePipeline = shaderCache.tryNewPipelineFromPersistentCache(qsbcKey, inKey: shaderString, inFeatures: featureSet))
284 return maybePipeline;
285
286 // Otherwise, build new shader code and run the resulting shaders through
287 // the shader conditioning pipeline.
288 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(renderable.getMaterial());
289 QSSGMaterialVertexPipeline vertexPipeline(shaderProgramGenerator,
290 shaderKeyProperties,
291 material.adapter);
292
293 return QSSGMaterialShaderGenerator::generateMaterialRhiShader(inShaderKeyPrefix: logPrefix(),
294 vertexGenerator&: vertexPipeline,
295 key: renderable.shaderDescription,
296 inProperties&: shaderKeyProperties,
297 inFeatureSet: featureSet,
298 inMaterial: renderable.material,
299 inLights: renderable.lights,
300 inFirstImage: renderable.firstImage,
301 shaderLibraryManager,
302 theCache&: shaderCache);
303}
304
305QSSGRhiShaderPipelinePtr QSSGRenderer::generateRhiShaderPipeline(QSSGSubsetRenderable &inRenderable,
306 const QSSGShaderFeatures &inFeatureSet)
307{
308 const auto &theCache = m_contextInterface->shaderCache();
309 const auto &shaderProgramGenerator = contextInterface()->shaderProgramGenerator();
310 const auto &shaderLibraryManager = contextInterface()->shaderLibraryManager();
311 return generateRhiShaderPipelineImpl(renderable&: inRenderable, shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *theCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties&: m_defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: m_generatedShaderString);
312}
313
314void QSSGRenderer::beginFrame(QSSGRenderLayer *layer)
315{
316 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(layer));
317}
318
319void QSSGRenderer::endFrame(QSSGRenderLayer *layer)
320{
321 // We need to do this endFrame(), as the material nodes might not exist after this!
322 for (auto *matObj : std::as_const(t&: m_materialClearDirty)) {
323 if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) {
324 static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty();
325 } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
326 matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
327 matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
328 static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty();
329 }
330 }
331 m_materialClearDirty.clear();
332
333 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(layer));
334}
335
336QSSGRenderer::PickResultList QSSGRenderer::syncPickAll(const QSSGRenderLayer &layer,
337 QSSGBufferManager &bufferManager,
338 const QSSGRenderRay &ray)
339{
340 PickResultList pickResults;
341 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
342 getLayerHitObjectList(layer, bufferManager, ray, inPickEverything: m_globalPickingEnabled, outIntersectionResult&: pickResults);
343 // Things are rendered in a particular order and we need to respect that ordering.
344 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
345 return lhs.m_distanceSq < rhs.m_distanceSq;
346 });
347 return pickResults;
348}
349
350QSSGRenderPickResult QSSGRenderer::syncPick(const QSSGRenderLayer &layer,
351 QSSGBufferManager &bufferManager,
352 const QSSGRenderRay &ray,
353 QSSGRenderNode *target)
354{
355 static const auto processResults = [](PickResultList &pickResults) {
356 if (pickResults.empty())
357 return QSSGPickResultProcessResult();
358 // Things are rendered in a particular order and we need to respect that ordering.
359 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
360 return lhs.m_distanceSq < rhs.m_distanceSq;
361 });
362 return QSSGPickResultProcessResult{ pickResults.at(idx: 0), true };
363 };
364
365 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
366 PickResultList pickResults;
367 if (target) {
368 // Pick against only one target
369 intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults);
370 return processResults(pickResults);
371 } else {
372 getLayerHitObjectList(layer, bufferManager, ray, inPickEverything: m_globalPickingEnabled, outIntersectionResult&: pickResults);
373 QSSGPickResultProcessResult retval = processResults(pickResults);
374 if (retval.m_wasPickConsumed)
375 return retval;
376 }
377
378 return QSSGPickResultProcessResult();
379}
380
381bool QSSGRenderer::isGlobalPickingEnabled() const
382{
383 return m_globalPickingEnabled;
384}
385
386void QSSGRenderer::setGlobalPickingEnabled(bool isEnabled)
387{
388 m_globalPickingEnabled = isEnabled;
389}
390
391QSSGRhiQuadRenderer *QSSGRenderer::rhiQuadRenderer()
392{
393 if (!m_contextInterface->rhiContext()->isValid())
394 return nullptr;
395
396 if (!m_rhiQuadRenderer)
397 m_rhiQuadRenderer = new QSSGRhiQuadRenderer;
398
399 return m_rhiQuadRenderer;
400}
401
402QSSGRhiCubeRenderer *QSSGRenderer::rhiCubeRenderer()
403{
404 if (!m_contextInterface->rhiContext()->isValid())
405 return nullptr;
406
407 if (!m_rhiCubeRenderer)
408 m_rhiCubeRenderer = new QSSGRhiCubeRenderer;
409
410 return m_rhiCubeRenderer;
411
412}
413
414void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
415{
416 m_currentLayer = &inLayer;
417}
418void QSSGRenderer::endLayerRender()
419{
420 m_currentLayer = nullptr;
421}
422
423using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
424static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
425{
426 if (QSSGRenderGraphObject::isRenderable(type: node.type))
427 renderables.push_back(t: &node);
428
429 for (const auto &child : node.children)
430 dfs(node: child, renderables);
431}
432
433void QSSGRenderer::getLayerHitObjectList(const QSSGRenderLayer &layer,
434 QSSGBufferManager &bufferManager,
435 const QSSGRenderRay &ray,
436 bool inPickEverything,
437 PickResultList &outIntersectionResult)
438{
439 RenderableList renderables;
440 for (const auto &childNode : layer.children)
441 dfs(node: childNode, renderables);
442
443 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
444 const auto &pickableObject = renderables.at(idx);
445 if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable))
446 intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult);
447 }
448}
449
450void QSSGRenderer::intersectRayWithSubsetRenderable(QSSGBufferManager &bufferManager,
451 const QSSGRenderRay &inRay,
452 const QSSGRenderNode &node,
453 QSSGRenderer::PickResultList &outIntersectionResultList)
454{
455 // Item2D's requires special handling
456 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
457 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
458 intersectRayWithItem2D(inRay, item2D, outIntersectionResultList);
459 return;
460 }
461
462 if (node.type != QSSGRenderGraphObject::Type::Model)
463 return;
464
465 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
466
467 // We have to have a guard here, as the meshes are usually loaded on the render thread,
468 // and we assume all meshes are loaded before picking and none are removed, which
469 // is usually true, except for custom geometry which can be updated at any time. So this
470 // guard should really only be locked whenever a custom geometry buffer is being updated
471 // on the render thread. Still naughty though because this can block the render thread.
472 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
473 auto mesh = bufferManager.getMeshForPicking(model);
474 if (!mesh)
475 return;
476
477 const auto &subMeshes = mesh->subsets;
478 QSSGBounds3 modelBounds;
479 for (const auto &subMesh : subMeshes)
480 modelBounds.include(b: subMesh.bounds);
481
482 if (modelBounds.isEmpty())
483 return;
484
485 const bool instancing = model.instancing(); // && instancePickingEnabled
486 int instanceCount = instancing ? model.instanceTable->count() : 1;
487
488 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
489
490 QMatrix4x4 modelTransform;
491 if (instancing) {
492 modelTransform = model.globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * model.localInstanceTransform;
493 } else {
494 modelTransform = model.globalTransform;
495 }
496 auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay);
497
498 auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds);
499
500 // If we don't intersect with the model at all, then there's no need to go furher down!
501 if (!hit.intersects())
502 continue;
503
504 // Check each submesh to find the closest intersection point
505 float minRayLength = std::numeric_limits<float>::max();
506 QSSGRenderRay::IntersectionResult intersectionResult;
507 QVector<QSSGRenderRay::IntersectionResult> results;
508
509 int subset = 0;
510 int resultSubset = 0;
511 for (const auto &subMesh : subMeshes) {
512 QSSGRenderRay::IntersectionResult result;
513 if (subMesh.bvhRoot) {
514 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData);
515 if (hit.intersects()) {
516 results.clear();
517 inRay.intersectWithBVH(data: rayData, bvh: subMesh.bvhRoot, mesh, intersections&: results);
518 float subMeshMinRayLength = std::numeric_limits<float>::max();
519 for (const auto &subMeshResult : std::as_const(t&: results)) {
520 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
521 result = subMeshResult;
522 subMeshMinRayLength = result.rayLengthSquared;
523 }
524 }
525 }
526 } else {
527 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds);
528 if (hit.intersects())
529 result = QSSGRenderRay::createIntersectionResult(data: rayData, hit);
530 }
531 if (result.intersects && result.rayLengthSquared < minRayLength) {
532 intersectionResult = result;
533 minRayLength = intersectionResult.rayLengthSquared;
534 resultSubset = subset;
535 }
536 subset++;
537 }
538
539 if (intersectionResult.intersects)
540 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model,
541 .m_distanceSq: intersectionResult.rayLengthSquared,
542 .m_localUVCoords: intersectionResult.relXY,
543 .m_scenePosition: intersectionResult.scenePosition,
544 .m_localPosition: intersectionResult.localPosition,
545 .m_faceNormal: intersectionResult.faceNormal,
546 .m_subset: resultSubset,
547 .m_instanceIndex: instanceIndex
548 });
549 }
550}
551
552void QSSGRenderer::intersectRayWithItem2D(const QSSGRenderRay &inRay, const QSSGRenderItem2D &item2D, QSSGRenderer::PickResultList &outIntersectionResultList)
553{
554 // Get the plane (and normal) that the item 2D is on
555 const QVector3D p0 = item2D.getGlobalPos();
556 const QVector3D normal = -item2D.getDirection();
557
558 const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal);
559 float intersectionTime = 0;
560 if (d > 1e-6f) {
561 const QVector3D p0l0 = p0 - inRay.origin;
562 intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d;
563 if (intersectionTime >= 0) {
564 // Intersection
565 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
566 const QMatrix4x4 inverseGlobalTransform = item2D.globalTransform.inverted();
567 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint);
568 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
569 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D,
570 .m_distanceSq: intersectionTime * intersectionTime,
571 .m_localUVCoords: qmlCoordinate,
572 .m_scenePosition: intersectionPoint,
573 .m_localPosition: localIntersectionPoint,
574 .m_faceNormal: normal });
575 }
576 }
577}
578
579QSSGRhiShaderPipelinePtr QSSGRenderer::getShaderPipelineForDefaultMaterial(QSSGSubsetRenderable &inRenderable,
580 const QSSGShaderFeatures &inFeatureSet)
581{
582 if (Q_UNLIKELY(m_currentLayer == nullptr)) {
583 Q_ASSERT(false);
584 return nullptr;
585 }
586
587 // This function is the main entry point for retrieving the shaders for a
588 // default material, and is called for every material for every model in
589 // every frame. Therefore, like with custom materials, employ a first level
590 // cache (a simple hash table), with a key that's quick to
591 // generate/hash/compare. Even though there are other levels of caching in
592 // the components that get invoked from here, those may not be suitable
593 // performance wise. So bail out right here as soon as possible.
594
595 QElapsedTimer timer;
596 timer.start();
597
598 QSSGRhiShaderPipelinePtr shaderPipeline;
599
600 // This just references inFeatureSet and inRenderable.shaderDescription -
601 // cheap to construct and is good enough for the find()
602 QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(),
603 inFeatureSet,
604 inRenderable.shaderDescription);
605 auto it = m_shaderMap.find(key: skey);
606 if (it == m_shaderMap.end()) {
607 Q_TRACE_SCOPE(QSSG_generateShader);
608 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
609 shaderPipeline = generateRhiShaderPipeline(inRenderable, inFeatureSet);
610 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
611 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
612 skey.detach();
613 // insert it no matter what, no point in trying over and over again
614 m_shaderMap.insert(key: skey, value: shaderPipeline);
615 } else {
616 shaderPipeline = it.value();
617 }
618
619 if (shaderPipeline != nullptr) {
620 if (m_currentLayer && m_currentLayer->camera) {
621 if (!m_currentLayer->cameraData.has_value())
622 [[maybe_unused]] const auto cd = m_currentLayer->getCachedCameraData();
623 }
624 }
625
626 m_contextInterface->rhiContext()->stats().registerMaterialShaderGenerationTime(ms: timer.elapsed());
627
628 return shaderPipeline;
629}
630
631QSSGLayerGlobalRenderProperties QSSGRenderer::getLayerGlobalRenderProperties()
632{
633 QSSGLayerRenderData &theData = *m_currentLayer;
634 const QSSGRenderLayer &theLayer = theData.layer;
635 if (!theData.cameraData.has_value() && theData.camera) // NOTE: Ensure we have a valid value!
636 [[maybe_unused]] const auto cd = theData.getCachedCameraData();
637
638 bool isYUpInFramebuffer = true;
639 bool isYUpInNDC = true;
640 bool isClipDepthZeroToOne = true;
641 if (m_contextInterface->rhiContext()->isValid()) {
642 QRhi *rhi = m_contextInterface->rhiContext()->rhi();
643 isYUpInFramebuffer = rhi->isYUpInFramebuffer();
644 isYUpInNDC = rhi->isYUpInNDC();
645 isClipDepthZeroToOne = rhi->isClipDepthZeroToOne();
646 }
647
648 const QSSGRhiRenderableTexture *depthTexture = theData.getRenderResult(id: QSSGFrameData::RenderResult::DepthTexture);
649 const QSSGRhiRenderableTexture *ssaoTexture = theData.getRenderResult(id: QSSGFrameData::RenderResult::AoTexture);
650 const QSSGRhiRenderableTexture *screenTexture = theData.getRenderResult(id: QSSGFrameData::RenderResult::ScreenTexture);
651
652 return QSSGLayerGlobalRenderProperties{ .layer: theLayer,
653 .camera: *theData.camera,
654 .cameraData: theData.cameraData.value(), // ensured/checked further up in this function
655 .shadowMapManager: theData.getShadowMapManager().get(),
656 .rhiDepthTexture: depthTexture->texture,
657 .rhiSsaoTexture: ssaoTexture->texture,
658 .rhiScreenTexture: screenTexture->texture,
659 .lightProbe: theLayer.lightProbe,
660 .probeHorizon: theLayer.probeHorizon,
661 .probeExposure: theLayer.probeExposure,
662 .probeOrientation: theLayer.probeOrientation,
663 .isYUpInFramebuffer: isYUpInFramebuffer,
664 .isYUpInNDC: isYUpInNDC,
665 .isClipDepthZeroToOne: isClipDepthZeroToOne};
666}
667
668void QSSGRenderer::setTonemapFeatures(QSSGShaderFeatures &features, QSSGRenderLayer::TonemapMode tonemapMode)
669{
670 features.set(feature: QSSGShaderFeatures::Feature::LinearTonemapping,
671 val: tonemapMode == QSSGRenderLayer::TonemapMode::Linear);
672 features.set(feature: QSSGShaderFeatures::Feature::AcesTonemapping,
673 val: tonemapMode == QSSGRenderLayer::TonemapMode::Aces);
674 features.set(feature: QSSGShaderFeatures::Feature::HejlDawsonTonemapping,
675 val: tonemapMode == QSSGRenderLayer::TonemapMode::HejlDawson);
676 features.set(feature: QSSGShaderFeatures::Feature::FilmicTonemapping,
677 val: tonemapMode == QSSGRenderLayer::TonemapMode::Filmic);
678}
679
680/////// RHI RENDER HELPERS
681
682std::pair<QSSGBoxPoints, QSSGBoxPoints> RenderHelpers::calculateSortedObjectBounds(const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
683 const QVector<QSSGRenderableObjectHandle> &sortedTransparentObjects)
684{
685 QSSGBounds3 boundsCasting;
686 QSSGBounds3 boundsReceiving;
687 for (const auto handles : { &sortedOpaqueObjects, &sortedTransparentObjects }) {
688 // Since we may have nodes that are not a child of the camera parent we go through all
689 // the opaque objects and include them in the bounds. Failing to do this can result in
690 // too small bounds.
691 for (const QSSGRenderableObjectHandle &handle : *handles) {
692 const QSSGRenderableObject &obj = *handle.obj;
693 // We skip objects not casting or receiving shadows since they don't influence or need to be covered by the shadow map
694 if (obj.renderableFlags.castsShadows())
695 boundsCasting.include(b: obj.globalBounds);
696 if (obj.renderableFlags.receivesShadows())
697 boundsReceiving.include(b: obj.globalBounds);
698 }
699 }
700 return { boundsCasting.toQSSGBoxPointsNoEmptyCheck(), boundsReceiving.toQSSGBoxPointsNoEmptyCheck() };
701}
702
703static QVector3D calcCenter(const QSSGBoxPoints &vertices)
704{
705 QVector3D center = vertices[0];
706 for (int i = 1; i < 8; ++i) {
707 center += vertices[i];
708 }
709 return center * 0.125f;
710}
711
712static QSSGBounds3 calculateShadowCameraBoundingBox(const QSSGBoxPoints &points, const QVector3D &forward, const QVector3D &up, const QVector3D &right)
713{
714 QSSGBounds3 bounds;
715 for (int i = 0; i < 8; ++i) {
716 const float distanceZ = QVector3D::dotProduct(v1: points[i], v2: forward);
717 const float distanceY = QVector3D::dotProduct(v1: points[i], v2: up);
718 const float distanceX = QVector3D::dotProduct(v1: points[i], v2: right);
719 bounds.include(v: QVector3D(distanceX, distanceY, distanceZ));
720 }
721 return bounds;
722}
723
724static QSSGBoxPoints computeFrustumBounds(const QSSGRenderCamera &inCamera)
725{
726 QMatrix4x4 viewProjection;
727 inCamera.calculateViewProjectionMatrix(outMatrix&: viewProjection);
728
729 bool invertible = false;
730 QMatrix4x4 inv = viewProjection.inverted(invertible: &invertible);
731 Q_ASSERT(invertible);
732
733 return { inv.map(point: QVector3D(-1, -1, -1)), inv.map(point: QVector3D(+1, -1, -1)), inv.map(point: QVector3D(+1, +1, -1)),
734 inv.map(point: QVector3D(-1, +1, -1)), inv.map(point: QVector3D(-1, -1, +1)), inv.map(point: QVector3D(+1, -1, +1)),
735 inv.map(point: QVector3D(+1, +1, +1)), inv.map(point: QVector3D(-1, +1, +1)) };
736}
737
738static void setupCubeReflectionCameras(const QSSGRenderReflectionProbe *inProbe, QSSGRenderCamera inCameras[6])
739{
740 Q_ASSERT(inProbe != nullptr);
741
742 // setup light matrix
743 quint32 mapRes = 1 << inProbe->reflectionMapRes;
744 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
745 static const QQuaternion rotOfs[6] {
746 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: -QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
747 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
748 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: QSSG_HALFPI), yaw: 0.f, roll: 0.f),
749 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: -QSSG_HALFPI), yaw: 0.f, roll: 0.f),
750 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_PI), roll: qRadiansToDegrees(radians: -QSSG_PI)),
751 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: 0.f, roll: qRadiansToDegrees(radians: QSSG_PI)),
752 };
753
754 const QVector3D inProbePos = inProbe->getGlobalPos();
755 const QVector3D inProbePivot = inProbe->pivot;
756
757 for (int i = 0; i < 6; ++i) {
758 inCameras[i].parent = nullptr;
759 inCameras[i].clipNear = 1.0f;
760 inCameras[i].clipFar = qMax<float>(a: 2.0f, b: 10000.0f);
761 inCameras[i].fov = qDegreesToRadians(degrees: 90.f);
762
763 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(position: inProbePos, scale: QSSGRenderNode::initScale, pivot: inProbePivot, rotation: rotOfs[i]);
764 inCameras[i].calculateGlobalVariables(inViewport: theViewport);
765 }
766}
767
768static void setupCameraForShadowMap(const QSSGRenderCamera &inCamera,
769 const QSSGRenderLight *inLight,
770 QSSGRenderCamera &theCamera,
771 const QSSGBoxPoints &castingBox,
772 const QSSGBoxPoints &receivingBox)
773{
774 using namespace RenderHelpers;
775
776 // setup light matrix
777 quint32 mapRes = 1 << inLight->m_shadowMapRes;
778 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
779 theCamera.clipNear = 1.0f;
780 theCamera.clipFar = inLight->m_shadowMapFar;
781 // Setup camera projection
782 QVector3D inLightPos = inLight->getGlobalPos();
783 QVector3D inLightDir = inLight->getDirection();
784 QVector3D inLightPivot = inLight->pivot;
785
786 inLightPos -= inLightDir * inCamera.clipNear;
787 theCamera.fov = qDegreesToRadians(degrees: 90.f);
788 theCamera.parent = nullptr;
789
790 if (inLight->type == QSSGRenderLight::Type::DirectionalLight) {
791 Q_ASSERT(theCamera.type == QSSGRenderCamera::Type::OrthographicCamera);
792 const QVector3D forward = inLightDir.normalized();
793 const QVector3D right = qFuzzyCompare(p1: qAbs(t: forward.y()), p2: 1.0f)
794 ? QVector3D::crossProduct(v1: forward, v2: QVector3D(1, 0, 0)).normalized()
795 : QVector3D::crossProduct(v1: forward, v2: QVector3D(0, 1, 0)).normalized();
796 const QVector3D up = QVector3D::crossProduct(v1: right, v2: forward).normalized();
797
798 // Calculate bounding box of the scene camera frustum
799 const QSSGBoxPoints frustumPoints = computeFrustumBounds(inCamera);
800 const QSSGBounds3 frustumBounds = calculateShadowCameraBoundingBox(points: frustumPoints, forward, up, right);
801 const QSSGBounds3 sceneCastingBounds = calculateShadowCameraBoundingBox(points: castingBox, forward, up, right);
802 const QSSGBounds3 boundsReceiving = calculateShadowCameraBoundingBox(points: receivingBox, forward, up, right);
803
804 QVector3D finalDims;
805 QVector3D center;
806 // Select smallest bounds from either scene or camera frustum
807 if (sceneCastingBounds.isFinite() && boundsReceiving.isFinite() // handle empty scene
808 && sceneCastingBounds.extents().lengthSquared() < frustumBounds.extents().lengthSquared()) {
809 center = calcCenter(vertices: castingBox);
810 const QVector3D centerReceiving = calcCenter(vertices: receivingBox);
811
812 // Since we need to make sure every rendered geometry can get a valid depth value from the shadow map
813 // we need to expand the scene bounding box along its z-axis so that it covers also receiving objects in the scene.
814 //
815 // We take the z dimensions of the casting bounds and expand it to include the z dimensions of the receiving objects.
816 // We call the casting bounding box 'a' and the receiving bounding box 'b'.
817
818 // length of boxes
819 const float aLength = sceneCastingBounds.dimensions().z();
820 const float bLength = boundsReceiving.dimensions().z();
821
822 // center position of boxes
823 const float aCenter = QVector3D::dotProduct(v1: center, v2: forward);
824 const float bCenter = QVector3D::dotProduct(v1: centerReceiving, v2: forward);
825
826 // distance between boxes
827 const float d = bCenter - aCenter;
828
829 // start/end positions
830 const float a0 = 0.f;
831 const float a1 = aLength;
832 const float b0 = (aLength * 0.5f) + d - (bLength * 0.5f);
833 const float b1 = (aLength * 0.5f) + d + (bLength * 0.5f);
834
835 // goal start/end position
836 const float ap0 = qMin(a: a0, b: b0);
837 const float ap1 = qMax(a: a1, b: b1);
838 // goal length
839 const float length = ap1 - ap0;
840 // goal center postion
841 const float c = (ap1 + ap0) * 0.5f;
842
843 // how much to move in forward direction
844 const float move = c - aLength * 0.5f;
845
846 center = center + forward * move;
847 finalDims = sceneCastingBounds.dimensions();
848 finalDims.setZ(length);
849 } else {
850 center = calcCenter(vertices: frustumPoints);
851 finalDims = frustumBounds.dimensions();
852 }
853
854 // Expand dimensions a little bit to avoid precision problems
855 finalDims *= 1.05f;
856
857 // Apply bounding box parameters to shadow map camera projection matrix
858 // so that the whole scene is fit inside the shadow map
859 theViewport.setHeight(finalDims.y());
860 theViewport.setWidth(finalDims.x());
861 theCamera.clipNear = -0.5f * finalDims.z();
862 theCamera.clipFar = 0.5f * finalDims.z();
863 theCamera.localTransform = QSSGRenderNode::calculateTransformMatrix(position: center, scale: QSSGRenderNode::initScale, pivot: inLightPivot, rotation: QQuaternion::fromDirection(direction: forward, up));
864 } else if (inLight->type == QSSGRenderLight::Type::PointLight) {
865 theCamera.lookAt(inCameraPos: inLightPos, inUpDir: QVector3D(0, 1.0, 0), inTargetPos: QVector3D(0, 0, 0), pivot: inLightPivot);
866 }
867
868 theCamera.calculateGlobalVariables(inViewport: theViewport);
869}
870
871static void addOpaqueDepthPrePassBindings(QSSGRhiContext *rhiCtx,
872 QSSGRhiShaderPipeline *shaderPipeline,
873 QSSGRenderableImage *renderableImage,
874 QSSGRhiShaderResourceBindingList &bindings,
875 bool isCustomMaterialMeshSubset = false)
876{
877 static const auto imageAffectsAlpha = [](QSSGRenderableImage::Type mapType) {
878 return mapType == QSSGRenderableImage::Type::BaseColor ||
879 mapType == QSSGRenderableImage::Type::Diffuse ||
880 mapType == QSSGRenderableImage::Type::Translucency ||
881 mapType == QSSGRenderableImage::Type::Opacity;
882 };
883
884 while (renderableImage) {
885 const auto mapType = renderableImage->m_mapType;
886 if (imageAffectsAlpha(mapType)) {
887 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: mapType);
888 const int samplerHint = int(mapType);
889 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
890 if (samplerBinding >= 0) {
891 QRhiTexture *texture = renderableImage->m_texture.m_texture;
892 if (samplerBinding >= 0 && texture) {
893 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
894 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: toRhi(op: renderableImage->m_imageNode.m_minFilterType),
895 .magFilter: toRhi(op: renderableImage->m_imageNode.m_magFilterType),
896 .mipmap: mipmapped ? toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
897 .hTiling: toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
898 .vTiling: toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
899 .zTiling: QRhiSampler::Repeat
900 });
901 bindings.addTexture(binding: samplerBinding, stage: RENDERER_VISIBILITY_ALL, tex: texture, sampler);
902 }
903 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
904 }
905 renderableImage = renderableImage->m_nextImage;
906 }
907 // For custom Materials we can't know which maps affect alpha, so map all
908 if (isCustomMaterialMeshSubset) {
909 QVector<QShaderDescription::InOutVariable> samplerVars =
910 shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
911 for (const QShaderDescription::InOutVariable &var : shaderPipeline->vertexStage()->shader().description().combinedImageSamplers()) {
912 auto it = std::find_if(first: samplerVars.cbegin(), last: samplerVars.cend(),
913 pred: [&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
914 if (it == samplerVars.cend())
915 samplerVars.append(t: var);
916 }
917
918 int maxSamplerBinding = -1;
919 for (const QShaderDescription::InOutVariable &var : samplerVars)
920 maxSamplerBinding = qMax(a: maxSamplerBinding, b: var.binding);
921
922 // Will need to set unused image-samplers to something dummy
923 // because the shader code contains all custom property textures,
924 // and not providing a binding for all of them is invalid with some
925 // graphics APIs (and will need a real texture because setting a
926 // null handle or similar is not permitted with some of them so the
927 // srb does not accept null QRhiTextures either; but first let's
928 // figure out what bindings are unused in this frame)
929 QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
930
931 if (maxSamplerBinding >= 0) {
932 // custom property textures
933 int customTexCount = shaderPipeline->extraTextureCount();
934 for (int i = 0; i < customTexCount; ++i) {
935 const QSSGRhiTexture &t(shaderPipeline->extraTextureAt(index: i));
936 const int samplerBinding = shaderPipeline->bindingForTexture(name: t.name);
937 if (samplerBinding >= 0) {
938 samplerBindingsSpecified.setBit(samplerBinding);
939 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: t.samplerDesc);
940 bindings.addTexture(binding: samplerBinding,
941 stage: RENDERER_VISIBILITY_ALL,
942 tex: t.texture,
943 sampler);
944 }
945 }
946 }
947
948 // use a dummy texture for the unused samplers in the shader
949 QRhiSampler *dummySampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
950 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
951 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
952 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
953 QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(flags: QRhiTexture::CubeMap, rub: resourceUpdates);
954 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
955
956 for (const QShaderDescription::InOutVariable &var : samplerVars) {
957 if (!samplerBindingsSpecified.testBit(i: var.binding)) {
958 QRhiTexture *t = var.type == QShaderDescription::SamplerCube ? dummyCubeTexture : dummyTexture;
959 bindings.addTexture(binding: var.binding, stage: RENDERER_VISIBILITY_ALL, tex: t, sampler: dummySampler);
960 }
961 }
962 }
963}
964
965static void setupCubeShadowCameras(const QSSGRenderLight *inLight, QSSGRenderCamera inCameras[6])
966{
967 Q_ASSERT(inLight != nullptr);
968 Q_ASSERT(inLight->type != QSSGRenderLight::Type::DirectionalLight);
969
970 // setup light matrix
971 quint32 mapRes = 1 << inLight->m_shadowMapRes;
972 QRectF theViewport(0.0f, 0.0f, (float)mapRes, (float)mapRes);
973 static const QQuaternion rotOfs[6] {
974 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: -QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
975 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_HALFPI), roll: qRadiansToDegrees(radians: QSSG_PI)),
976 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: QSSG_HALFPI), yaw: 0.f, roll: 0.f),
977 QQuaternion::fromEulerAngles(pitch: qRadiansToDegrees(radians: -QSSG_HALFPI), yaw: 0.f, roll: 0.f),
978 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: qRadiansToDegrees(radians: QSSG_PI), roll: qRadiansToDegrees(radians: -QSSG_PI)),
979 QQuaternion::fromEulerAngles(pitch: 0.f, yaw: 0.f, roll: qRadiansToDegrees(radians: QSSG_PI)),
980 };
981
982 const QVector3D inLightPos = inLight->getGlobalPos();
983 const QVector3D inLightPivot = inLight->pivot;
984
985 for (int i = 0; i < 6; ++i) {
986 inCameras[i].parent = nullptr;
987 inCameras[i].clipNear = 1.0f;
988 inCameras[i].clipFar = qMax<float>(a: 2.0f, b: inLight->m_shadowMapFar);
989 inCameras[i].fov = qDegreesToRadians(degrees: 90.f);
990 inCameras[i].localTransform = QSSGRenderNode::calculateTransformMatrix(position: inLightPos, scale: QSSGRenderNode::initScale, pivot: inLightPivot, rotation: rotOfs[i]);
991 inCameras[i].calculateGlobalVariables(inViewport: theViewport);
992 }
993
994 /*
995 if ( inLight->type == RenderLightTypes::Point ) return;
996
997 QVector3D viewDirs[6];
998 QVector3D viewUp[6];
999 QMatrix3x3 theDirMatrix( inLight->m_GlobalTransform.getUpper3x3() );
1000
1001 viewDirs[0] = theDirMatrix.transform( QVector3D( 1.f, 0.f, 0.f ) );
1002 viewDirs[2] = theDirMatrix.transform( QVector3D( 0.f, -1.f, 0.f ) );
1003 viewDirs[4] = theDirMatrix.transform( QVector3D( 0.f, 0.f, 1.f ) );
1004 viewDirs[0].normalize(); viewDirs[2].normalize(); viewDirs[4].normalize();
1005 viewDirs[1] = -viewDirs[0];
1006 viewDirs[3] = -viewDirs[2];
1007 viewDirs[5] = -viewDirs[4];
1008
1009 viewUp[0] = viewDirs[2];
1010 viewUp[1] = viewDirs[2];
1011 viewUp[2] = viewDirs[5];
1012 viewUp[3] = viewDirs[4];
1013 viewUp[4] = viewDirs[2];
1014 viewUp[5] = viewDirs[2];
1015
1016 for (int i = 0; i < 6; ++i)
1017 {
1018 inCameras[i].LookAt( inLightPos, viewUp[i], inLightPos + viewDirs[i] );
1019 inCameras[i].CalculateGlobalVariables( theViewport, QVector2D( theViewport.m_Width,
1020 theViewport.m_Height ) );
1021 }
1022 */
1023}
1024
1025static int setupInstancing(QSSGSubsetRenderable *renderable, QSSGRhiGraphicsPipelineState *ps, QSSGRhiContext *rhiCtx, const QVector3D &cameraDirection, const QVector3D &cameraPosition)
1026{
1027 // TODO: non-static so it can be used from QSSGCustomMaterialSystem::rhiPrepareRenderable()?
1028 const bool instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, renderable, cameraDirection, cameraPosition, minThreshold: renderable->instancingLodMin, maxThreshold: renderable->instancingLodMax);
1029 int instanceBufferBinding = 0;
1030 if (instancing) {
1031 // set up new bindings for instanced buffers
1032 const quint32 stride = renderable->modelContext.model.instanceTable->stride();
1033 QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
1034 std::copy(first: ps->ia.inputLayout.cbeginBindings(), last: ps->ia.inputLayout.cendBindings(), result: std::back_inserter(x&: bindings));
1035 bindings.append(t: { stride, QRhiVertexInputBinding::PerInstance });
1036 instanceBufferBinding = bindings.size() - 1;
1037 ps->ia.inputLayout.setBindings(first: bindings.cbegin(), last: bindings.cend());
1038 }
1039 return instanceBufferBinding;
1040}
1041
1042static void rhiPrepareResourcesForReflectionMap(QSSGRhiContext *rhiCtx,
1043 QSSGPassKey passKey,
1044 const QSSGLayerRenderData &inData,
1045 QSSGReflectionMapEntry *pEntry,
1046 QSSGRhiGraphicsPipelineState *ps,
1047 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
1048 QSSGRenderCamera &inCamera,
1049 QSSGRenderer &renderer,
1050 QSSGRenderTextureCubeFace cubeFace)
1051{
1052 using namespace RenderHelpers;
1053
1054 if ((inData.layer.background == QSSGRenderLayer::Background::SkyBox && inData.layer.lightProbe) ||
1055 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1056 rhiPrepareSkyBoxForReflectionMap(rhiCtx, passKey, layer&: inData.layer, inCamera, renderer, entry: pEntry, cubeFace);
1057
1058 QSSGShaderFeatures features = inData.getShaderFeatures();
1059
1060 for (const auto &handle : sortedOpaqueObjects) {
1061 QSSGRenderableObject &inObject = *handle.obj;
1062
1063 QMatrix4x4 modelViewProjection;
1064 if (inObject.type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || inObject.type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1065 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(inObject));
1066 const bool hasSkinning = renderer.defaultMaterialShaderKeyProperties().m_boneCount.getValue(inDataStore: renderable.shaderDescription) > 0;
1067 modelViewProjection = hasSkinning ? pEntry->m_viewProjection
1068 : pEntry->m_viewProjection * renderable.globalTransform;
1069 }
1070
1071 rhiPrepareRenderable(rhiCtx, passKey, inData, inObject, renderPassDescriptor: pEntry->m_rhiRenderPassDesc, ps, featureSet: features, samples: 1,
1072 inCamera: &inCamera, alteredModelViewProjection: &modelViewProjection, cubeFace, entry: pEntry);
1073 }
1074}
1075
1076static inline void addDepthTextureBindings(QSSGRhiContext *rhiCtx,
1077 QSSGRhiShaderPipeline *shaderPipeline,
1078 QSSGRhiShaderResourceBindingList &bindings)
1079{
1080 if (shaderPipeline->depthTexture()) {
1081 int binding = shaderPipeline->bindingForTexture(name: "qt_depthTexture", hint: int(QSSGRhiSamplerBindingHints::DepthTexture));
1082 if (binding >= 0) {
1083 // nearest min/mag, no mipmap
1084 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
1085 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1086 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::FragmentStage, tex: shaderPipeline->depthTexture(), sampler);
1087 } // else ignore, not an error
1088 }
1089
1090 // SSAO texture
1091 if (shaderPipeline->ssaoTexture()) {
1092 int binding = shaderPipeline->bindingForTexture(name: "qt_aoTexture", hint: int(QSSGRhiSamplerBindingHints::AoTexture));
1093 if (binding >= 0) {
1094 // linear min/mag, no mipmap
1095 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1096 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1097 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::FragmentStage, tex: shaderPipeline->ssaoTexture(), sampler);
1098 } // else ignore, not an error
1099 }
1100}
1101
1102static void rhiPrepareResourcesForShadowMap(QSSGRhiContext *rhiCtx,
1103 const QSSGLayerRenderData &inData,
1104 QSSGPassKey passKey,
1105 const QSSGLayerGlobalRenderProperties &globalRenderProperties,
1106 QSSGShadowMapEntry *pEntry,
1107 QSSGRhiGraphicsPipelineState *ps,
1108 const QVector2D *depthAdjust,
1109 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
1110 QSSGRenderCamera &inCamera,
1111 bool orthographic,
1112 QSSGRenderTextureCubeFace cubeFace)
1113{
1114 QSSGShaderFeatures featureSet;
1115 if (orthographic)
1116 featureSet.set(feature: QSSGShaderFeatures::Feature::OrthoShadowPass, val: true);
1117 else
1118 featureSet.set(feature: QSSGShaderFeatures::Feature::CubeShadowPass, val: true);
1119
1120 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
1121
1122 for (const auto &handle : sortedOpaqueObjects) {
1123 QSSGRenderableObject *theObject = handle.obj;
1124 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1125
1126 QSSGShaderFeatures objectFeatureSet = featureSet;
1127 const bool isOpaqueDepthPrePass = theObject->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
1128 if (isOpaqueDepthPrePass)
1129 objectFeatureSet.set(feature: QSSGShaderFeatures::Feature::OpaqueDepthPrePass, val: true);
1130
1131 QSSGRhiDrawCallData *dcd = nullptr;
1132 QMatrix4x4 modelViewProjection;
1133 QSSGSubsetRenderable &renderable(static_cast<QSSGSubsetRenderable &>(*theObject));
1134 const auto &renderer(renderable.renderer);
1135 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1136 const bool hasSkinning = renderer->defaultMaterialShaderKeyProperties().m_boneCount.getValue(inDataStore: renderable.shaderDescription) > 0;
1137 modelViewProjection = hasSkinning ? pEntry->m_lightVP
1138 : pEntry->m_lightVP * renderable.globalTransform;
1139 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(renderable.subset.offset) << 3));
1140 dcd = &rhiCtx->drawCallData(key: { .cid: passKey, .model: &renderable.modelContext.model,
1141 .entry: pEntry, .entryIdx: entryIdx });
1142 }
1143
1144 QSSGRhiShaderResourceBindingList bindings;
1145 QSSGRhiShaderPipelinePtr shaderPipeline;
1146 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*theObject));
1147 if (theObject->type == QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset) {
1148 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
1149 ps->cullMode = QSSGRhiGraphicsPipelineState::toCullMode(cullFaceMode: material.cullMode);
1150 const bool blendParticles = subsetRenderable.renderer->defaultMaterialShaderKeyProperties().m_blendParticles.getValue(inDataStore: subsetRenderable.shaderDescription);
1151
1152 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet: objectFeatureSet);
1153 if (!shaderPipeline)
1154 continue;
1155 shaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd->ubuf);
1156 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1157 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, camera: inCamera, depthAdjust, alteredModelViewProjection: &modelViewProjection);
1158 if (blendParticles)
1159 QSSGParticleRenderer::updateUniformsForParticleModel(shaderPipeline&: *shaderPipeline, ubufData, model: &subsetRenderable.modelContext.model, offset: subsetRenderable.subset.offset);
1160 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1161 if (blendParticles)
1162 QSSGParticleRenderer::prepareParticlesForModel(shaderPipeline&: *shaderPipeline, rhiCtx, bindings, model: &subsetRenderable.modelContext.model);
1163 } else if (theObject->type == QSSGSubsetRenderable::Type::CustomMaterialMeshSubset) {
1164 const auto &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1165 ps->cullMode = QSSGRhiGraphicsPipelineState::toCullMode(cullFaceMode: material.m_cullMode);
1166
1167 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1168 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material, renderable&: subsetRenderable, featureSet: objectFeatureSet);
1169 if (!shaderPipeline)
1170 continue;
1171 shaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd->ubuf);
1172 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1173 // inCamera is the shadow camera, not the same as inData.camera
1174 customMaterialSystem.updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, material, renderable&: subsetRenderable,
1175 camera: inCamera, depthAdjust, alteredModelViewProjection: &modelViewProjection);
1176 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1177 }
1178
1179 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1180
1181 ps->shaderPipeline = shaderPipeline.get();
1182 ps->ia = subsetRenderable.subset.rhi.ia;
1183 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection: globalRenderProperties.cameraData.direction, cameraPosition: globalRenderProperties.cameraData.position);
1184 ps->ia.bakeVertexInputLocations(shaders: *shaderPipeline, instanceBufferBinding);
1185
1186
1187 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd->ubuf);
1188
1189 // Depth and SSAO textures, in case a custom material's shader code does something with them.
1190 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
1191
1192 if (isOpaqueDepthPrePass) {
1193 addOpaqueDepthPrePassBindings(rhiCtx,
1194 shaderPipeline: shaderPipeline.get(),
1195 renderableImage: subsetRenderable.firstImage,
1196 bindings,
1197 isCustomMaterialMeshSubset: (theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
1198 }
1199
1200 // There is no screen texture at this stage. But the shader from a
1201 // custom material may rely on it, and an object with that material
1202 // can end up in the shadow map's object list. So bind a dummy
1203 // texture then due to the lack of other options.
1204 int binding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
1205 if (binding >= 0) {
1206 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
1207 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
1208 QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
1209 QRhiTexture *dummyTexture = rhiCtx->dummyTexture(flags: {}, rub: resourceUpdates);
1210 rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
1211 bindings.addTexture(binding,
1212 stage: QRhiShaderResourceBinding::FragmentStage,
1213 tex: dummyTexture, sampler);
1214 }
1215
1216 QRhiShaderResourceBindings *srb = rhiCtx->srb(bindings);
1217 subsetRenderable.rhiRenderData.shadowPass.pipeline = rhiCtx->pipeline(key: QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: pEntry->m_rhiRenderPassDesc, srb),
1218 rpDesc: pEntry->m_rhiRenderPassDesc,
1219 srb);
1220 subsetRenderable.rhiRenderData.shadowPass.srb[cubeFaceIdx] = srb;
1221 }
1222 }
1223}
1224
1225static void fillTargetBlend(QRhiGraphicsPipeline::TargetBlend *targetBlend, QSSGRenderDefaultMaterial::MaterialBlendMode materialBlend)
1226{
1227 // Assuming default values in the other TargetBlend fields
1228 switch (materialBlend) {
1229 case QSSGRenderDefaultMaterial::MaterialBlendMode::Screen:
1230 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
1231 targetBlend->dstColor = QRhiGraphicsPipeline::One;
1232 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
1233 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
1234 break;
1235 case QSSGRenderDefaultMaterial::MaterialBlendMode::Multiply:
1236 targetBlend->srcColor = QRhiGraphicsPipeline::DstColor;
1237 targetBlend->dstColor = QRhiGraphicsPipeline::Zero;
1238 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
1239 targetBlend->dstAlpha = QRhiGraphicsPipeline::One;
1240 break;
1241 default:
1242 // Use SourceOver for everything else
1243 targetBlend->srcColor = QRhiGraphicsPipeline::SrcAlpha;
1244 targetBlend->dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1245 targetBlend->srcAlpha = QRhiGraphicsPipeline::One;
1246 targetBlend->dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1247 break;
1248 }
1249}
1250
1251void RenderHelpers::rhiPrepareRenderable(QSSGRhiContext *rhiCtx,
1252 QSSGPassKey passKey,
1253 const QSSGLayerRenderData &inData,
1254 QSSGRenderableObject &inObject,
1255 QRhiRenderPassDescriptor *renderPassDescriptor,
1256 QSSGRhiGraphicsPipelineState *ps,
1257 QSSGShaderFeatures featureSet,
1258 int samples,
1259 QSSGRenderCamera *inCamera,
1260 QMatrix4x4 *alteredModelViewProjection,
1261 QSSGRenderTextureCubeFace cubeFace,
1262 QSSGReflectionMapEntry *entry)
1263{
1264 QSSGRenderCamera *camera = inData.camera;
1265 if (inCamera)
1266 camera = inCamera;
1267
1268 switch (inObject.type) {
1269 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1270 {
1271 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1272
1273 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(flag: QSSGRenderableObjectFlag::ReceivesReflections))
1274 featureSet.set(feature: QSSGShaderFeatures::Feature::ReflectionProbe, val: true);
1275
1276 if ((cubeFace != QSSGRenderTextureCubeFaceNone)) {
1277 // Disable tonemapping for the reflection pass
1278 featureSet.disableTonemapping();
1279 }
1280
1281 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1282 featureSet.set(feature: QSSGShaderFeatures::Feature::Lightmap, val: true);
1283
1284 const auto &shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
1285 if (shaderPipeline) {
1286 // Unlike the subsetRenderable (which is allocated per frame so is
1287 // not persistent in any way), the model reference is persistent in
1288 // the sense that it references the model node in the scene graph.
1289 // Combined with the layer node (multiple View3Ds may share the
1290 // same scene!), this is suitable as a key to get the uniform
1291 // buffers that were used with the rendering of the same model in
1292 // the previous frame.
1293 QSSGRhiShaderResourceBindingList bindings;
1294 const auto &modelNode = subsetRenderable.modelContext.model;
1295 const bool blendParticles = subsetRenderable.renderer->defaultMaterialShaderKeyProperties().m_blendParticles.getValue(inDataStore: subsetRenderable.shaderDescription);
1296
1297
1298 // NOTE:
1299 // - entryIdx should 0 for QSSGRenderTextureCubeFaceNone.
1300 // In all other cases the entryIdx is a combination of the cubeface idx and the subset offset, where the lower bits
1301 // are the cubeface idx.
1302 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
1303 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * (cubeFaceIdx + (quintptr(subsetRenderable.subset.offset) << 3));
1304 // If there's an entry we merge that with the address of the material
1305 const auto entryPartA = reinterpret_cast<quintptr>(&subsetRenderable.material);
1306 const auto entryPartB = reinterpret_cast<quintptr>(entry);
1307 const void *entryId = reinterpret_cast<const void *>(entryPartA ^ entryPartB);
1308
1309 QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: passKey, .model: &modelNode, .entry: entryId, .entryIdx: entryIdx });
1310
1311 shaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd.ubuf);
1312 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1313 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, camera: *camera, depthAdjust: nullptr, alteredModelViewProjection);
1314 if (blendParticles)
1315 QSSGParticleRenderer::updateUniformsForParticleModel(shaderPipeline&: *shaderPipeline, ubufData, model: &subsetRenderable.modelContext.model, offset: subsetRenderable.subset.offset);
1316 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1317
1318 if (blendParticles)
1319 QSSGParticleRenderer::prepareParticlesForModel(shaderPipeline&: *shaderPipeline, rhiCtx, bindings, model: &subsetRenderable.modelContext.model);
1320
1321 // Skinning
1322 if (QRhiTexture *boneTexture = inData.getBonemapTexture(modelContext: subsetRenderable.modelContext)) {
1323 int binding = shaderPipeline->bindingForTexture(name: "qt_boneTexture");
1324 if (binding >= 0) {
1325 QRhiSampler *boneSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
1326 .magFilter: QRhiSampler::Nearest,
1327 .mipmap: QRhiSampler::None,
1328 .hTiling: QRhiSampler::ClampToEdge,
1329 .vTiling: QRhiSampler::ClampToEdge,
1330 .zTiling: QRhiSampler::Repeat
1331 });
1332 bindings.addTexture(binding,
1333 stage: QRhiShaderResourceBinding::VertexStage,
1334 tex: boneTexture,
1335 sampler: boneSampler);
1336 }
1337 }
1338 // Morphing
1339 auto *targetsTexture = subsetRenderable.subset.rhi.targetsTexture;
1340 if (targetsTexture) {
1341 int binding = shaderPipeline->bindingForTexture(name: "qt_morphTargetTexture");
1342 if (binding >= 0) {
1343 QRhiSampler *targetsSampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest,
1344 .magFilter: QRhiSampler::Nearest,
1345 .mipmap: QRhiSampler::None,
1346 .hTiling: QRhiSampler::ClampToEdge,
1347 .vTiling: QRhiSampler::ClampToEdge,
1348 .zTiling: QRhiSampler::ClampToEdge
1349 });
1350 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::VertexStage, tex: subsetRenderable.subset.rhi.targetsTexture, sampler: targetsSampler);
1351 }
1352 }
1353
1354 ps->samples = samples;
1355
1356 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
1357 ps->cullMode = QSSGRhiGraphicsPipelineState::toCullMode(cullFaceMode: material.cullMode);
1358 fillTargetBlend(targetBlend: &ps->targetBlend, materialBlend: material.blendMode);
1359
1360 ps->ia = subsetRenderable.subset.rhi.ia;
1361 QVector3D cameraDirection = inData.cameraData->direction;
1362 if (inCamera)
1363 cameraDirection = inCamera->getScalingCorrectDirection();
1364 QVector3D cameraPosition = inData.cameraData->position;
1365 if (inCamera)
1366 cameraPosition = inCamera->getGlobalPos();
1367 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection, cameraPosition);
1368 ps->ia.bakeVertexInputLocations(shaders: *shaderPipeline, instanceBufferBinding);
1369
1370 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf, offset: 0, size: shaderPipeline->ub0Size());
1371
1372 if (shaderPipeline->isLightingEnabled()) {
1373 bindings.addUniformBuffer(binding: 1, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf,
1374 offset: shaderPipeline->ub0LightDataOffset(),
1375 size: shaderPipeline->ub0LightDataSize());
1376 }
1377
1378 // Texture maps
1379 QSSGRenderableImage *renderableImage = subsetRenderable.firstImage;
1380 while (renderableImage) {
1381 const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(type: renderableImage->m_mapType);
1382 const int samplerHint = int(renderableImage->m_mapType);
1383 int samplerBinding = shaderPipeline->bindingForTexture(name: samplerName, hint: samplerHint);
1384 if (samplerBinding >= 0) {
1385 QRhiTexture *texture = renderableImage->m_texture.m_texture;
1386 if (samplerBinding >= 0 && texture) {
1387 const bool mipmapped = texture->flags().testFlag(flag: QRhiTexture::MipMapped);
1388 QSSGRhiSamplerDescription samplerDesc = {
1389 .minFilter: toRhi(op: renderableImage->m_imageNode.m_minFilterType),
1390 .magFilter: toRhi(op: renderableImage->m_imageNode.m_magFilterType),
1391 .mipmap: mipmapped ? toRhi(op: renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
1392 .hTiling: toRhi(tiling: renderableImage->m_imageNode.m_horizontalTilingMode),
1393 .vTiling: toRhi(tiling: renderableImage->m_imageNode.m_verticalTilingMode),
1394 .zTiling: QRhiSampler::Repeat
1395 };
1396 rhiCtx->checkAndAdjustForNPoT(texture, samplerDescription: &samplerDesc);
1397 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: samplerDesc);
1398 bindings.addTexture(binding: samplerBinding, stage: RENDERER_VISIBILITY_ALL, tex: texture, sampler);
1399 }
1400 } // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
1401 renderableImage = renderableImage->m_nextImage;
1402 }
1403
1404 if (shaderPipeline->isLightingEnabled()) {
1405 // Shadow map textures
1406 const int shadowMapCount = shaderPipeline->shadowMapCount();
1407 for (int i = 0; i < shadowMapCount; ++i) {
1408 QSSGRhiShadowMapProperties &shadowMapProperties(shaderPipeline->shadowMapAt(index: i));
1409 QRhiTexture *texture = shadowMapProperties.shadowMapTexture;
1410 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1411 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1412 Q_ASSERT(texture && sampler);
1413 const QByteArray &name(shadowMapProperties.shadowMapTextureUniformName);
1414 if (shadowMapProperties.cachedBinding < 0)
1415 shadowMapProperties.cachedBinding = shaderPipeline->bindingForTexture(name);
1416 if (shadowMapProperties.cachedBinding < 0) {
1417 qWarning(msg: "No combined image sampler for shadow map texture '%s'", name.data());
1418 continue;
1419 }
1420 bindings.addTexture(binding: shadowMapProperties.cachedBinding, stage: QRhiShaderResourceBinding::FragmentStage,
1421 tex: texture, sampler);
1422 }
1423
1424 // Prioritize reflection texture over Light Probe texture because
1425 // reflection texture also contains the irradiance and pre filtered
1426 // values for the light probe.
1427 if (featureSet.isSet(feature: QSSGShaderFeatures::Feature::ReflectionProbe)) {
1428 int reflectionSampler = shaderPipeline->bindingForTexture(name: "qt_reflectionMap");
1429 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear,
1430 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1431 QRhiTexture* reflectionTexture = inData.getReflectionMapManager()->reflectionMapEntry(probeIdx: subsetRenderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
1432 if (reflectionSampler >= 0 && reflectionTexture)
1433 bindings.addTexture(binding: reflectionSampler, stage: QRhiShaderResourceBinding::FragmentStage, tex: reflectionTexture, sampler);
1434 } else if (shaderPipeline->lightProbeTexture()) {
1435 int binding = shaderPipeline->bindingForTexture(name: "qt_lightProbe", hint: int(QSSGRhiSamplerBindingHints::LightProbe));
1436 if (binding >= 0) {
1437 auto tiling = shaderPipeline->lightProbeTiling();
1438 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::Linear, // enables mipmapping
1439 .hTiling: toRhi(tiling: tiling.first), .vTiling: toRhi(tiling: tiling.second), .zTiling: QRhiSampler::Repeat });
1440 bindings.addTexture(binding, stage: QRhiShaderResourceBinding::FragmentStage,
1441 tex: shaderPipeline->lightProbeTexture(), sampler);
1442 } else {
1443 qWarning(msg: "Could not find sampler for lightprobe");
1444 }
1445 }
1446
1447 // Screen Texture
1448 if (shaderPipeline->screenTexture()) {
1449 int binding = shaderPipeline->bindingForTexture(name: "qt_screenTexture", hint: int(QSSGRhiSamplerBindingHints::ScreenTexture));
1450 if (binding >= 0) {
1451 // linear min/mag, mipmap filtering depends on the
1452 // texture, with SCREEN_TEXTURE there are no mipmaps, but
1453 // once SCREEN_MIP_TEXTURE is seen the texture (the same
1454 // one) has mipmaps generated.
1455 QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(flag: QRhiTexture::MipMapped)
1456 ? QRhiSampler::Linear : QRhiSampler::None;
1457 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: mipFilter,
1458 .hTiling: QRhiSampler::Repeat, .vTiling: QRhiSampler::Repeat, .zTiling: QRhiSampler::Repeat });
1459 bindings.addTexture(binding,
1460 stage: QRhiShaderResourceBinding::FragmentStage,
1461 tex: shaderPipeline->screenTexture(), sampler);
1462 } // else ignore, not an error
1463 }
1464
1465 if (shaderPipeline->lightmapTexture()) {
1466 int binding = shaderPipeline->bindingForTexture(name: "qt_lightmap", hint: int(QSSGRhiSamplerBindingHints::LightmapTexture));
1467 if (binding >= 0) {
1468 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1469 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1470 bindings.addTexture(binding,
1471 stage: QRhiShaderResourceBinding::FragmentStage,
1472 tex: shaderPipeline->lightmapTexture(), sampler);
1473 } // else ignore, not an error
1474 }
1475 }
1476
1477 // Depth and SSAO textures
1478 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
1479
1480 // Instead of always doing a QHash find in srb(), store the binding
1481 // list and the srb object in the per-model+material
1482 // QSSGRhiUniformBufferSet. While this still needs comparing the
1483 // binding list, to see if something has changed, it results in
1484 // significant gains with lots of models in the scene (because the
1485 // srb hash table becomes large then, so avoiding the lookup as
1486 // much as possible is helpful)
1487 QRhiShaderResourceBindings *&srb = dcd.srb;
1488 bool srbChanged = false;
1489 if (!srb || bindings != dcd.bindings) {
1490 srb = rhiCtx->srb(bindings);
1491 dcd.bindings = bindings;
1492 srbChanged = true;
1493 }
1494
1495 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1496 subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
1497 else
1498 subsetRenderable.rhiRenderData.mainPass.srb = srb;
1499
1500 const QSSGGraphicsPipelineStateKey pipelineKey = QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc: renderPassDescriptor, srb);
1501 if (dcd.pipeline
1502 && !srbChanged
1503 && dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash // we have the hash code anyway, use it to early out upon mismatch
1504 && dcd.renderTargetDescription == pipelineKey.renderTargetDescription
1505 && dcd.ps == *ps)
1506 {
1507 if (cubeFace != QSSGRenderTextureCubeFaceNone)
1508 subsetRenderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
1509 else
1510 subsetRenderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
1511 } else {
1512 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1513 subsetRenderable.rhiRenderData.reflectionPass.pipeline = rhiCtx->pipeline(key: pipelineKey,
1514 rpDesc: renderPassDescriptor,
1515 srb);
1516 dcd.pipeline = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1517 } else {
1518 subsetRenderable.rhiRenderData.mainPass.pipeline = rhiCtx->pipeline(key: pipelineKey,
1519 rpDesc: renderPassDescriptor,
1520 srb);
1521 dcd.pipeline = subsetRenderable.rhiRenderData.mainPass.pipeline;
1522 }
1523 dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
1524 dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
1525 dcd.ps = *ps;
1526 }
1527 }
1528 break;
1529 }
1530 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1531 {
1532 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(inObject));
1533 const QSSGRenderCustomMaterial &material = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
1534 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1535
1536 featureSet.set(feature: QSSGShaderFeatures::Feature::LightProbe, val: inData.layer.lightProbe || material.m_iblProbe);
1537
1538 if ((cubeFace == QSSGRenderTextureCubeFaceNone) && subsetRenderable.reflectionProbeIndex >= 0 && subsetRenderable.renderableFlags.testFlag(flag: QSSGRenderableObjectFlag::ReceivesReflections))
1539 featureSet.set(feature: QSSGShaderFeatures::Feature::ReflectionProbe, val: true);
1540
1541 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1542 // Disable tonemapping for the reflection pass
1543 featureSet.disableTonemapping();
1544 }
1545
1546 if (subsetRenderable.renderableFlags.rendersWithLightmap())
1547 featureSet.set(feature: QSSGShaderFeatures::Feature::Lightmap, val: true);
1548
1549 customMaterialSystem.rhiPrepareRenderable(ps, passKey, renderable&: subsetRenderable, featureSet,
1550 material, layerData: inData, renderPassDescriptor, samples,
1551 camera: inCamera, cubeFace, modelViewProjection: alteredModelViewProjection, entry);
1552 break;
1553 }
1554 case QSSGRenderableObject::Type::Particles:
1555 {
1556 QSSGParticlesRenderable &particleRenderable(static_cast<QSSGParticlesRenderable &>(inObject));
1557 const auto &shaderPipeline = shadersForParticleMaterial(ps, particleRenderable);
1558 if (shaderPipeline) {
1559 QSSGParticleRenderer::rhiPrepareRenderable(shaderPipeline&: *shaderPipeline, passKey, rhiCtx, ps, renderable&: particleRenderable, inData, renderPassDescriptor, samples,
1560 camera: inCamera, cubeFace, entry);
1561 }
1562 break;
1563 }
1564 }
1565}
1566
1567void RenderHelpers::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
1568 const QSSGRhiGraphicsPipelineState &state,
1569 QSSGRenderableObject &object,
1570 bool *needsSetViewport,
1571 QSSGRenderTextureCubeFace cubeFace)
1572{
1573 switch (object.type) {
1574 case QSSGRenderableObject::Type::DefaultMaterialMeshSubset:
1575 {
1576 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1577
1578 QRhiGraphicsPipeline *ps = subsetRenderable.rhiRenderData.mainPass.pipeline;
1579 QRhiShaderResourceBindings *srb = subsetRenderable.rhiRenderData.mainPass.srb;
1580
1581 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
1582 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
1583 ps = subsetRenderable.rhiRenderData.reflectionPass.pipeline;
1584 srb = subsetRenderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
1585 }
1586
1587 if (!ps || !srb)
1588 return;
1589
1590 QRhiBuffer *vertexBuffer = subsetRenderable.subset.rhi.vertexBuffer->buffer();
1591 QRhiBuffer *indexBuffer = subsetRenderable.subset.rhi.indexBuffer ? subsetRenderable.subset.rhi.indexBuffer->buffer() : nullptr;
1592
1593 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1594 // QRhi optimizes out unnecessary binding of the same pipline
1595 cb->setGraphicsPipeline(ps);
1596 cb->setShaderResources(srb);
1597
1598 if (*needsSetViewport) {
1599 cb->setViewport(state.viewport);
1600 if (state.scissorEnable)
1601 cb->setScissor(state.scissor);
1602 *needsSetViewport = false;
1603 }
1604
1605 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1606 int vertexBufferCount = 1;
1607 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1608 quint32 instances = 1;
1609 if ( subsetRenderable.modelContext.model.instancing()) {
1610 instances = subsetRenderable.modelContext.model.instanceCount();
1611 // If the instance count is 0, the bail out before trying to do any
1612 // draw calls. Making an instanced draw call with a count of 0 is invalid
1613 // for Metal and likely other API's as well.
1614 // It is possible that the particale system may produce 0 instances here
1615 if (instances == 0)
1616 return;
1617 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable.instanceBuffer, 0);
1618 vertexBufferCount = 2;
1619 }
1620 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1621 if (state.usesStencilRef)
1622 cb->setStencilRef(state.stencilRef);
1623 if (indexBuffer) {
1624 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: subsetRenderable.subset.rhi.indexBuffer->indexFormat());
1625 cb->drawIndexed(indexCount: subsetRenderable.subset.lodCount(lodLevel: subsetRenderable.subsetLevelOfDetail), instanceCount: instances, firstIndex: subsetRenderable.subset.lodOffset(lodLevel: subsetRenderable.subsetLevelOfDetail));
1626 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable.subset.lodCount(subsetRenderable.subsetLevelOfDetail), instances));
1627 } else {
1628 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
1629 cb->draw(vertexCount: subsetRenderable.subset.count, instanceCount: instances, firstVertex: subsetRenderable.subset.offset);
1630 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable.subset.count, instances));
1631 }
1632 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable.subset.count | quint64(instances) << 32),
1633 QVector<int>({subsetRenderable.modelContext.model.profilingId,
1634 subsetRenderable.material.profilingId}));
1635 break;
1636 }
1637 case QSSGRenderableObject::Type::CustomMaterialMeshSubset:
1638 {
1639 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(object));
1640 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
1641 customMaterialSystem.rhiRenderRenderable(rhiCtx, renderable&: subsetRenderable, needsSetViewport, cubeFace, state);
1642 break;
1643 }
1644 case QSSGRenderableObject::Type::Particles:
1645 {
1646 QSSGParticlesRenderable &renderable(static_cast<QSSGParticlesRenderable &>(object));
1647 QSSGParticleRenderer::rhiRenderRenderable(rhiCtx, renderable, needsSetViewport, cubeFace, state);
1648 break;
1649 }
1650 }
1651}
1652
1653void RenderHelpers::rhiRenderShadowMap(QSSGRhiContext *rhiCtx,
1654 QSSGPassKey passKey,
1655 QSSGRhiGraphicsPipelineState &ps,
1656 QSSGRenderShadowMap &shadowMapManager,
1657 const QSSGRenderCamera &camera,
1658 const QSSGShaderLightList &globalLights,
1659 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
1660 QSSGRenderer &renderer,
1661 const QSSGBoxPoints &castingObjectsBox,
1662 const QSSGBoxPoints &receivingObjectsBox)
1663{
1664 const QSSGLayerGlobalRenderProperties &globalRenderProperties = renderer.getLayerGlobalRenderProperties();
1665 QSSG_ASSERT(globalRenderProperties.layer.renderData, return);
1666 const QSSGLayerRenderData &layerData = *globalRenderProperties.layer.renderData;
1667
1668 static const auto rhiRenderOneShadowMap = [](QSSGRhiContext *rhiCtx,
1669 QSSGRhiGraphicsPipelineState *ps,
1670 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
1671 int cubeFace) {
1672 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1673 bool needsSetViewport = true;
1674
1675 for (const auto &handle : sortedOpaqueObjects) {
1676 QSSGRenderableObject *theObject = handle.obj;
1677 QSSG_ASSERT(theObject->renderableFlags.castsShadows(), continue);
1678 if (theObject->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || theObject->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
1679 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(theObject));
1680
1681 QRhiBuffer *vertexBuffer = renderable->subset.rhi.vertexBuffer->buffer();
1682 QRhiBuffer *indexBuffer = renderable->subset.rhi.indexBuffer
1683 ? renderable->subset.rhi.indexBuffer->buffer()
1684 : nullptr;
1685
1686 // Ideally we shouldn't need to deal with this, as only "valid" objects should be processed at this point.
1687 if (!renderable->rhiRenderData.shadowPass.pipeline)
1688 continue;
1689
1690 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
1691
1692 cb->setGraphicsPipeline(renderable->rhiRenderData.shadowPass.pipeline);
1693
1694 QRhiShaderResourceBindings *srb = renderable->rhiRenderData.shadowPass.srb[cubeFace];
1695 cb->setShaderResources(srb);
1696
1697 if (needsSetViewport) {
1698 cb->setViewport(ps->viewport);
1699 needsSetViewport = false;
1700 }
1701
1702 QRhiCommandBuffer::VertexInput vertexBuffers[2];
1703 int vertexBufferCount = 1;
1704 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
1705 quint32 instances = 1;
1706 if (renderable->modelContext.model.instancing()) {
1707 instances = renderable->modelContext.model.instanceCount();
1708 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable->instanceBuffer, 0);
1709 vertexBufferCount = 2;
1710 }
1711 if (indexBuffer) {
1712 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: renderable->subset.rhi.indexBuffer->indexFormat());
1713 cb->drawIndexed(indexCount: renderable->subset.count, instanceCount: instances, firstIndex: renderable->subset.offset);
1714 QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable->subset.count, instances));
1715 } else {
1716 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
1717 cb->draw(vertexCount: renderable->subset.count, instanceCount: instances, firstVertex: renderable->subset.offset);
1718 QSSGRHICTX_STAT(rhiCtx, draw(renderable->subset.count, instances));
1719 }
1720 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable->subset.count | quint64(instances) << 32),
1721 QVector<int>({renderable->modelContext.model.profilingId,
1722 renderable->material.profilingId}));
1723 }
1724 }
1725 };
1726
1727 static const auto rhiBlurShadowMap = [](QSSGRhiContext *rhiCtx,
1728 QSSGShadowMapEntry *pEntry,
1729 QSSGRenderer &renderer,
1730 float shadowFilter,
1731 float shadowMapFar,
1732 bool orthographic) {
1733 // may not be able to do the blur pass if the number of max color
1734 // attachments is the gl/vk spec mandated minimum of 4, and we need 6.
1735 // (applicable only to !orthographic, whereas orthographic always works)
1736 if (!pEntry->m_rhiBlurRenderTarget0 || !pEntry->m_rhiBlurRenderTarget1)
1737 return;
1738
1739 QRhi *rhi = rhiCtx->rhi();
1740 QSSGRhiGraphicsPipelineState ps;
1741 QRhiTexture *map = orthographic ? pEntry->m_rhiDepthMap : pEntry->m_rhiDepthCube;
1742 QRhiTexture *workMap = orthographic ? pEntry->m_rhiDepthCopy : pEntry->m_rhiCubeCopy;
1743 const QSize size = map->pixelSize();
1744 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1745
1746 const auto &blurXPipeline = orthographic ? renderer.getRhiOrthographicShadowBlurXShader()
1747 : renderer.getRhiCubemapShadowBlurXShader();
1748 if (!blurXPipeline)
1749 return;
1750 ps.shaderPipeline = blurXPipeline.get();
1751
1752 ps.colorAttachmentCount = orthographic ? 1 : 6;
1753
1754 // construct a key that is unique for this frame (we use a dynamic buffer
1755 // so even if the same key gets used in the next frame, just updating the
1756 // contents on the same QRhiBuffer is ok due to QRhi's internal double buffering)
1757 QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: map, .model: nullptr, .entry: nullptr, .entryIdx: 0 });
1758 if (!dcd.ubuf) {
1759 dcd.ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: 64 + 8);
1760 dcd.ubuf->create();
1761 }
1762
1763 // the blur also needs Y reversed in order to get correct results (while
1764 // the second blur step would end up with the correct orientation without
1765 // this too, but we need to blur the correct fragments in the second step
1766 // hence the flip is important)
1767 QMatrix4x4 flipY;
1768 // correct for D3D and Metal but not for Vulkan because there the Y is down
1769 // in NDC so that kind of self-corrects...
1770 if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
1771 flipY.data()[5] = -1.0f;
1772 float cameraProperties[2] = { shadowFilter, shadowMapFar };
1773 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1774 memcpy(dest: ubufData, src: flipY.constData(), n: 64);
1775 memcpy(dest: ubufData + 64, src: cameraProperties, n: 8);
1776 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1777
1778 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1779 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
1780 Q_ASSERT(sampler);
1781
1782 QSSGRhiShaderResourceBindingList bindings;
1783 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
1784 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: map, sampler);
1785 QRhiShaderResourceBindings *srb = rhiCtx->srb(bindings);
1786
1787 QSSGRhiQuadRenderer::Flags quadFlags;
1788 if (orthographic) // orthoshadowshadowblurx and y have attr_uv as well
1789 quadFlags |= QSSGRhiQuadRenderer::UvCoords;
1790 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
1791 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: pEntry->m_rhiBlurRenderTarget0, flags: quadFlags);
1792
1793 // repeat for blur Y, now depthCopy -> depthMap or cubeCopy -> depthCube
1794
1795 const auto &blurYPipeline = orthographic ? renderer.getRhiOrthographicShadowBlurYShader()
1796 : renderer.getRhiCubemapShadowBlurYShader();
1797 if (!blurYPipeline)
1798 return;
1799 ps.shaderPipeline = blurYPipeline.get();
1800
1801 bindings.clear();
1802 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
1803 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: workMap, sampler);
1804 srb = rhiCtx->srb(bindings);
1805
1806 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
1807 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: pEntry->m_rhiBlurRenderTarget1, flags: quadFlags);
1808 };
1809
1810 QRhi *rhi = rhiCtx->rhi();
1811 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1812
1813 // We need to deal with a clip depth range of [0, 1] or
1814 // [-1, 1], depending on the graphics API underneath.
1815 QVector2D depthAdjust; // (d + depthAdjust[0]) * depthAdjust[1] = d mapped to [0, 1]
1816 if (rhi->isClipDepthZeroToOne()) {
1817 // d is [0, 1] so no need for any mapping
1818 depthAdjust[0] = 0.0f;
1819 depthAdjust[1] = 1.0f;
1820 } else {
1821 // d is [-1, 1]
1822 depthAdjust[0] = 1.0f;
1823 depthAdjust[1] = 0.5f;
1824 }
1825
1826 // Create shadow map for each light in the scene
1827 for (int i = 0, ie = globalLights.size(); i != ie; ++i) {
1828 if (!globalLights[i].shadows || globalLights[i].light->m_fullyBaked)
1829 continue;
1830
1831 QSSGShadowMapEntry *pEntry = shadowMapManager.shadowMapEntry(lightIdx: i);
1832 if (!pEntry)
1833 continue;
1834
1835 Q_ASSERT(pEntry->m_rhiDepthStencil);
1836 const bool orthographic = pEntry->m_rhiDepthMap && pEntry->m_rhiDepthCopy;
1837 if (orthographic) {
1838 const QSize size = pEntry->m_rhiDepthMap->pixelSize();
1839 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1840
1841 const auto &light = globalLights[i].light;
1842 const auto cameraType = (light->type == QSSGRenderLight::Type::DirectionalLight) ? QSSGRenderCamera::Type::OrthographicCamera : QSSGRenderCamera::Type::CustomCamera;
1843 QSSGRenderCamera theCamera(cameraType);
1844 setupCameraForShadowMap(inCamera: camera, inLight: light, theCamera, castingBox: castingObjectsBox, receivingBox: receivingObjectsBox);
1845 theCamera.calculateViewProjectionMatrix(outMatrix&: pEntry->m_lightVP);
1846 pEntry->m_lightView = theCamera.globalTransform.inverted(); // pre-calculate this for the material
1847
1848 rhiPrepareResourcesForShadowMap(rhiCtx, inData: layerData, passKey, globalRenderProperties, pEntry, ps: &ps, depthAdjust: &depthAdjust,
1849 sortedOpaqueObjects, inCamera&: theCamera, orthographic: true, cubeFace: QSSGRenderTextureCubeFaceNone);
1850
1851 // Render into the 2D texture pEntry->m_rhiDepthMap, using
1852 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1853 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[0];
1854 cb->beginPass(rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags());
1855 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1856 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1857 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, 0);
1858 cb->endPass();
1859 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1860 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
1861
1862 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1863 rhiBlurShadowMap(rhiCtx, pEntry, renderer, globalLights[i].light->m_shadowFilter, globalLights[i].light->m_shadowMapFar, true);
1864 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map_blur"));
1865 } else {
1866 Q_ASSERT(pEntry->m_rhiDepthCube && pEntry->m_rhiCubeCopy);
1867 const QSize size = pEntry->m_rhiDepthCube->pixelSize();
1868 ps.viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1869
1870 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1871 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1872 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1873 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1874 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1875 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1876 setupCubeShadowCameras(inLight: globalLights[i].light, inCameras: theCameras);
1877 pEntry->m_lightView = QMatrix4x4();
1878
1879 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1880 for (const auto face : QSSGRenderTextureCubeFaces) {
1881 theCameras[quint8(face)].calculateViewProjectionMatrix(outMatrix&: pEntry->m_lightVP);
1882 pEntry->m_lightCubeView[quint8(face)] = theCameras[quint8(face)].globalTransform.inverted(); // pre-calculate this for the material
1883
1884 rhiPrepareResourcesForShadowMap(rhiCtx, inData: layerData, passKey, globalRenderProperties, pEntry, ps: &ps, depthAdjust: &depthAdjust,
1885 sortedOpaqueObjects, inCamera&: theCameras[quint8(face)], orthographic: false, cubeFace: face);
1886 }
1887
1888 for (const auto face : QSSGRenderTextureCubeFaces) {
1889 // Render into one face of the cubemap texture pEntry->m_rhiDephCube, using
1890 // pEntry->m_rhiDepthStencil as the (throwaway) depth/stencil buffer.
1891
1892 QSSGRenderTextureCubeFace outFace = face;
1893 // FACE S T GL
1894 // +x -z, -y right
1895 // -x +z, -y left
1896 // +y +x, +z top
1897 // -y +x, -z bottom
1898 // +z +x, -y front
1899 // -z -x, -y back
1900 // FACE S T D3D
1901 // +x -z, +y right
1902 // -x +z, +y left
1903 // +y +x, -z bottom
1904 // -y +x, +z top
1905 // +z +x, +y front
1906 // -z -x, +y back
1907 if (swapYFaces) {
1908 // +Y and -Y faces get swapped (D3D, Vulkan, Metal).
1909 // See shadowMapping.glsllib. This is complemented there by reversing T as well.
1910 if (outFace == QSSGRenderTextureCubeFace::PosY)
1911 outFace = QSSGRenderTextureCubeFace::NegY;
1912 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1913 outFace = QSSGRenderTextureCubeFace::PosY;
1914 }
1915 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1916 cb->beginPass(rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags());
1917 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
1918 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1919 rhiRenderOneShadowMap(rhiCtx, &ps, sortedOpaqueObjects, quint8(face));
1920 cb->endPass();
1921 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
1922 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("shadow_cube", 0, outFace));
1923 }
1924
1925 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1926 rhiBlurShadowMap(rhiCtx, pEntry, renderer, globalLights[i].light->m_shadowFilter, globalLights[i].light->m_shadowMapFar, false);
1927 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_cube_blur"));
1928 }
1929 }
1930}
1931
1932void RenderHelpers::rhiRenderReflectionMap(QSSGRhiContext *rhiCtx,
1933 QSSGPassKey passKey,
1934 const QSSGLayerRenderData &inData,
1935 QSSGRhiGraphicsPipelineState *ps,
1936 QSSGRenderReflectionMap &reflectionMapManager,
1937 const QVector<QSSGRenderReflectionProbe *> &reflectionProbes,
1938 const QVector<QSSGRenderableObjectHandle> &reflectionPassObjects,
1939 QSSGRenderer &renderer)
1940{
1941 QRhi *rhi = rhiCtx->rhi();
1942 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1943
1944 const bool renderSkybox = (inData.layer.background == QSSGRenderLayer::Background::SkyBox ||
1945 inData.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap)
1946 && rhiCtx->rhi()->isFeatureSupported(feature: QRhi::TexelFetch);
1947
1948 for (int i = 0, ie = reflectionProbes.size(); i != ie; ++i) {
1949 QSSGReflectionMapEntry *pEntry = reflectionMapManager.reflectionMapEntry(probeIdx: i);
1950 if (!pEntry)
1951 continue;
1952
1953 if (!pEntry->m_needsRender)
1954 continue;
1955
1956 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame && pEntry->m_rendered)
1957 continue;
1958
1959 if (reflectionProbes[i]->texture)
1960 continue;
1961
1962 Q_ASSERT(pEntry->m_rhiDepthStencil);
1963 Q_ASSERT(pEntry->m_rhiCube);
1964
1965 const QSize size = pEntry->m_rhiCube->pixelSize();
1966 ps->viewport = QRhiViewport(0, 0, float(size.width()), float(size.height()));
1967
1968 QSSGRenderCamera theCameras[6] { QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1969 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1970 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1971 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1972 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera},
1973 QSSGRenderCamera{QSSGRenderCamera::Type::PerspectiveCamera} };
1974 setupCubeReflectionCameras(inProbe: reflectionProbes[i], inCameras: theCameras);
1975 const bool swapYFaces = !rhi->isYUpInFramebuffer();
1976 for (const auto face : QSSGRenderTextureCubeFaces) {
1977 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face);
1978 theCameras[cubeFaceIdx].calculateViewProjectionMatrix(outMatrix&: pEntry->m_viewProjection);
1979
1980 rhiPrepareResourcesForReflectionMap(rhiCtx, passKey, inData, pEntry, ps,
1981 sortedOpaqueObjects: reflectionPassObjects, inCamera&: theCameras[cubeFaceIdx], renderer, cubeFace: face);
1982 }
1983 QRhiRenderPassDescriptor *renderPassDesc = nullptr;
1984 for (auto face : QSSGRenderTextureCubeFaces) {
1985 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
1986 face = pEntry->m_timeSliceFace;
1987
1988 QSSGRenderTextureCubeFace outFace = face;
1989 // Faces are swapped similarly to shadow maps due to differences in backends
1990 // Prefilter step handles correcting orientation differences in the final render
1991 if (swapYFaces) {
1992 if (outFace == QSSGRenderTextureCubeFace::PosY)
1993 outFace = QSSGRenderTextureCubeFace::NegY;
1994 else if (outFace == QSSGRenderTextureCubeFace::NegY)
1995 outFace = QSSGRenderTextureCubeFace::PosY;
1996 }
1997 QRhiTextureRenderTarget *rt = pEntry->m_rhiRenderTargets[quint8(outFace)];
1998 cb->beginPass(rt, colorClearValue: reflectionProbes[i]->clearColor, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags());
1999 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rt));
2000 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
2001
2002 if (renderSkybox && pEntry->m_skyBoxSrbs[quint8(face)]) {
2003 const bool isSkyBox = inData.layer.background == QSSGRenderLayer::Background::SkyBox;
2004 const auto &shaderPipeline = isSkyBox ? renderer.getRhiSkyBoxShader(tonemapMode: QSSGRenderLayer::TonemapMode::None, isRGBE: inData.layer.skyBoxIsRgbe8)
2005 : renderer.getRhiSkyBoxCubeShader();
2006 Q_ASSERT(shaderPipeline);
2007 ps->shaderPipeline = shaderPipeline.get();
2008 QRhiShaderResourceBindings *srb = pEntry->m_skyBoxSrbs[quint8(face)];
2009 if (!renderPassDesc)
2010 renderPassDesc = rt->newCompatibleRenderPassDescriptor();
2011 rt->setRenderPassDescriptor(renderPassDesc);
2012 isSkyBox ? renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps, srb, rpDesc: renderPassDesc, flags: {})
2013 : renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx, ps, srb, rpDesc: renderPassDesc, flags: {});
2014 }
2015
2016 bool needsSetViewport = true;
2017 for (const auto &handle : reflectionPassObjects)
2018 rhiRenderRenderable(rhiCtx, state: *ps, object&: *handle.obj, needsSetViewport: &needsSetViewport, cubeFace: face);
2019
2020 cb->endPass();
2021 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
2022 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_cube", 0, outFace));
2023
2024 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
2025 break;
2026 }
2027 if (renderPassDesc)
2028 renderPassDesc->deleteLater();
2029
2030 pEntry->renderMips(context: rhiCtx);
2031
2032 if (pEntry->m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
2033 pEntry->m_timeSliceFace = QSSGBaseTypeHelpers::next(face: pEntry->m_timeSliceFace); // Wraps
2034
2035 if (reflectionProbes[i]->refreshMode == QSSGRenderReflectionProbe::ReflectionRefreshMode::FirstFrame)
2036 pEntry->m_rendered = true;
2037
2038 reflectionProbes[i]->hasScheduledUpdate = false;
2039 pEntry->m_needsRender = false;
2040 }
2041}
2042
2043bool RenderHelpers::rhiPrepareAoTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex)
2044{
2045 QRhi *rhi = rhiCtx->rhi();
2046 bool needsBuild = false;
2047
2048 if (!renderableTex->texture) {
2049 // the ambient occlusion texture is always non-msaa, even if multisampling is used in the main pass
2050 renderableTex->texture = rhiCtx->rhi()->newTexture(format: QRhiTexture::RGBA8, pixelSize: size, sampleCount: 1, flags: QRhiTexture::RenderTarget);
2051 needsBuild = true;
2052 } else if (renderableTex->texture->pixelSize() != size) {
2053 renderableTex->texture->setPixelSize(size);
2054 needsBuild = true;
2055 }
2056
2057 if (needsBuild) {
2058 if (!renderableTex->texture->create()) {
2059 qWarning(msg: "Failed to build ambient occlusion texture (size %dx%d)", size.width(), size.height());
2060 renderableTex->reset();
2061 return false;
2062 }
2063 renderableTex->resetRenderTarget();
2064 renderableTex->rt = rhi->newTextureRenderTarget(desc: { renderableTex->texture });
2065 renderableTex->rt->setName(QByteArrayLiteral("Ambient occlusion"));
2066 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2067 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2068 if (!renderableTex->rt->create()) {
2069 qWarning(msg: "Failed to build render target for ambient occlusion texture");
2070 renderableTex->reset();
2071 return false;
2072 }
2073 }
2074
2075 return true;
2076}
2077
2078void RenderHelpers::rhiRenderAoTexture(QSSGRhiContext *rhiCtx,
2079 QSSGPassKey passKey,
2080 QSSGRenderer &renderer,
2081 QSSGRhiShaderPipeline &shaderPipeline,
2082 QSSGRhiGraphicsPipelineState &ps,
2083 const SSAOMapPass::AmbientOcclusion &ao,
2084 const QSSGRhiRenderableTexture &rhiAoTexture,
2085 const QSSGRhiRenderableTexture &rhiDepthTexture,
2086 const QSSGRenderCamera &camera)
2087{
2088 // no texelFetch in GLSL <= 120 and GLSL ES 100
2089 if (!rhiCtx->rhi()->isFeatureSupported(feature: QRhi::TexelFetch)) {
2090 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2091 // just clear and stop there
2092 cb->beginPass(rt: rhiAoTexture.rt, colorClearValue: Qt::white, depthStencilClearValue: { 1.0f, 0 });
2093 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiAoTexture.rt));
2094 cb->endPass();
2095 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
2096 return;
2097 }
2098
2099 ps.shaderPipeline = &shaderPipeline;
2100
2101 const float R2 = ao.aoDistance * ao.aoDistance * 0.16f;
2102 const QSize textureSize = rhiAoTexture.texture->pixelSize();
2103 const float rw = float(textureSize.width());
2104 const float rh = float(textureSize.height());
2105 const float fov = camera.verticalFov(aspectRatio: rw / rh);
2106 const float tanHalfFovY = tanf(x: 0.5f * fov * (rh / rw));
2107 const float invFocalLenX = tanHalfFovY * (rw / rh);
2108
2109 const QVector4D aoProps(ao.aoStrength * 0.01f, ao.aoDistance * 0.4f, ao.aoSoftness * 0.02f, ao.aoBias);
2110 const QVector4D aoProps2(float(ao.aoSamplerate), (ao.aoDither) ? 1.0f : 0.0f, 0.0f, 0.0f);
2111 const QVector4D aoScreenConst(1.0f / R2, rh / (2.0f * tanHalfFovY), 1.0f / rw, 1.0f / rh);
2112 const QVector4D uvToEyeConst(2.0f * invFocalLenX, -2.0f * tanHalfFovY, -invFocalLenX, tanHalfFovY);
2113 const QVector2D cameraProps(camera.clipNear, camera.clipFar);
2114
2115 // layout(std140, binding = 0) uniform buf {
2116 // vec4 aoProperties;
2117 // vec4 aoProperties2;
2118 // vec4 aoScreenConst;
2119 // vec4 uvToEyeConst;
2120 // vec2 cameraProperties;
2121
2122 const int UBUF_SIZE = 72;
2123 QSSGRhiDrawCallData &dcd(rhiCtx->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: nullptr, .entryIdx: 0 }));
2124 if (!dcd.ubuf) {
2125 dcd.ubuf = rhiCtx->rhi()->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: UBUF_SIZE);
2126 dcd.ubuf->create();
2127 }
2128
2129 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2130 memcpy(dest: ubufData, src: &aoProps, n: 16);
2131 memcpy(dest: ubufData + 16, src: &aoProps2, n: 16);
2132 memcpy(dest: ubufData + 32, src: &aoScreenConst, n: 16);
2133 memcpy(dest: ubufData + 48, src: &uvToEyeConst, n: 16);
2134 memcpy(dest: ubufData + 64, src: &cameraProps, n: 8);
2135 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2136
2137 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, .mipmap: QRhiSampler::None,
2138 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
2139 QSSGRhiShaderResourceBindingList bindings;
2140 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
2141 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: rhiDepthTexture.texture, sampler);
2142 QRhiShaderResourceBindings *srb = rhiCtx->srb(bindings);
2143
2144 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
2145 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: rhiAoTexture.rt, flags: {});
2146}
2147
2148bool RenderHelpers::rhiPrepareScreenTexture(QSSGRhiContext *rhiCtx, const QSize &size, bool wantsMips, QSSGRhiRenderableTexture *renderableTex)
2149{
2150 QRhi *rhi = rhiCtx->rhi();
2151 bool needsBuild = false;
2152 QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
2153 if (wantsMips)
2154 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
2155
2156 if (!renderableTex->texture) {
2157 // always non-msaa, even if multisampling is used in the main pass
2158 renderableTex->texture = rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: size, sampleCount: 1, flags);
2159 needsBuild = true;
2160 } else if (renderableTex->texture->pixelSize() != size) {
2161 renderableTex->texture->setPixelSize(size);
2162 needsBuild = true;
2163 }
2164
2165 if (!renderableTex->depthStencil) {
2166 renderableTex->depthStencil = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: size);
2167 needsBuild = true;
2168 } else if (renderableTex->depthStencil->pixelSize() != size) {
2169 renderableTex->depthStencil->setPixelSize(size);
2170 needsBuild = true;
2171 }
2172
2173 if (needsBuild) {
2174 if (!renderableTex->texture->create()) {
2175 qWarning(msg: "Failed to build screen texture (size %dx%d)", size.width(), size.height());
2176 renderableTex->reset();
2177 return false;
2178 }
2179 if (!renderableTex->depthStencil->create()) {
2180 qWarning(msg: "Failed to build depth-stencil buffer for screen texture (size %dx%d)",
2181 size.width(), size.height());
2182 renderableTex->reset();
2183 return false;
2184 }
2185 renderableTex->resetRenderTarget();
2186 QRhiTextureRenderTargetDescription desc;
2187 desc.setColorAttachments({ QRhiColorAttachment(renderableTex->texture) });
2188 desc.setDepthStencilBuffer(renderableTex->depthStencil);
2189 renderableTex->rt = rhi->newTextureRenderTarget(desc);
2190 renderableTex->rt->setName(QByteArrayLiteral("Screen texture"));
2191 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2192 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2193 if (!renderableTex->rt->create()) {
2194 qWarning(msg: "Failed to build render target for screen texture");
2195 renderableTex->reset();
2196 return false;
2197 }
2198 }
2199
2200 return true;
2201}
2202
2203void RenderHelpers::rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCamera &inCamera, QSSGRenderer &renderer)
2204{
2205 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2206 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare grid"));
2207
2208 QSSGRhiShaderResourceBindingList bindings;
2209
2210 int uniformBinding = 0;
2211 const int ubufSize = 64 * 2 * sizeof(float) + 4 * sizeof(float) + 4 * sizeof(quint32); // 2x mat4 + 4x float + 1x bool
2212
2213 QSSGRhiDrawCallData &dcd(rhiCtx->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: nullptr, .entryIdx: 0 })); // Change to Grid?
2214
2215 QRhi *rhi = rhiCtx->rhi();
2216 if (!dcd.ubuf) {
2217 dcd.ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
2218 dcd.ubuf->create();
2219 }
2220
2221 // Param
2222 const float nearF = inCamera.clipNear;
2223 const float farF = inCamera.clipFar;
2224 const float scale = layer.gridScale;
2225 const quint32 gridFlags = layer.gridFlags;
2226
2227 const float yFactor = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2228
2229 QMatrix4x4 viewProj(Qt::Uninitialized);
2230 inCamera.calculateViewProjectionMatrix(outMatrix&: viewProj);
2231 QMatrix4x4 invViewProj = viewProj.inverted();
2232
2233 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2234 memcpy(dest: ubufData + 64 * 0, src: viewProj.constData(), n: 64);
2235 memcpy(dest: ubufData + 64 * 1, src: invViewProj.constData(), n: 64);
2236 memcpy(dest: ubufData + 64 * 2 + 0, src: &nearF, n: 4);
2237 memcpy(dest: ubufData + 64 * 2 + 4 * 1, src: &farF, n: 4);
2238 memcpy(dest: ubufData + 64 * 2 + 4 * 2, src: &scale, n: 4);
2239 memcpy(dest: ubufData + 64 * 2 + 4 * 3, src: &yFactor, n: 4);
2240 memcpy(dest: ubufData + 64 * 2 + 4 * 4, src: &gridFlags, n: 4);
2241 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2242
2243 bindings.addUniformBuffer(binding: uniformBinding, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
2244
2245 layer.gridSrb = rhiCtx->srb(bindings);
2246 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
2247
2248 cb->debugMarkEnd();
2249}
2250
2251namespace {
2252void rhiPrepareSkyBox_helper(QSSGRhiContext *rhiCtx,
2253 QSSGPassKey passKey,
2254 QSSGRenderLayer &layer,
2255 QSSGRenderCamera &inCamera,
2256 QSSGRenderer &renderer,
2257 QSSGReflectionMapEntry *entry = nullptr,
2258 QSSGRenderTextureCubeFace cubeFace = QSSGRenderTextureCubeFaceNone)
2259{
2260 const bool cubeMapMode = layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap;
2261 const QSSGRenderImageTexture lightProbeTexture =
2262 cubeMapMode ? renderer.contextInterface()->bufferManager()->loadRenderImage(image: layer.skyBoxCubeMap, inMipMode: QSSGBufferManager::MipModeDisable)
2263 : renderer.contextInterface()->bufferManager()->loadRenderImage(image: layer.lightProbe, inMipMode: QSSGBufferManager::MipModeBsdf);
2264 const bool hasValidTexture = lightProbeTexture.m_texture != nullptr;
2265 if (hasValidTexture) {
2266 if (cubeFace == QSSGRenderTextureCubeFaceNone)
2267 layer.skyBoxIsRgbe8 = lightProbeTexture.m_flags.isRgbe8();
2268
2269 QSSGRhiShaderResourceBindingList bindings;
2270
2271 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear,
2272 .magFilter: QRhiSampler::Linear,
2273 .mipmap: cubeMapMode ? QRhiSampler::None : QRhiSampler::Linear, // cube map doesn't have mipmaps
2274 .hTiling: QRhiSampler::Repeat,
2275 .vTiling: QRhiSampler::ClampToEdge,
2276 .zTiling: QRhiSampler::Repeat });
2277 int samplerBinding = 1; //the shader code is hand-written, so we don't need to look that up
2278 const int ubufSize = 2 * 4 * 3 * sizeof(float) + 2 * 4 * 4 * sizeof(float) + 2 * sizeof(float); // 2x mat3 + 2x mat4 + 2 floats
2279 bindings.addTexture(binding: samplerBinding,
2280 stage: QRhiShaderResourceBinding::FragmentStage,
2281 tex: lightProbeTexture.m_texture, sampler);
2282
2283 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
2284 const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone) * cubeFaceIdx;
2285 QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: passKey, .model: nullptr, .entry: entry, .entryIdx: entryIdx });
2286
2287 QRhi *rhi = rhiCtx->rhi();
2288 if (!dcd.ubuf) {
2289 dcd.ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
2290 dcd.ubuf->create();
2291 }
2292
2293 const QMatrix4x4 &inverseProjection = inCamera.projection.inverted();
2294 const QMatrix4x4 &viewMatrix = inCamera.globalTransform;
2295 QMatrix4x4 viewProjection(Qt::Uninitialized); // For cube mode
2296 inCamera.calculateViewProjectionWithoutTranslation(near: 0.1f, far: 5.0f, outMatrix&: viewProjection);
2297
2298 float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
2299 const float exposure = layer.probeExposure;
2300 // orientation
2301 const QMatrix3x3 &rotationMatrix(layer.probeOrientation);
2302 const float blurAmount = layer.skyboxBlurAmount;
2303 const float maxMipLevel = float(lightProbeTexture.m_mipmapCount - 2);
2304
2305 const QVector4D skyboxProperties = {
2306 adjustY,
2307 exposure,
2308 blurAmount,
2309 maxMipLevel
2310 };
2311
2312 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2313 memcpy(dest: ubufData, src: viewMatrix.constData(), n: 44);
2314 memcpy(dest: ubufData + 48, src: inverseProjection.constData(), n: 64);
2315 memcpy(dest: ubufData + 112, src: rotationMatrix.constData(), n: 12);
2316 memcpy(dest: ubufData + 128, src: (char *)rotationMatrix.constData() + 12, n: 12);
2317 memcpy(dest: ubufData + 144, src: (char *)rotationMatrix.constData() + 24, n: 12);
2318 memcpy(dest: ubufData + 160, src: &skyboxProperties, n: 16);
2319 memcpy(dest: ubufData + 176, src: viewProjection.constData(), n: 64); //###
2320 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2321
2322 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd.ubuf);
2323
2324 if (cubeFace != QSSGRenderTextureCubeFaceNone) {
2325 const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(face: cubeFace);
2326 entry->m_skyBoxSrbs[cubeFaceIdx] = rhiCtx->srb(bindings);
2327 } else {
2328 layer.skyBoxSrb = rhiCtx->srb(bindings);
2329 }
2330
2331 if (cubeMapMode)
2332 renderer.rhiCubeRenderer()->prepareCube(rhiCtx, maybeRub: nullptr);
2333 else
2334 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
2335 }
2336}
2337} // namespace
2338
2339void RenderHelpers::rhiPrepareSkyBox(QSSGRhiContext *rhiCtx,
2340 QSSGPassKey passKey,
2341 QSSGRenderLayer &layer,
2342 QSSGRenderCamera &inCamera,
2343 QSSGRenderer &renderer)
2344{
2345 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2346 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox"));
2347
2348 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, inCamera, renderer);
2349
2350 cb->debugMarkEnd();
2351}
2352
2353void RenderHelpers::rhiPrepareSkyBoxForReflectionMap(QSSGRhiContext *rhiCtx,
2354 QSSGPassKey passKey,
2355 QSSGRenderLayer &layer,
2356 QSSGRenderCamera &inCamera,
2357 QSSGRenderer &renderer,
2358 QSSGReflectionMapEntry *entry,
2359 QSSGRenderTextureCubeFace cubeFace)
2360{
2361 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2362 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare skybox for reflection cube map"));
2363
2364 rhiPrepareSkyBox_helper(rhiCtx, passKey, layer, inCamera, renderer, entry, cubeFace);
2365
2366 cb->debugMarkEnd();
2367}
2368
2369bool RenderHelpers::rhiPrepareDepthPass(QSSGRhiContext *rhiCtx,
2370 QSSGPassKey passKey,
2371 const QSSGRhiGraphicsPipelineState &basePipelineState,
2372 QRhiRenderPassDescriptor *rpDesc,
2373 QSSGLayerRenderData &inData,
2374 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
2375 const QVector<QSSGRenderableObjectHandle> &sortedTransparentObjects,
2376 int samples)
2377{
2378 static const auto rhiPrepareDepthPassForObject = [](QSSGRhiContext *rhiCtx,
2379 QSSGPassKey passKey,
2380 QSSGLayerRenderData &inData,
2381 QSSGRenderableObject *obj,
2382 QRhiRenderPassDescriptor *rpDesc,
2383 QSSGRhiGraphicsPipelineState *ps) {
2384 QSSGRhiShaderPipelinePtr shaderPipeline;
2385
2386 const bool isOpaqueDepthPrePass = obj->depthWriteMode == QSSGDepthDrawMode::OpaquePrePass;
2387 QSSGShaderFeatures featureSet;
2388 featureSet.set(feature: QSSGShaderFeatures::Feature::DepthPass, val: true);
2389 if (isOpaqueDepthPrePass)
2390 featureSet.set(feature: QSSGShaderFeatures::Feature::OpaqueDepthPrePass, val: true);
2391
2392 QSSGRhiDrawCallData *dcd = nullptr;
2393 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2394 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2395 const void *modelNode = &subsetRenderable.modelContext.model;
2396 dcd = &rhiCtx->drawCallData(key: { .cid: passKey, .model: modelNode, .entry: &subsetRenderable.material, .entryIdx: 0 });
2397 }
2398
2399 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset) {
2400 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2401 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(subsetRenderable.getMaterial());
2402 ps->cullMode = QSSGRhiGraphicsPipelineState::toCullMode(cullFaceMode: material.cullMode);
2403
2404 shaderPipeline = shadersForDefaultMaterial(ps, subsetRenderable, featureSet);
2405 if (shaderPipeline) {
2406 shaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd->ubuf);
2407 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2408 updateUniformsForDefaultMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, subsetRenderable, camera: *inData.camera, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
2409 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2410 } else {
2411 return false;
2412 }
2413 } else if (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2414 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2415
2416 const auto &customMaterial = static_cast<const QSSGRenderCustomMaterial &>(subsetRenderable.getMaterial());
2417
2418 ps->cullMode = QSSGRhiGraphicsPipelineState::toCullMode(cullFaceMode: customMaterial.m_cullMode);
2419
2420 QSSGCustomMaterialSystem &customMaterialSystem(*subsetRenderable.renderer->contextInterface()->customMaterialSystem().get());
2421 shaderPipeline = customMaterialSystem.shadersForCustomMaterial(ps, material: customMaterial, renderable&: subsetRenderable, featureSet);
2422
2423 if (shaderPipeline) {
2424 shaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd->ubuf);
2425 char *ubufData = dcd->ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
2426 customMaterialSystem.updateUniformsForCustomMaterial(shaderPipeline&: *shaderPipeline, rhiCtx, inData, ubufData, ps, material: customMaterial, renderable&: subsetRenderable,
2427 camera: *inData.camera, depthAdjust: nullptr, alteredModelViewProjection: nullptr);
2428 dcd->ubuf->endFullDynamicBufferUpdateForCurrentFrame();
2429 } else {
2430 return false;
2431 }
2432 }
2433
2434 // the rest is common, only relying on QSSGSubsetRenderableBase, not the subclasses
2435 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2436 QSSGSubsetRenderable &subsetRenderable(static_cast<QSSGSubsetRenderable &>(*obj));
2437 ps->ia = subsetRenderable.subset.rhi.ia;
2438
2439 int instanceBufferBinding = setupInstancing(renderable: &subsetRenderable, ps, rhiCtx, cameraDirection: inData.cameraData->direction, cameraPosition: inData.cameraData->position);
2440 ps->ia.bakeVertexInputLocations(shaders: *shaderPipeline, instanceBufferBinding);
2441
2442 QSSGRhiShaderResourceBindingList bindings;
2443 bindings.addUniformBuffer(binding: 0, stage: RENDERER_VISIBILITY_ALL, buf: dcd->ubuf);
2444
2445 // Depth and SSAO textures, in case a custom material's shader code does something with them.
2446 addDepthTextureBindings(rhiCtx, shaderPipeline: shaderPipeline.get(), bindings);
2447
2448 if (isOpaqueDepthPrePass) {
2449 addOpaqueDepthPrePassBindings(rhiCtx,
2450 shaderPipeline: shaderPipeline.get(),
2451 renderableImage: subsetRenderable.firstImage,
2452 bindings,
2453 isCustomMaterialMeshSubset: (obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset));
2454 }
2455
2456 QRhiShaderResourceBindings *srb = rhiCtx->srb(bindings);
2457
2458 subsetRenderable.rhiRenderData.depthPrePass.pipeline = rhiCtx->pipeline(key: QSSGGraphicsPipelineStateKey::create(state: *ps, rpDesc, srb),
2459 rpDesc,
2460 srb);
2461 subsetRenderable.rhiRenderData.depthPrePass.srb = srb;
2462 }
2463
2464 return true;
2465 };
2466
2467 // Phase 1 (prepare) for the Z prepass or the depth texture generation.
2468 // These renders opaque (Z prepass), or opaque and transparent (depth
2469 // texture), objects with depth test/write enabled, and color write
2470 // disabled, using a very simple set of shaders.
2471
2472 QSSGRhiGraphicsPipelineState ps = basePipelineState; // viewport and others are filled out already
2473 // We took a copy of the pipeline state since we do not want to conflict
2474 // with what rhiPrepare() collects for its own use. So here just change
2475 // whatever we need.
2476
2477 ps.samples = samples;
2478 ps.depthTestEnable = true;
2479 ps.depthWriteEnable = true;
2480 ps.targetBlend.colorWrite = {};
2481
2482 for (const QSSGRenderableObjectHandle &handle : sortedOpaqueObjects) {
2483 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2484 return false;
2485 }
2486
2487 for (const QSSGRenderableObjectHandle &handle : sortedTransparentObjects) {
2488 if (!rhiPrepareDepthPassForObject(rhiCtx, passKey, inData, handle.obj, rpDesc, &ps))
2489 return false;
2490 }
2491
2492 return true;
2493}
2494
2495void RenderHelpers::rhiRenderDepthPass(QSSGRhiContext *rhiCtx,
2496 const QSSGRhiGraphicsPipelineState &pipelineState,
2497 const QVector<QSSGRenderableObjectHandle> &sortedOpaqueObjects,
2498 const QVector<QSSGRenderableObjectHandle> &sortedTransparentObjects,
2499 bool *needsSetViewport)
2500{
2501 static const auto rhiRenderDepthPassForImp = [](QSSGRhiContext *rhiCtx,
2502 const QSSGRhiGraphicsPipelineState &pipelineState,
2503 const QVector<QSSGRenderableObjectHandle> &objects,
2504 bool *needsSetViewport) {
2505 for (const auto &oh : objects) {
2506 QSSGRenderableObject *obj = oh.obj;
2507
2508 // casts to SubsetRenderableBase so it works for both default and custom materials
2509 if (obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset || obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
2510 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2511 QSSGSubsetRenderable *subsetRenderable(static_cast<QSSGSubsetRenderable *>(obj));
2512
2513 QRhiBuffer *vertexBuffer = subsetRenderable->subset.rhi.vertexBuffer->buffer();
2514 QRhiBuffer *indexBuffer = subsetRenderable->subset.rhi.indexBuffer
2515 ? subsetRenderable->subset.rhi.indexBuffer->buffer()
2516 : nullptr;
2517
2518 QRhiGraphicsPipeline *ps = subsetRenderable->rhiRenderData.depthPrePass.pipeline;
2519 if (!ps)
2520 return;
2521
2522 QRhiShaderResourceBindings *srb = subsetRenderable->rhiRenderData.depthPrePass.srb;
2523 if (!srb)
2524 return;
2525
2526 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
2527 cb->setGraphicsPipeline(ps);
2528 cb->setShaderResources(srb);
2529
2530 if (*needsSetViewport) {
2531 cb->setViewport(pipelineState.viewport);
2532 *needsSetViewport = false;
2533 }
2534
2535 QRhiCommandBuffer::VertexInput vertexBuffers[2];
2536 int vertexBufferCount = 1;
2537 vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
2538 quint32 instances = 1;
2539 if (subsetRenderable->modelContext.model.instancing()) {
2540 instances = subsetRenderable->modelContext.model.instanceCount();
2541 vertexBuffers[1] = QRhiCommandBuffer::VertexInput(subsetRenderable->instanceBuffer, 0);
2542 vertexBufferCount = 2;
2543 }
2544
2545 if (indexBuffer) {
2546 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers, indexBuf: indexBuffer, indexOffset: 0, indexFormat: subsetRenderable->subset.rhi.indexBuffer->indexFormat());
2547 cb->drawIndexed(indexCount: subsetRenderable->subset.count, instanceCount: instances, firstIndex: subsetRenderable->subset.offset);
2548 QSSGRHICTX_STAT(rhiCtx, drawIndexed(subsetRenderable->subset.count, instances));
2549 } else {
2550 cb->setVertexInput(startBinding: 0, bindingCount: vertexBufferCount, bindings: vertexBuffers);
2551 cb->draw(vertexCount: subsetRenderable->subset.count, instanceCount: instances, firstVertex: subsetRenderable->subset.offset);
2552 QSSGRHICTX_STAT(rhiCtx, draw(subsetRenderable->subset.count, instances));
2553 }
2554 Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (subsetRenderable->subset.count | quint64(instances) << 32),
2555 QVector<int>({subsetRenderable->modelContext.model.profilingId,
2556 subsetRenderable->material.profilingId}));
2557 }
2558 }
2559 };
2560
2561 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedOpaqueObjects, needsSetViewport);
2562 rhiRenderDepthPassForImp(rhiCtx, pipelineState, sortedTransparentObjects, needsSetViewport);
2563}
2564
2565bool RenderHelpers::rhiPrepareDepthTexture(QSSGRhiContext *rhiCtx, const QSize &size, QSSGRhiRenderableTexture *renderableTex)
2566{
2567 QRhi *rhi = rhiCtx->rhi();
2568 bool needsBuild = false;
2569
2570 if (!renderableTex->texture) {
2571 QRhiTexture::Format format = QRhiTexture::D32F;
2572 if (!rhi->isTextureFormatSupported(format))
2573 format = QRhiTexture::D16;
2574 if (!rhi->isTextureFormatSupported(format))
2575 qWarning(msg: "Depth texture not supported");
2576 // the depth texture is always non-msaa, even if multisampling is used in the main pass
2577 renderableTex->texture = rhiCtx->rhi()->newTexture(format, pixelSize: size, sampleCount: 1, flags: QRhiTexture::RenderTarget);
2578 needsBuild = true;
2579 } else if (renderableTex->texture->pixelSize() != size) {
2580 renderableTex->texture->setPixelSize(size);
2581 needsBuild = true;
2582 }
2583
2584 if (needsBuild) {
2585 if (!renderableTex->texture->create()) {
2586 qWarning(msg: "Failed to build depth texture (size %dx%d, format %d)",
2587 size.width(), size.height(), int(renderableTex->texture->format()));
2588 renderableTex->reset();
2589 return false;
2590 }
2591 renderableTex->resetRenderTarget();
2592 QRhiTextureRenderTargetDescription rtDesc;
2593 rtDesc.setDepthTexture(renderableTex->texture);
2594 renderableTex->rt = rhi->newTextureRenderTarget(desc: rtDesc);
2595 renderableTex->rt->setName(QByteArrayLiteral("Depth texture"));
2596 renderableTex->rpDesc = renderableTex->rt->newCompatibleRenderPassDescriptor();
2597 renderableTex->rt->setRenderPassDescriptor(renderableTex->rpDesc);
2598 if (!renderableTex->rt->create()) {
2599 qWarning(msg: "Failed to build render target for depth texture");
2600 renderableTex->reset();
2601 return false;
2602 }
2603 }
2604
2605 return true;
2606}
2607
2608QT_END_NAMESPACE
2609

source code of qtquick3d/src/runtimerender/rendererimpl/qssgrenderer.cpp