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