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 renderable.firstImage,
305 shaderLibraryManager,
306 shaderCache);
307}
308
309QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipeline(QSSGRenderer &renderer,
310 QSSGSubsetRenderable &inRenderable,
311 const QSSGShaderFeatures &inFeatureSet)
312{
313 auto *currentLayer = renderer.m_currentLayer;
314 auto &generatedShaderString = currentLayer->generatedShaderString;
315 const auto &m_contextInterface = renderer.m_contextInterface;
316 const auto &theCache = m_contextInterface->shaderCache();
317 const auto &shaderProgramGenerator = m_contextInterface->shaderProgramGenerator();
318 const auto &shaderLibraryManager = m_contextInterface->shaderLibraryManager();
319 return QSSGRendererPrivate::generateRhiShaderPipelineImpl(inRenderable, *shaderLibraryManager, *theCache, *shaderProgramGenerator, currentLayer->defaultMaterialShaderKeyProperties, inFeatureSet, generatedShaderString);
320}
321
322void QSSGRenderer::beginFrame(QSSGRenderLayer &layer, bool allowRecursion)
323{
324 const bool executeBeginFrame = !(allowRecursion && (m_activeFrameRef++ != 0));
325 if (executeBeginFrame) {
326 m_contextInterface->perFrameAllocator()->reset();
327 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(&layer));
328 resetResourceCounters(&layer);
329 }
330}
331
332bool QSSGRenderer::endFrame(QSSGRenderLayer &layer, bool allowRecursion)
333{
334 const bool executeEndFrame = !(allowRecursion && (--m_activeFrameRef != 0));
335 if (executeEndFrame) {
336 cleanupUnreferencedBuffers(&layer);
337
338 // We need to do this endFrame(), as the material nodes might not exist after this!
339 for (auto *matObj : std::as_const(m_materialClearDirty)) {
340 if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) {
341 static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty();
342 } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
343 matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
344 matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
345 static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty();
346 }
347 }
348 m_materialClearDirty.clear();
349
350 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(&layer));
351
352 ++m_frameCount;
353 }
354
355 return executeEndFrame;
356}
357
358QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickAll(const QSSGRenderContextInterface &ctx,
359 const QSSGRenderLayer &layer,
360 const QSSGRenderRay &ray)
361{
362 const auto &bufferManager = ctx.bufferManager();
363 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
364 PickResultList pickResults;
365 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
366 getLayerHitObjectList(layer, *bufferManager, ray, isGlobalPickingEnabled, pickResults);
367 // Things are rendered in a particular order and we need to respect that ordering.
368 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
369 return lhs.m_distanceSq < rhs.m_distanceSq;
370 });
371 return pickResults;
372}
373
374QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPick(const QSSGRenderContextInterface &ctx,
375 const QSSGRenderLayer &layer,
376 const QSSGRenderRay &ray,
377 QSSGRenderNode *target)
378{
379 const auto &bufferManager = ctx.bufferManager();
380 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*ctx.renderer());
381
382 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
383 PickResultList pickResults;
384 if (target)
385 intersectRayWithSubsetRenderable(layer, *bufferManager, ray, *target, pickResults);
386 else
387 getLayerHitObjectList(layer, *bufferManager, ray, isGlobalPickingEnabled, pickResults);
388
389 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
390 return lhs.m_distanceSq < rhs.m_distanceSq;
391 });
392 return pickResults;
393}
394
395QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickSubset(const QSSGRenderLayer &layer,
396 QSSGBufferManager &bufferManager,
397 const QSSGRenderRay &ray,
398 QVarLengthArray<QSSGRenderNode*> subset)
399{
400 QSSGRendererPrivate::PickResultList pickResults;
401 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
402
403 for (auto target : subset)
404 intersectRayWithSubsetRenderable(layer, bufferManager, ray, *target, pickResults);
405
406 std::stable_sort(pickResults.begin(), pickResults.end(), [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
407 return lhs.m_distanceSq < rhs.m_distanceSq;
408 });
409 return pickResults;
410}
411
412void QSSGRendererPrivate::setGlobalPickingEnabled(QSSGRenderer &renderer, bool isEnabled)
413{
414 renderer.m_globalPickingEnabled = isEnabled;
415}
416
417void QSSGRendererPrivate::setRenderContextInterface(QSSGRenderer &renderer, QSSGRenderContextInterface *ctx)
418{
419 renderer.m_contextInterface = ctx;
420}
421
422void QSSGRendererPrivate::setSgRenderContext(QSSGRenderer &renderer, QSGRenderContext *sgRenderCtx)
423{
424 renderer.m_qsgRenderContext = sgRenderCtx;
425}
426
427QSGRenderContext *QSSGRendererPrivate::getSgRenderContext(const QSSGRenderer &renderer)
428{
429 return renderer.m_qsgRenderContext.data();
430}
431
432const std::unique_ptr<QSSGRhiQuadRenderer> &QSSGRenderer::rhiQuadRenderer() const
433{
434 if (!m_rhiQuadRenderer)
435 m_rhiQuadRenderer = std::make_unique<QSSGRhiQuadRenderer>();
436
437 return m_rhiQuadRenderer;
438}
439
440const std::unique_ptr<QSSGRhiCubeRenderer> &QSSGRenderer::rhiCubeRenderer() const
441{
442 if (!m_rhiCubeRenderer)
443 m_rhiCubeRenderer = std::make_unique<QSSGRhiCubeRenderer>();
444
445 return m_rhiCubeRenderer;
446
447}
448
449void QSSGRenderer::beginSubLayerRender(QSSGLayerRenderData &inLayer)
450{
451 inLayer.saveRenderState(*this);
452 m_currentLayer = nullptr;
453}
454
455void QSSGRenderer::endSubLayerRender(QSSGLayerRenderData &inLayer)
456{
457 inLayer.restoreRenderState(*this);
458 m_currentLayer = &inLayer;
459}
460
461void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
462{
463 m_currentLayer = &inLayer;
464}
465void QSSGRenderer::endLayerRender()
466{
467 m_currentLayer = nullptr;
468}
469
470using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
471static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
472{
473 if (QSSGRenderGraphObject::isRenderable(node.type))
474 renderables.push_back(&node);
475
476 for (const auto &child : node.children)
477 dfs(child, renderables);
478}
479
480void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer,
481 QSSGBufferManager &bufferManager,
482 const QSSGRenderRay &ray,
483 bool inPickEverything,
484 PickResultList &outIntersectionResult)
485{
486 RenderableList renderables;
487 for (const auto &childNode : layer.children)
488 dfs(childNode, renderables);
489
490 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
491 const auto &pickableObject = renderables.at(idx);
492 if (inPickEverything || pickableObject->getLocalState(QSSGRenderNode::LocalState::Pickable))
493 intersectRayWithSubsetRenderable(layer, bufferManager, ray, *pickableObject, outIntersectionResult);
494 }
495}
496
497void QSSGRendererPrivate::intersectRayWithSubsetRenderable(const QSSGRenderLayer &layer,
498 QSSGBufferManager &bufferManager,
499 const QSSGRenderRay &inRay,
500 const QSSGRenderNode &node,
501 PickResultList &outIntersectionResultList)
502{
503 if (!layer.renderData)
504 return;
505
506 const auto *renderData = layer.renderData;
507
508 // Item2D's requires special handling
509 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
510 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
511 intersectRayWithItem2D(layer, inRay, item2D, outIntersectionResultList);
512 return;
513 }
514
515 if (node.type != QSSGRenderGraphObject::Type::Model)
516 return;
517
518 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
519
520 // We have to have a guard here, as the meshes are usually loaded on the render thread,
521 // and we assume all meshes are loaded before picking and none are removed, which
522 // is usually true, except for custom geometry which can be updated at any time. So this
523 // guard should really only be locked whenever a custom geometry buffer is being updated
524 // on the render thread. Still naughty though because this can block the render thread.
525 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
526 auto mesh = bufferManager.getMeshForPicking(model);
527 if (!mesh)
528 return;
529
530 const auto &subMeshes = mesh->subsets;
531 QSSGBounds3 modelBounds;
532 for (const auto &subMesh : subMeshes)
533 modelBounds.include(subMesh.bounds);
534
535 if (modelBounds.isEmpty())
536 return;
537
538 const bool instancing = model.instancing(); // && instancePickingEnabled
539 int instanceCount = instancing ? model.instanceTable->count() : 1;
540
541 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
542
543 QMatrix4x4 modelTransform;
544 if (instancing) {
545 const auto &[localInstanceTransform, globalInstanceTransform] = renderData->getInstanceTransforms(model);
546 modelTransform = globalInstanceTransform * model.instanceTable->getTransform(instanceIndex) * localInstanceTransform;
547 } else {
548 modelTransform = renderData->getGlobalTransform(model);
549 }
550 auto rayData = QSSGRenderRay::createRayData(modelTransform, inRay);
551
552 auto hit = QSSGRenderRay::intersectWithAABBv2(rayData, modelBounds);
553
554 // If we don't intersect with the model at all, then there's no need to go furher down!
555 if (!hit.intersects())
556 continue;
557
558 // Check each submesh to find the closest intersection point
559 float minRayLength = std::numeric_limits<float>::max();
560 QSSGRenderRay::IntersectionResult intersectionResult;
561 QVector<QSSGRenderRay::IntersectionResult> results;
562
563 int subset = 0;
564 int resultSubset = 0;
565 for (const auto &subMesh : subMeshes) {
566 QSSGRenderRay::IntersectionResult result;
567 if (!subMesh.bvhRoot.isNull()) {
568 hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bvhRoot->boundingData);
569 if (hit.intersects()) {
570 results.clear();
571 inRay.intersectWithBVH(rayData, static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, results);
572 float subMeshMinRayLength = std::numeric_limits<float>::max();
573 for (const auto &subMeshResult : std::as_const(results)) {
574 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
575 result = subMeshResult;
576 subMeshMinRayLength = result.rayLengthSquared;
577 }
578 }
579 }
580 } else {
581 hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bounds);
582 if (hit.intersects())
583 result = QSSGRenderRay::createIntersectionResult(rayData, hit);
584 }
585 if (result.intersects && result.rayLengthSquared < minRayLength) {
586 intersectionResult = result;
587 minRayLength = intersectionResult.rayLengthSquared;
588 resultSubset = subset;
589 }
590 subset++;
591 }
592
593 if (intersectionResult.intersects)
594 outIntersectionResultList.push_back(QSSGRenderPickResult { &model,
595 intersectionResult.rayLengthSquared,
596 intersectionResult.relXY,
597 intersectionResult.scenePosition,
598 intersectionResult.localPosition,
599 intersectionResult.faceNormal,
600 intersectionResult.sceneFaceNormal,
601 resultSubset,
602 instanceIndex
603 });
604 }
605}
606
607void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderLayer &layer,
608 const QSSGRenderRay &inRay,
609 const QSSGRenderItem2D &item2D,
610 PickResultList &outIntersectionResultList)
611{
612 const auto &globalTransform = layer.renderData->getGlobalTransform(item2D);
613
614 // Get the plane (and normal) that the item 2D is on
615 const QVector3D p0 = QSSGRenderNode::getGlobalPos(globalTransform);
616 const QVector3D normal = -QSSGRenderNode::getDirection(globalTransform);
617
618 const float d = QVector3D::dotProduct(inRay.direction, normal);
619 float intersectionTime = 0;
620 if (d > 1e-6f) {
621 const QVector3D p0l0 = p0 - inRay.origin;
622 intersectionTime = QVector3D::dotProduct(p0l0, normal) / d;
623 if (intersectionTime >= 0) {
624 // Intersection
625 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
626 const QMatrix4x4 inverseGlobalTransform = globalTransform.inverted();
627 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(inverseGlobalTransform, intersectionPoint);
628 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
629 outIntersectionResultList.push_back(QSSGRenderPickResult { &item2D,
630 intersectionTime * intersectionTime,
631 qmlCoordinate,
632 intersectionPoint,
633 localIntersectionPoint,
634 -normal, -normal });
635 }
636 }
637}
638
639QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer,
640 QSSGSubsetRenderable &inRenderable,
641 const QSSGShaderFeatures &inFeatureSet)
642{
643 auto *m_currentLayer = renderer.m_currentLayer;
644 QSSG_ASSERT(m_currentLayer != nullptr, return {});
645
646 // This function is the main entry point for retrieving the shaders for a
647 // default material, and is called for every material for every model in
648 // every frame. Therefore, like with custom materials, employ a first level
649 // cache (a simple hash table), with a key that's quick to
650 // generate/hash/compare. Even though there are other levels of caching in
651 // the components that get invoked from here, those may not be suitable
652 // performance wise. So bail out right here as soon as possible.
653 auto &shaderMap = m_currentLayer->shaderMap;
654
655 QElapsedTimer timer;
656 timer.start();
657
658 QSSGRhiShaderPipelinePtr shaderPipeline;
659
660 // This just references inFeatureSet and inRenderable.shaderDescription -
661 // cheap to construct and is good enough for the find()
662 QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(),
663 inFeatureSet,
664 inRenderable.shaderDescription);
665 auto it = shaderMap.find(skey);
666 if (it == shaderMap.end()) {
667 Q_TRACE_SCOPE(QSSG_generateShader);
668 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
669 shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet);
670 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
671 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
672 skey.detach();
673 // insert it no matter what, no point in trying over and over again
674 shaderMap.insert(skey, shaderPipeline);
675 } else {
676 shaderPipeline = it.value();
677 }
678
679 if (shaderPipeline != nullptr) {
680 if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty())
681 m_currentLayer->ensureCachedCameraDatas();
682 }
683
684 const auto &rhiContext = renderer.m_contextInterface->rhiContext();
685 QSSGRhiContextStats::get(*rhiContext).registerMaterialShaderGenerationTime(timer.elapsed());
686
687 return shaderPipeline;
688}
689
690QT_END_NAMESPACE
friend class QSSGRenderContextInterface
static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
static QByteArray logPrefix()