Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrenderer.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
5
6
8
9#include <QtQuick3DRuntimeRender/private/qssgrenderitem2d_p.h>
10#include "../qssgrendercontextcore.h"
11#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
14#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
15#include "../qssgrendercontextcore.h"
16#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrendershadercodegenerator_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h>
20#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
21#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
22#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
23#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
24#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
25#include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h>
26#include "../qssgshadermapkey_p.h"
27#include "../qssgrenderpickresult_p.h"
28#include "../graphobjects/qssgrenderroot_p.h"
29
30#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
31#include <QtQuick3DUtils/private/qssgdataref_p.h>
32#include <QtQuick3DUtils/private/qssgutils_p.h>
33#include <QtQuick3DUtils/private/qssgassert_p.h>
34#include <QtQuick3DUtils/private/qssgfrustum_p.h>
35#include <qtquick3d_tracepoints_p.h>
36
37#include <QtQuick/private/qsgcontext_p.h>
38#include <QtQuick/private/qsgrenderer_p.h>
39
40#include <QtCore/QMutexLocker>
41#include <QtCore/QBitArray>
42
43#include <cstdlib>
44#include <algorithm>
45#include <limits>
46
47/*
48 Rendering is done is several steps, these are:
49
50 1. \l{QSSGRenderer::beginFrame(){beginFrame()} - set's up the renderer to start a new frame.
51
52 2. Now that the renderer is reset, values for the \l{QSSGRenderer::setViewport}{viewport}, \l{QSSGRenderer::setDpr}{dpr},
53 \l{QSSGRenderer::setScissorRect}{scissorRect} etc. should be updated.
54
55 3. \l{QSSGRenderer::prepareLayerForRender()} - At this stage the scene tree will be traversed
56 and state for the renderer needed to render gets collected. This includes, but is not limited to,
57 calculating global transforms, loading of meshes, preparing materials and setting up the rendering
58 steps needed for the frame (opaque and transparent pass etc.)
59 If the there are custom \l{QQuick3DRenderExtension}{render extensions} added to to \l{View3D::extensions}{View3D}
60 then they will get their first chance to modify or react to the collected data here.
61 If the users have implemented the virtual function \l{QSSGRenderExtension::prepareData()}{prepareData} it will be
62 called after all active nodes have been collected and had their global data updated, but before any mesh or material
63 has been loaded.
64
65 4. \l{QSSGRenderer::rhiPrepare()} - Starts rendering necessary sub-scenes and prepare resources.
66 Sub-scenes, or sub-passes that are to be done in full, will be done at this stage.
67
68 5. \l{QSSGRenderer::rhiRender()} - Renders the scene to the main target.
69
70 6. \l{QSSGRenderer::endFrame()} - Marks the frame as done and cleans-up dirty states and
71 uneeded resources.
72*/
73
74QT_BEGIN_NAMESPACE
75
76struct QSSGRenderableImage;
77class QSSGSubsetRenderable;
78
79void QSSGRenderer::releaseCachedResources()
80{
81 m_rhiQuadRenderer.reset();
82 m_rhiCubeRenderer.reset();
83}
84
85void QSSGRenderer::registerItem2DData(QSSGRenderItem2DData &data)
86{
87 // Check if data is already in the m_item2DDatas list, if not insert it.
88 for (const auto *item2DData : m_item2DDatas) {
89 if (item2DData == &data)
90 return;
91 }
92
93 m_item2DDatas.push_back(&data);
94}
95
96void QSSGRenderer::unregisterItem2DData(QSSGRenderItem2DData &data)
97{
98 const auto foundIt = std::find(m_item2DDatas.begin(), m_item2DDatas.end(), &data);
99 if (foundIt != m_item2DDatas.end())
100 m_item2DDatas.erase(foundIt);
101}
102
103void QSSGRenderer::releaseItem2DData(const QSSGRenderItem2D &item2D)
104{
105 for (auto *item2DData : m_item2DDatas)
106 item2DData->releaseRenderData(item2D);
107}
108
109QSSGRenderer::QSSGRenderer() = default;
110
111QSSGRenderer::~QSSGRenderer()
112{
113 m_contextInterface = nullptr;
114 releaseCachedResources();
115}
116
117void QSSGRenderer::cleanupUnreferencedBuffers(QSSGRenderLayer *inLayer)
118{
119 // Now check for unreferenced buffers and release them if necessary
120 m_contextInterface->bufferManager()->cleanupUnreferencedBuffers(m_frameCount, inLayer);
121}
122
123void QSSGRenderer::resetResourceCounters(QSSGRenderLayer *inLayer)
124{
125 m_contextInterface->bufferManager()->resetUsageCounters(m_frameCount, inLayer);
126}
127
128bool QSSGRenderer::prepareLayerForRender(QSSGRenderLayer &inLayer)
129{
130 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(inLayer);
131 Q_ASSERT(theRenderData);
132
133 // Need to check if the world root node is dirty and if we need to trigger
134 // a reindex of the world root node.
135 Q_ASSERT(inLayer.rootNode);
136 if (inLayer.rootNode->isDirty(QSSGRenderRoot::DirtyFlag::TreeDirty))
137 inLayer.rootNode->reindex(); // Clears TreeDirty flag
138
139 beginLayerRender(*theRenderData);
140 theRenderData->resetForFrame();
141 theRenderData->prepareForRender();
142 endLayerRender();
143 return theRenderData->layerPrepResult.getFlags().wasDirty();
144}
145
146// Phase 1: prepare. Called when the renderpass is not yet started on the command buffer.
147void QSSGRenderer::rhiPrepare(QSSGRenderLayer &inLayer)
148{
149 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(inLayer);
150 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
151
152 const auto layerPrepResult = theRenderData->layerPrepResult;
153 if (layerPrepResult.isLayerVisible()) {
154 ///
155 QSSGRhiContext *rhiCtx = contextInterface()->rhiContext().get();
156 QSSG_ASSERT(rhiCtx->isValid() && rhiCtx->rhi()->isRecordingFrame(), return);
157 beginLayerRender(*theRenderData);
158 theRenderData->maybeProcessLightmapBaking();
159 // Process active passes. "PreMain" passes are individual passes
160 // that does can and should be done in the rhi prepare phase.
161 // It is assumed that passes are sorted in the list with regards to
162 // execution order.
163 const auto &activePasses = theRenderData->activePasses;
164 for (const auto &pass : activePasses) {
165 pass->renderPrep(*this, *theRenderData);
166 if (pass->passType() == QSSGRenderPass::Type::Standalone)
167 pass->renderPass(*this);
168 }
169
170 endLayerRender();
171 }
172}
173
174// Phase 2: render. Called within an active renderpass on the command buffer.
175void QSSGRenderer::rhiRender(QSSGRenderLayer &inLayer)
176{
177 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(inLayer);
178 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
179 if (theRenderData->layerPrepResult.isLayerVisible()) {
180 beginLayerRender(*theRenderData);
181 const auto &activePasses = theRenderData->activePasses;
182 for (const auto &pass : activePasses) {
183 if (pass->passType() == QSSGRenderPass::Type::Main || pass->passType() == QSSGRenderPass::Type::Extension)
184 pass->renderPass(*this);
185 }
186 endLayerRender();
187 }
188}
189
190template<typename Container>
191static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
192{
193 const auto &rhiCtx = rci.rhiContext();
194 if (!rhiCtx->isValid())
195 return;
196
197 const auto &bufferManager = rci.bufferManager();
198
199 for (const auto &resource : resources) {
200 if (resource->type == QSSGRenderGraphObject::Type::Geometry) {
201 auto geometry = static_cast<QSSGRenderGeometry*>(resource);
202 bufferManager->releaseGeometry(geometry);
203 } else if (resource->type == QSSGRenderGraphObject::Type::Model) {
204 auto model = static_cast<QSSGRenderModel*>(resource);
205 QSSGRhiContextPrivate::get(rhiCtx.get())->cleanupDrawCallData(model);
206 delete model->particleBuffer;
207 } else if (resource->type == QSSGRenderGraphObject::Type::TextureData || resource->type == QSSGRenderGraphObject::Type::Skin) {
208 static_assert(std::is_base_of_v<QSSGRenderTextureData, QSSGRenderSkin>, "QSSGRenderSkin is expected to be a QSSGRenderTextureData type!");
209 auto textureData = static_cast<QSSGRenderTextureData *>(resource);
210 bufferManager->releaseTextureData(textureData);
211 } else if (resource->type == QSSGRenderGraphObject::Type::RenderExtension) {
212 auto *rext = static_cast<QSSGRenderExtension *>(resource);
213 bufferManager->releaseExtensionResult(*rext);
214 } else if (resource->type == QSSGRenderGraphObject::Type::ModelInstance) {
215 auto *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
216 auto *table = static_cast<QSSGRenderInstanceTable *>(resource);
217 rhiCtxD->releaseInstanceBuffer(table);
218 } else if (resource->type == QSSGRenderGraphObject::Type::Item2D) {
219 auto *item2D = static_cast<QSSGRenderItem2D *>(resource);
220 rci.renderer()->releaseItem2DData(*item2D);
221 } else if (resource->type == QSSGRenderGraphObject::Type::RenderPass) {
222 auto *userPass = static_cast<QSSGRenderUserPass *>(resource);
223 bufferManager->releaseUserRenderPass(*userPass);
224 }
225
226 // ### There might be more types that need to be supported
227
228 delete resource;
229 }
230}
231
232void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources)
233{
234 cleanupResourcesImpl(*m_contextInterface, resources);
235 resources.clear();
236}
237
238void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources)
239{
240 cleanupResourcesImpl(*m_contextInterface, resources);
241 resources.clear();
242}
243
244QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer)
245{
246 if (layer.renderData == nullptr)
247 layer.renderData = new QSSGLayerRenderData(layer, *this);
248
249 return layer.renderData;
250}
251
252void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material)
253{
254 m_materialClearDirty.insert(material);
255}
256
257static QByteArray rendererLogPrefix() { return QByteArrayLiteral("mesh default material pipeline-- "); }
258
259
260QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipelineImpl(QSSGSubsetRenderable &renderable,
261 QSSGShaderLibraryManager &shaderLibraryManager,
262 QSSGShaderCache &shaderCache,
263 QSSGProgramGenerator &shaderProgramGenerator,
264 const QSSGShaderDefaultMaterialKeyProperties &shaderKeyProperties,
265 const QSSGShaderFeatures &featureSet,
266 const QSSGUserShaderAugmentation &shaderAugmentation,
267 QByteArray &shaderString)
268{
269 shaderString = rendererLogPrefix();
270 QSSGShaderDefaultMaterialKey theKey(renderable.shaderDescription);
271
272 // This is not a cheap operation. This function assumes that it will not be
273 // hit for every material for every model in every frame (except of course
274 // for materials that got changed). In practice this is ensured by the
275 // cheaper-to-lookup cache in getShaderPipelineForDefaultMaterial().
276 theKey.toString(shaderString, shaderKeyProperties);
277
278 // Check the in-memory, per-QSSGShaderCache (and so per-QQuickWindow)
279 // runtime cache. That may get cleared upon an explicit call to
280 // QQuickWindow::releaseResources(), but will otherwise store all
281 // encountered shader pipelines in any View3D in the window.
282 if (const auto &maybePipeline = shaderCache.tryGetRhiShaderPipeline(shaderString, featureSet))
283 return maybePipeline;
284
285 // Check if there's a pre-built (offline generated) shader for available.
286 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(shaderString, QQsbCollection::toFeatureSet(featureSet));
287 const QQsbCollection::EntryMap &pregenEntries = shaderLibraryManager.m_preGeneratedShaderEntries;
288 if (!pregenEntries.isEmpty()) {
289 const auto foundIt = pregenEntries.constFind(QQsbCollection::Entry(qsbcKey));
290 if (foundIt != pregenEntries.cend())
291 return shaderCache.newPipelineFromPregenerated(shaderString, featureSet, *foundIt, renderable.material);
292 }
293
294 // Try the persistent (disk-based) cache then.
295 if (const auto &maybePipeline = shaderCache.tryNewPipelineFromPersistentCache(qsbcKey, shaderString, featureSet))
296 return maybePipeline;
297
298 // Otherwise, build new shader code and run the resulting shaders through
299 // the shader conditioning pipeline.
300 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(renderable.getMaterial());
301 QSSGMaterialVertexPipeline vertexPipeline(shaderProgramGenerator,
302 shaderKeyProperties,
303 material.adapter);
304
305 return QSSGMaterialShaderGenerator::generateMaterialRhiShader(rendererLogPrefix(),
306 vertexPipeline,
307 renderable.shaderDescription,
308 shaderKeyProperties,
309 featureSet,
310 renderable.material,
311 shaderLibraryManager,
312 shaderCache,
313 shaderAugmentation);
314}
315
316QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipeline(QSSGRenderer &renderer,
317 QSSGSubsetRenderable &inRenderable,
318 const QSSGShaderFeatures &inFeatureSet,
319 const QSSGUserShaderAugmentation &shaderAugmentation = {})
320{
321 auto *currentLayer = renderer.m_currentLayer;
322 auto &generatedShaderString = currentLayer->generatedShaderString;
323 const auto &m_contextInterface = renderer.m_contextInterface;
324 const auto &theCache = m_contextInterface->shaderCache();
325 const auto &shaderProgramGenerator = m_contextInterface->shaderProgramGenerator();
326 const auto &shaderLibraryManager = m_contextInterface->shaderLibraryManager();
327 return QSSGRendererPrivate::generateRhiShaderPipelineImpl(inRenderable, *shaderLibraryManager, *theCache, *shaderProgramGenerator, currentLayer->defaultMaterialShaderKeyProperties, inFeatureSet, shaderAugmentation, generatedShaderString);
328}
329
330void QSSGRenderer::beginFrame(QSSGRenderLayer &layer, bool allowRecursion)
331{
332 const bool executeBeginFrame = !(allowRecursion && (m_activeFrameRef++ != 0));
333 if (executeBeginFrame) {
334 m_contextInterface->perFrameAllocator()->reset();
335 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(&layer));
336 resetResourceCounters(&layer);
337 }
338}
339
340bool QSSGRenderer::endFrame(QSSGRenderLayer &layer, bool allowRecursion)
341{
342 const bool executeEndFrame = !(allowRecursion && (--m_activeFrameRef != 0));
343 if (executeEndFrame) {
344 cleanupUnreferencedBuffers(&layer);
345
346 // We need to do this endFrame(), as the material nodes might not exist after this!
347 for (auto *matObj : std::as_const(m_materialClearDirty)) {
348 if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) {
349 static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty();
350 } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
351 matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
352 matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
353 static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty();
354 }
355 }
356 m_materialClearDirty.clear();
357
358 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(&layer));
359
360 ++m_frameCount;
361 }
362
363 return executeEndFrame;
364}
365
366QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickAll(const QSSGRenderContextInterface &ctx,
367 const QSSGRenderLayer &layer,
368 const QSSGRenderRay &ray)
369{
370 const auto &bufferManager = ctx.bufferManager();
371 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
372 PickResultList pickResults;
373 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
374 getLayerHitObjectList(layer, *bufferManager, ray, isGlobalPickingEnabled, pickResults);
375 // Things are rendered in a particular order and we need to respect that ordering.
376 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
377 return lhs.m_distanceSq < rhs.m_distanceSq;
378 });
379 return pickResults;
380}
381
382QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPick(const QSSGRenderContextInterface &ctx,
383 const QSSGRenderLayer &layer,
384 const QSSGRenderRay &ray,
385 QSSGRenderNode *target)
386{
387 const auto &bufferManager = ctx.bufferManager();
388 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
389
390 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
391 PickResultList pickResults;
392 if (target)
393 intersectRayWithSubsetRenderable(layer, *bufferManager, ray, *target, pickResults);
394 else
395 getLayerHitObjectList(layer, *bufferManager, ray, isGlobalPickingEnabled, pickResults);
396
397 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
398 return lhs.m_distanceSq < rhs.m_distanceSq;
399 });
400 return pickResults;
401}
402
403using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
404static void getPickableRecursive(const QSSGRenderNode &node, RenderableList &renderables, bool pickEverything = false)
405{
406 if (QSSGRenderGraphObject::isRenderable(node.type) && (pickEverything || node.getLocalState(QSSGRenderNode::LocalState::Pickable))) {
407 renderables.push_back(&node);
408 }
409
410 for (const auto &child : node.children)
411 getPickableRecursive(child, renderables, pickEverything);
412}
413
414std::optional<QSSGRenderPickResult> QSSGRendererPrivate::syncPickClosestPoint(const QSSGRenderContextInterface &ctx,
415 const QSSGRenderLayer &layer,
416 const QVector3D &center, const float radiusSquared,
417 QSSGRenderNode *target)
418{
419 const auto &bufferManager = ctx.bufferManager();
420
421 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
422 std::optional<QSSGRenderPickResult> result = std::nullopt;
423 if (target) {
424 result = closestPointOnSubsetRenderable(layer, *bufferManager, center, radiusSquared, *target);
425 } else {
426 const bool pickEverything = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
427 RenderableList renderables;
428 for (const auto &childNode : layer.children)
429 getPickableRecursive(childNode, renderables, pickEverything);
430 float bestDistSquared = radiusSquared;
431 for (const auto &childNode : renderables) {
432 const auto res = closestPointOnSubsetRenderable(layer, *bufferManager, center, bestDistSquared, *childNode);
433 if (res.has_value()) {
434 bestDistSquared = res.value().m_distanceSq;
435 result = res;
436 }
437 }
438 }
439
440 return result;
441}
442
443QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickSubset(const QSSGRenderLayer &layer,
444 QSSGBufferManager &bufferManager,
445 const QSSGRenderRay &ray,
446 QVarLengthArray<QSSGRenderNode*> subset)
447{
448 QSSGRendererPrivate::PickResultList pickResults;
449 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
450
451 for (auto target : subset)
452 intersectRayWithSubsetRenderable(layer, bufferManager, ray, *target, pickResults);
453
454 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
455 return lhs.m_distanceSq < rhs.m_distanceSq;
456 });
457 return pickResults;
458}
459
460void QSSGRendererPrivate::setGlobalPickingEnabled(QSSGRenderer &renderer, bool isEnabled)
461{
462 renderer.m_globalPickingEnabled = isEnabled;
463}
464
465void QSSGRendererPrivate::setRenderContextInterface(QSSGRenderer &renderer, QSSGRenderContextInterface *ctx)
466{
467 renderer.m_contextInterface = ctx;
468}
469
470void QSSGRendererPrivate::setSgRenderContext(QSSGRenderer &renderer, QSGRenderContext *sgRenderCtx)
471{
472 renderer.m_qsgRenderContext = sgRenderCtx;
473}
474
475QSGRenderContext *QSSGRendererPrivate::getSgRenderContext(const QSSGRenderer &renderer)
476{
477 return renderer.m_qsgRenderContext.data();
478}
479
480const std::unique_ptr<QSSGRhiQuadRenderer> &QSSGRenderer::rhiQuadRenderer() const
481{
482 if (!m_rhiQuadRenderer)
483 m_rhiQuadRenderer = std::make_unique<QSSGRhiQuadRenderer>();
484
485 return m_rhiQuadRenderer;
486}
487
488const std::unique_ptr<QSSGRhiCubeRenderer> &QSSGRenderer::rhiCubeRenderer() const
489{
490 if (!m_rhiCubeRenderer)
491 m_rhiCubeRenderer = std::make_unique<QSSGRhiCubeRenderer>();
492
493 return m_rhiCubeRenderer;
494
495}
496
497void QSSGRenderer::beginSubLayerRender(QSSGLayerRenderData &inLayer)
498{
499 inLayer.saveRenderState(*this);
500 m_currentLayer = nullptr;
501}
502
503void QSSGRenderer::endSubLayerRender(QSSGLayerRenderData &inLayer)
504{
505 inLayer.restoreRenderState(*this);
506 m_currentLayer = &inLayer;
507}
508
509void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
510{
511 m_currentLayer = &inLayer;
512}
513void QSSGRenderer::endLayerRender()
514{
515 m_currentLayer = nullptr;
516}
517
518static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
519{
520 if (QSSGRenderGraphObject::isRenderable(node.type))
521 renderables.push_back(&node);
522
523 for (const auto &child : node.children)
524 dfs(child, renderables);
525}
526
527void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer,
528 QSSGBufferManager &bufferManager,
529 const QSSGRenderRay &ray,
530 bool inPickEverything,
531 PickResultList &outIntersectionResult)
532{
533 RenderableList renderables;
534 for (const auto &childNode : layer.children)
535 dfs(childNode, renderables);
536
537 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
538 const auto &pickableObject = renderables.at(idx);
539 if (inPickEverything || pickableObject->getLocalState(QSSGRenderNode::LocalState::Pickable))
540 intersectRayWithSubsetRenderable(layer, bufferManager, ray, *pickableObject, outIntersectionResult);
541 }
542}
543
544namespace {
545
546static inline QVector3D multiply(const QMatrix3x3& M, const QVector3D& v)
547{
548 return QVector3D(
549 M(0,0) * v.x() + M(0,1) * v.y() + M(0,2) * v.z(),
550 M(1,0) * v.x() + M(1,1) * v.y() + M(1,2) * v.z(),
551 M(2,0) * v.x() + M(2,1) * v.y() + M(2,2) * v.z()
552 );
553}
554
555// Return true if G ≈ s^2 I; outputs s2 (>=0). tolerance is relative-ish.
556static inline bool isUniformScaleMetric(const QMatrix3x3& G, float& s2, float tolerance = 1e-5f) {
557 const float gxx = G(0,0), gyy = G(1,1), gzz = G(2,2);
558 const float gxy = G(0,1), gxz = G(0,2), gyz = G(1,2);
559
560 // Average of diagonals as robust estimate of s^2
561 s2 = (gxx + gyy + gzz) / 3.0f;
562
563 // Scale for relative tolerance (avoid divide by zero)
564 const float scale = std::max({ std::fabs(gxx), std::fabs(gyy), std::fabs(gzz), 1.0f });
565
566 // Off-diagonals should be ~0; diagonals should be ~equal to s2
567 const bool offDiagOK = (std::fabs(gxy) <= tolerance * scale) &&
568 (std::fabs(gxz) <= tolerance * scale) &&
569 (std::fabs(gyz) <= tolerance * scale) &&
570 (std::fabs(G(1,0)) <= tolerance * scale) && // in case it's not exactly symmetric
571 (std::fabs(G(2,0)) <= tolerance * scale) &&
572 (std::fabs(G(2,1)) <= tolerance * scale);
573
574 const bool diagOK = (std::fabs(gxx - s2) <= tolerance * scale) &&
575 (std::fabs(gyy - s2) <= tolerance * scale) &&
576 (std::fabs(gzz - s2) <= tolerance * scale);
577
578 return offDiagOK && diagOK && (s2 >= 0.0f);
579}
580
581struct EuclideanDot
582{
583 inline float operator()(const QVector3D& u, const QVector3D& v) const {
584 return QVector3D::dotProduct(u, v);
585 }
586};
587
588struct MetricDot
589{
590 QMatrix3x3 G;
591 inline float operator()(const QVector3D& u, const QVector3D& v) const {
592 // u^T (G v)
593 return QVector3D::dotProduct(u, multiply(G, v));
594 }
595};
596
597// Closest point on triangle ABC to point p, using metric defined by template class
598// This code is based on: https://github.com/RenderKit/embree/blob/master/tutorials/common/math/closest_point.h
599// Copyright 2009-2021 Intel Corporation
600// SPDX-License-Identifier: Apache-2.0
601
602template<class Dot>
603static QVector3D closestPointOnTriangle(const QVector3D &p,
604 const QVector3D &a,
605 const QVector3D &b,
606 const QVector3D &c,
607 const Dot &dot,
608 float &u, float &v, float &w)
609{
610 const QVector3D ab = b - a;
611 const QVector3D ac = c - a;
612 const QVector3D ap = p - a;
613
614 // Vertex region A
615 const float d1 = dot(ab, ap);
616 const float d2 = dot(ac, ap);
617 if (d1 <= 0.f && d2 <= 0.f) {
618 u = 1.0f; v = 0.0f; w = 0.0f;
619 return a;
620 }
621
622 // Vertex region B
623 const QVector3D bp = p - b;
624 const float d3 = dot(ab, bp);
625 const float d4 = dot(ac, bp);
626 if (d3 >= 0.f && d4 <= d3) {
627 u = 0.0f; v = 1.0f; w = 0.0f;
628 return b;
629 }
630
631 // Edge AB
632 const float vc = d1 * d4 - d3 * d2;
633 if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) {
634 const float v_edge = d1 / (d1 - d3);
635 u = 1.0f - v_edge; v = v_edge; w = 0.0f;
636 return a + v_edge * ab;
637 }
638
639 // Vertex region C
640 const QVector3D cp = p - c;
641 const float d5 = dot(ab, cp);
642 const float d6 = dot(ac, cp);
643 if (d6 >= 0.f && d5 <= d6) {
644 u = 0.0f; v = 0.0f; w = 1.0f;
645 return c;
646 }
647
648 // Edge AC
649 const float vb = d5 * d2 - d1 * d6;
650 if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) {
651 const float w_edge = d2 / (d2 - d6);
652 u = 1.0f - w_edge; v = 0.0f; w = w_edge;
653 return a + w_edge * ac;
654 }
655
656 // Edge BC
657 const float va = d3 * d6 - d5 * d4;
658 if (va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) {
659 const QVector3D bc = c - b;
660 const float w_edge = (d4 - d3) / ((d4 - d3) + (d5 - d6));
661 u = 0.0f; v = 1.0f - w_edge; w = w_edge;
662 return b + w_edge * bc;
663 }
664
665 // Inside face region
666 const float denom = va + vb + vc;
667
668 // Check for degenerate case
669 if (std::abs(denom) < 1e-20f) {
670 // Degenerate triangle in metric space: fall back to closest among vertices
671 const float da = dot(ap, ap);
672 const float db = dot(bp, bp);
673 const float dc = dot(cp, cp);
674 if (da <= db && da <= dc) {
675 u = 1.0f; v = 0.0f; w = 0.0f;
676 return a;
677 }
678 if (db <= dc) {
679 u = 0.0f; v = 1.0f; w = 0.0f;
680 return b;
681 }
682 u = 0.0f; v = 0.0f; w = 1.0f;
683 return c;
684 }
685
686 const float invDenom = 1.0f / denom;
687 u = va * invDenom;
688 v = vb * invDenom;
689 w = vc * invDenom;
690 return a + v * ab + w * ac;
691}
692
693struct SphereData
694{
695 QMatrix4x4 globalTransform; // model -> world
696 QMatrix3x3 pullbackMetric;
697 QVector3D centerLocal; // sphere center in model local space
698};
699
700// Create local-space query data from world-space sphere and model transform.
701// The local radius uses max column length of the inverse linear part as a cheap,
702// conservative bound under non-uniform scaling/shear.
703static inline SphereData createSphereData(const QMatrix4x4 &globalTransform,
704 const QVector3D &centerWorld)
705{
706 QMatrix4x4 inv = globalTransform.inverted();
707
708 // center in local space
709 const QVector3D centerLocal = QSSGUtils::mat44::transform(inv, centerWorld);
710
711 const QMatrix3x3 A = QSSGUtils::mat44::getUpper3x3(globalTransform);
712 const QMatrix3x3 G = A.transposed() * A;
713
714 return SphereData{ globalTransform, G, centerLocal };
715}
716
717// Squared distance from point to axis-aligned bounding box.
718static inline float distanceSqPointTransformedAABB(const QVector3D &localPoint,
719 const QSSGBounds3 &localAABB,
720 const QMatrix3x3 &G)
721{
722 // Find the closest point on the AABB in local space
723 QVector3D closestLocal(
724 qBound(localAABB.minimum.x(), localPoint.x(), localAABB.maximum.x()),
725 qBound(localAABB.minimum.y(), localPoint.y(), localAABB.maximum.y()),
726 qBound(localAABB.minimum.z(), localPoint.z(), localAABB.maximum.z())
727 );
728
729 // Compute the difference vector in local space
730 QVector3D localDiff = localPoint - closestLocal;
731
732 // Use the pullback metric to get the squared world-space distance
733 // ||v||_world^2 = v^T * G * v where G = A^T * A
734 MetricDot dot{G};
735 return dot(localDiff, localDiff);
736}
737
738struct ClosestPointResult
739{
740 bool found = false;
741 float distSq = std::numeric_limits<float>::max();
742 QVector3D localPoint;
743 QVector3D scenePoint;
744 QVector3D faceNormal;
745 QVector3D sceneNormal;
746 QVector2D uv;
747 int subset = -1;
748 int instanceIndex = -1;
749};
750
751static void closestPointBVHLeafNode(const SphereData &data,
752 const QSSGMeshBVHNode *node,
753 const QSSGRenderMesh *mesh,
754 int subset,
755 int instanceIndex,
756 ClosestPointResult &best)
757{
758 const int begin = node->offset;
759 const int end = begin + node->count;
760 const auto &triangles = mesh->bvh->triangles();
761
762 // Determine if we can use faster Euclidean distance computation
763 float uniformScaleFactor = 1.0f;
764 const bool isUniformScale = isUniformScaleMetric(data.pullbackMetric, uniformScaleFactor);
765 const MetricDot metricDot { data.pullbackMetric };
766
767 for (int i = begin; i < end; ++i) {
768 const auto &triangle = triangles[i];
769
770 // Micro-pruning: skip triangles whose bounds are already farther than current best
771 const float triangleDistSq = distanceSqPointTransformedAABB(data.centerLocal, triangle.bounds, data.pullbackMetric);
772 if (triangleDistSq >= best.distSq)
773 continue;
774
775 // Find closest point on triangle
776 float u, v, w;
777 QVector3D closestPoint;
778
779 if (isUniformScale) {
780 closestPoint = closestPointOnTriangle(data.centerLocal,
781 triangle.vertex1, triangle.vertex2, triangle.vertex3,
782 EuclideanDot{},
783 u, v, w);
784 } else {
785 closestPoint = closestPointOnTriangle(data.centerLocal,
786 triangle.vertex1, triangle.vertex2, triangle.vertex3,
787 metricDot,
788 u, v, w);
789 }
790
791 // Compute squared distance in metric space
792 const QVector3D delta = data.centerLocal - closestPoint;
793 const float distSq = metricDot(delta, delta);
794
795 // Update best result if this is closer
796 if (distSq < best.distSq) {
797 best.distSq = distSq;
798 best.localPoint = closestPoint;
799 best.scenePoint = QSSGUtils::mat44::transform(data.globalTransform, closestPoint);
800 best.subset = subset;
801 best.instanceIndex = instanceIndex;
802 best.found = true;
803
804 // Interpolate UV coordinates using barycentric coordinates.
805 // Note that we're using a different definition of u, v, w than intersectWithBVHTriangles
806 best.uv = u * triangle.uvCoord1 + v * triangle.uvCoord2 + w * triangle.uvCoord3;
807
808 // Compute face normal in local space
809 const QVector3D edge1 = triangle.vertex2 - triangle.vertex1;
810 const QVector3D edge2 = triangle.vertex3 - triangle.vertex1;
811 best.faceNormal = QVector3D::normal(edge1, edge2).normalized();
812 const QMatrix3x3 normalMatrix = data.globalTransform.normalMatrix();
813 best.sceneNormal = QSSGUtils::mat33::transform(normalMatrix, best.faceNormal);
814 }
815 }
816}
817
818static void closestPointBVH(const SphereData &data,
819 const QSSGMeshBVHNode *node,
820 const QSSGRenderMesh *mesh,
821 int subset,
822 int instanceIndex,
823 ClosestPointResult &best)
824{
825 if (!node || !mesh || !mesh->bvh)
826 return;
827
828 // Prune by AABB distance vs. current best
829 const float aabbDistSq = distanceSqPointTransformedAABB(data.centerLocal, node->boundingData, data.pullbackMetric);
830 if (aabbDistSq >= best.distSq)
831 return;
832
833 // Leaf node: compute closest point on each triangle
834 if (node->count != 0) {
835 closestPointBVHLeafNode(data, node, mesh, subset, instanceIndex, best);
836 return;
837 }
838
839 // Internal node: visit children in order of increasing AABB distance
840 const auto *leftChild = static_cast<const QSSGMeshBVHNode *>(node->left);
841 const auto *rightChild = static_cast<const QSSGMeshBVHNode *>(node->right);
842
843 // Compute AABB distances for both children
844 const float leftDistSq = leftChild
845 ? distanceSqPointTransformedAABB(data.centerLocal, leftChild->boundingData, data.pullbackMetric)
846 : std::numeric_limits<float>::max();
847 const float rightDistSq = rightChild
848 ? distanceSqPointTransformedAABB(data.centerLocal, rightChild->boundingData, data.pullbackMetric)
849 : std::numeric_limits<float>::max();
850
851 // Visit children in order of increasing distance (closer child first for better pruning)
852 if (leftDistSq < rightDistSq) {
853 if (leftDistSq < best.distSq)
854 closestPointBVH(data, leftChild, mesh, subset, instanceIndex, best);
855 if (rightDistSq < best.distSq)
856 closestPointBVH(data, rightChild, mesh, subset, instanceIndex, best);
857 } else {
858 if (rightDistSq < best.distSq)
859 closestPointBVH(data, rightChild, mesh, subset, instanceIndex, best);
860 if (leftDistSq < best.distSq)
861 closestPointBVH(data, leftChild, mesh, subset, instanceIndex, best);
862 }
863}
864} // namespace (anonymous)
865
866std::optional<QSSGRenderPickResult>
867QSSGRendererPrivate::closestPointOnSubsetRenderable(const QSSGRenderLayer& layer,
868 QSSGBufferManager& bufferManager,
869 const QVector3D& center,
870 const float radiusSquared,
871 const QSSGRenderNode& node)
872{
873 if (!layer.renderData)
874 return std::nullopt;
875
876 const auto *renderData = layer.renderData;
877
878 // Note: If we want to extend this to also handling Item2D, this is where we would do it.
879 // if (node.type == QSSGRenderGraphObject::Type::Item2D) {
880 // ...
881 // }
882
883 if (node.type != QSSGRenderGraphObject::Type::Model)
884 return std::nullopt;
885
886 const auto &model = static_cast<const QSSGRenderModel &>(node);
887
888 // We have to have a guard here, as the meshes are usually loaded on the render thread,
889 // and we assume all meshes are loaded before picking and none are removed, which
890 // is usually true, except for custom geometry which can be updated at any time. So this
891 // guard should really only be locked whenever a custom geometry buffer is being updated
892 // on the render thread. Still naughty though because this can block the render thread.
893
894 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
895
896 auto mesh = bufferManager.getMeshForPicking(model);
897 if (!mesh)
898 return std::nullopt;
899
900 // Early culling: check if sphere can reach model bounds
901 QSSGBounds3 modelBounds;
902 for (const auto &subset : mesh->subsets)
903 modelBounds.include(subset.bounds);
904
905 if (modelBounds.isEmpty())
906 return std::nullopt;
907
908 const bool instancing = model.instancing();
909 int instanceCount = instancing ? model.instanceTable->count() : 1;
910 const auto instanceTransforms = instancing ? renderData->getInstanceTransforms(model) : QSSGLayerRenderData::InstanceTransforms{};
911
912 ClosestPointResult best;
913 best.distSq = radiusSquared; // Start with sphere radius as max distance
914
915 for (int i = 0; i < instanceCount; ++i) {
916 int instanceIndex = 0;
917 QMatrix4x4 modelTransform;
918 if (instancing) {
919 instanceIndex = i;
920 modelTransform = instanceTransforms.global * model.instanceTable->getTransform(instanceIndex) * instanceTransforms.local;
921 } else {
922 modelTransform = renderData->getGlobalTransform(model);
923 }
924 const SphereData data = createSphereData(modelTransform, center);
925
926 if (distanceSqPointTransformedAABB(data.centerLocal, modelBounds, data.pullbackMetric) > best.distSq)
927 continue;
928
929 for (int subsetIndex = 0; subsetIndex < mesh->subsets.size(); ++subsetIndex) {
930 const auto &subset = mesh->subsets[subsetIndex];
931
932 // Cull subset if its bounds are beyond our current best distance
933 if (distanceSqPointTransformedAABB(data.centerLocal, subset.bounds, data.pullbackMetric) >= best.distSq)
934 continue;
935
936 if (!subset.bvhRoot.isNull()) {
937 const auto *bvhRoot = static_cast<const QSSGMeshBVHNode *>(subset.bvhRoot);
938 closestPointBVH(data, bvhRoot, mesh, subsetIndex, instanceIndex, best);
939 }
940 }
941 }
942 if (best.found) {
943 return QSSGRenderPickResult{
944 &model,
945 best.distSq,
946 best.uv,
947 best.scenePoint,
948 best.localPoint,
949 best.faceNormal,
950 best.sceneNormal,
951 best.subset,
952 best.instanceIndex
953 };
954 }
955
956 return std::nullopt;
957}
958
959void QSSGRendererPrivate::intersectRayWithSubsetRenderable(const QSSGRenderLayer &layer,
960 QSSGBufferManager &bufferManager,
961 const QSSGRenderRay &inRay,
962 const QSSGRenderNode &node,
963 PickResultList &outIntersectionResultList)
964{
965 if (!layer.renderData)
966 return;
967
968 const auto *renderData = layer.renderData;
969
970 // Item2D's requires special handling
971 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
972 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
973 intersectRayWithItem2D(layer, inRay, item2D, outIntersectionResultList);
974 return;
975 }
976
977 if (node.type != QSSGRenderGraphObject::Type::Model)
978 return;
979
980 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
981
982 // We have to have a guard here, as the meshes are usually loaded on the render thread,
983 // and we assume all meshes are loaded before picking and none are removed, which
984 // is usually true, except for custom geometry which can be updated at any time. So this
985 // guard should really only be locked whenever a custom geometry buffer is being updated
986 // on the render thread. Still naughty though because this can block the render thread.
987 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
988 auto mesh = bufferManager.getMeshForPicking(model);
989 if (!mesh)
990 return;
991
992 const auto &subMeshes = mesh->subsets;
993 QSSGBounds3 modelBounds;
994 for (const auto &subMesh : subMeshes)
995 modelBounds.include(subMesh.bounds);
996
997 if (modelBounds.isEmpty())
998 return;
999
1000 const bool instancing = model.instancing(); // && instancePickingEnabled
1001 int instanceCount = instancing ? model.instanceTable->count() : 1;
1002
1003 const auto instanceTransforms = instancing ? renderData->getInstanceTransforms(model) : QSSGLayerRenderData::InstanceTransforms{};
1004
1005 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
1006
1007 QMatrix4x4 modelTransform;
1008 if (instancing) {
1009 modelTransform = instanceTransforms.global * model.instanceTable->getTransform(instanceIndex) * instanceTransforms.local;
1010 } else {
1011 modelTransform = renderData->getGlobalTransform(model);
1012 }
1013 auto rayData = QSSGRenderRay::createRayData(modelTransform, inRay);
1014
1015 auto hit = QSSGRenderRay::intersectWithAABBv2(rayData, modelBounds);
1016
1017 // If we don't intersect with the model at all, then there's no need to go furher down!
1018 if (!hit.intersects())
1019 continue;
1020
1021 // Check each submesh to find the closest intersection point
1022 float minRayLength = std::numeric_limits<float>::max();
1023 QSSGRenderRay::IntersectionResult intersectionResult;
1024 QVector<QSSGRenderRay::IntersectionResult> results;
1025
1026 int subset = 0;
1027 int resultSubset = 0;
1028 for (const auto &subMesh : subMeshes) {
1029 QSSGRenderRay::IntersectionResult result;
1030 if (!subMesh.bvhRoot.isNull()) {
1031 hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bvhRoot->boundingData);
1032 if (hit.intersects()) {
1033 results.clear();
1034 inRay.intersectWithBVH(rayData, static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, results);
1035 float subMeshMinRayLength = std::numeric_limits<float>::max();
1036 for (const auto &subMeshResult : std::as_const(results)) {
1037 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
1038 result = subMeshResult;
1039 subMeshMinRayLength = result.rayLengthSquared;
1040 }
1041 }
1042 }
1043 } else {
1044 hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bounds);
1045 if (hit.intersects())
1046 result = QSSGRenderRay::createIntersectionResult(rayData, hit);
1047 }
1048 if (result.intersects && result.rayLengthSquared < minRayLength) {
1049 intersectionResult = result;
1050 minRayLength = intersectionResult.rayLengthSquared;
1051 resultSubset = subset;
1052 }
1053 subset++;
1054 }
1055
1056 if (intersectionResult.intersects)
1057 outIntersectionResultList.push_back(QSSGRenderPickResult { &model,
1058 intersectionResult.rayLengthSquared,
1059 intersectionResult.relXY,
1060 intersectionResult.scenePosition,
1061 intersectionResult.localPosition,
1062 intersectionResult.faceNormal,
1063 intersectionResult.sceneFaceNormal,
1064 resultSubset,
1065 instanceIndex
1066 });
1067 }
1068}
1069
1070void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderLayer &layer,
1071 const QSSGRenderRay &inRay,
1072 const QSSGRenderItem2D &item2D,
1073 PickResultList &outIntersectionResultList)
1074{
1075 const auto &globalTransform = layer.renderData->getGlobalTransform(item2D);
1076
1077 // Get the plane (and normal) that the item 2D is on
1078 const QVector3D p0 = QSSGRenderNode::getGlobalPos(globalTransform);
1079 const QVector3D normal = -QSSGRenderNode::getDirection(globalTransform);
1080
1081 const float d = QVector3D::dotProduct(inRay.direction, normal);
1082 float intersectionTime = 0;
1083 if (d > 1e-6f) {
1084 const QVector3D p0l0 = p0 - inRay.origin;
1085 intersectionTime = QVector3D::dotProduct(p0l0, normal) / d;
1086 if (intersectionTime >= 0) {
1087 // Intersection
1088 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
1089 const QMatrix4x4 inverseGlobalTransform = globalTransform.inverted();
1090 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(inverseGlobalTransform, intersectionPoint);
1091 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
1092 outIntersectionResultList.push_back(QSSGRenderPickResult { &item2D,
1093 intersectionTime * intersectionTime,
1094 qmlCoordinate,
1095 intersectionPoint,
1096 localIntersectionPoint,
1097 -normal, -normal });
1098 }
1099 }
1100}
1101
1102QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer,
1103 QSSGSubsetRenderable &inRenderable,
1104 const QSSGShaderFeatures &inFeatureSet,
1105 const QSSGUserShaderAugmentation &shaderAugmentation)
1106{
1107 auto *m_currentLayer = renderer.m_currentLayer;
1108 QSSG_ASSERT(m_currentLayer != nullptr, return {});
1109
1110 // This function is the main entry point for retrieving the shaders for a
1111 // default material, and is called for every material for every model in
1112 // every frame. Therefore, like with custom materials, employ a first level
1113 // cache (a simple hash table), with a key that's quick to
1114 // generate/hash/compare. Even though there are other levels of caching in
1115 // the components that get invoked from here, those may not be suitable
1116 // performance wise. So bail out right here as soon as possible.
1117 auto &shaderMap = m_currentLayer->shaderMap;
1118
1119 QElapsedTimer timer;
1120 timer.start();
1121
1122 QSSGRhiShaderPipelinePtr shaderPipeline;
1123
1124 // This just references inFeatureSet and inRenderable.shaderDescription -
1125 // cheap to construct and is good enough for the find()
1126 // FIXME: Would be good to have some better approach here for the key.
1127 QByteArray name = shaderAugmentation.preamble + shaderAugmentation.body;
1128 for (const auto &def : shaderAugmentation.defines)
1129 name.append(def.name).append(def.value);
1130 QSSGShaderMapKey skey = QSSGShaderMapKey(name,
1131 inFeatureSet,
1132 inRenderable.shaderDescription);
1133 auto it = shaderMap.find(skey);
1134 if (it == shaderMap.end()) {
1135 Q_TRACE_SCOPE(QSSG_generateShader);
1136 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
1137 shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet, shaderAugmentation);
1138 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
1139 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
1140 skey.detach();
1141 // insert it no matter what, no point in trying over and over again
1142 shaderMap.insert(skey, shaderPipeline);
1143 } else {
1144 shaderPipeline = it.value();
1145 }
1146
1147 if (shaderPipeline != nullptr) {
1148 if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty())
1149 m_currentLayer->ensureCachedCameraDatas();
1150 }
1151
1152 const auto &rhiContext = renderer.m_contextInterface->rhiContext();
1153 QSSGRhiContextStats::get(*rhiContext).registerMaterialShaderGenerationTime(timer.elapsed());
1154
1155 return shaderPipeline;
1156}
1157
1158QList<const QSSGRenderNode *> QSSGRendererPrivate::syncPickInFrustum(const QSSGRenderContextInterface &ctx,
1159 const QSSGRenderLayer &layer,
1160 const QSSGFrustum &frustum)
1161{
1162 if (!layer.renderData)
1163 return {};
1164
1165 auto &bufferManager = ctx.bufferManager();
1166 const bool pickEverything = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
1167
1168 RenderableList nodes;
1169 for (const auto &child : layer.children)
1170 getPickableRecursive(child, nodes, pickEverything);
1171
1172 QList<const QSSGRenderNode *> ret;
1173 for (const auto node : nodes) {
1174 auto aabb = node->getBounds(*bufferManager);
1175 if (!aabb.isEmpty()) {
1176 const auto &transform = layer.renderData->getGlobalTransform(*node);
1177 aabb.transform(transform);
1178 if (frustum.contains(aabb))
1179 ret.append(node);
1180 }
1181 }
1182
1183 return ret;
1184}
1185
1186QT_END_NAMESPACE
friend class QSSGRenderContextInterface
static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
static void getPickableRecursive(const QSSGRenderNode &node, RenderableList &renderables, bool pickEverything=false)
static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
static QByteArray rendererLogPrefix()