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 | |
36 | QT_BEGIN_NAMESPACE |
37 | |
38 | struct QSSGRenderableImage; |
39 | struct QSSGSubsetRenderable; |
40 | |
41 | static constexpr float QSSG_PI = float(M_PI); |
42 | static constexpr float QSSG_HALFPI = float(M_PI_2); |
43 | |
44 | static const QRhiShaderResourceBinding::StageFlags RENDERER_VISIBILITY_ALL = |
45 | QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; |
46 | |
47 | static 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 | |
58 | static 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 | |
69 | static 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 | |
117 | void QSSGRenderer::releaseCachedResources() |
118 | { |
119 | delete m_rhiQuadRenderer; |
120 | m_rhiQuadRenderer = nullptr; |
121 | delete m_rhiCubeRenderer; |
122 | m_rhiCubeRenderer = nullptr; |
123 | } |
124 | |
125 | QSSGRenderer::QSSGRenderer() = default; |
126 | |
127 | QSSGRenderer::~QSSGRenderer() |
128 | { |
129 | m_contextInterface = nullptr; |
130 | releaseCachedResources(); |
131 | } |
132 | |
133 | void QSSGRenderer::setRenderContextInterface(QSSGRenderContextInterface *ctx) |
134 | { |
135 | m_contextInterface = ctx; |
136 | } |
137 | |
138 | bool 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. |
150 | void 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. |
178 | void 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 | |
194 | template<typename Container> |
195 | static 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 | |
221 | void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources) |
222 | { |
223 | cleanupResourcesImpl(rci: *m_contextInterface, resources); |
224 | resources.clear(); |
225 | } |
226 | |
227 | void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources) |
228 | { |
229 | cleanupResourcesImpl(rci: *m_contextInterface, resources); |
230 | resources.clear(); |
231 | } |
232 | |
233 | QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer) |
234 | { |
235 | if (layer.renderData == nullptr) |
236 | layer.renderData = new QSSGLayerRenderData(layer, *this); |
237 | |
238 | return layer.renderData; |
239 | } |
240 | |
241 | void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material) |
242 | { |
243 | m_materialClearDirty.insert(value: material); |
244 | } |
245 | |
246 | static QByteArray logPrefix() { return QByteArrayLiteral("mesh default material pipeline-- " ); } |
247 | |
248 | |
249 | QSSGRhiShaderPipelinePtr 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 | |
305 | QSSGRhiShaderPipelinePtr 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 | |
314 | void QSSGRenderer::beginFrame(QSSGRenderLayer *layer) |
315 | { |
316 | QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(layer)); |
317 | } |
318 | |
319 | void 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 | |
336 | QSSGRenderer::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 | |
350 | QSSGRenderPickResult 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 | |
381 | bool QSSGRenderer::isGlobalPickingEnabled() const |
382 | { |
383 | return m_globalPickingEnabled; |
384 | } |
385 | |
386 | void QSSGRenderer::setGlobalPickingEnabled(bool isEnabled) |
387 | { |
388 | m_globalPickingEnabled = isEnabled; |
389 | } |
390 | |
391 | QSSGRhiQuadRenderer *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 | |
402 | QSSGRhiCubeRenderer *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 | |
414 | void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer) |
415 | { |
416 | m_currentLayer = &inLayer; |
417 | } |
418 | void QSSGRenderer::endLayerRender() |
419 | { |
420 | m_currentLayer = nullptr; |
421 | } |
422 | |
423 | using RenderableList = QVarLengthArray<const QSSGRenderNode *>; |
424 | static 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 | |
433 | void 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 | |
450 | void 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 | |
552 | void 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 | |
579 | QSSGRhiShaderPipelinePtr 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 | |
631 | QSSGLayerGlobalRenderProperties 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 | |
668 | void 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 | |
682 | std::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 | |
703 | static 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 | |
712 | static 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 | |
724 | static 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 | |
738 | static 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 | |
768 | static 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 | |
871 | static 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 | |
965 | static 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 | |
1025 | static 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 | |
1042 | static 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 | |
1076 | static 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 | |
1102 | static 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 | |
1225 | static 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 | |
1251 | void 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 | |
1567 | void 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 | |
1653 | void 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 | |
1932 | void 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 | |
2043 | bool 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 | |
2078 | void 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 | |
2148 | bool 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 | |
2203 | void 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 | |
2251 | namespace { |
2252 | void 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 | |
2339 | void 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 | |
2353 | void 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 | |
2369 | bool 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 | |
2495 | void 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 | |
2565 | bool 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 | |
2608 | QT_END_NAMESPACE |
2609 | |