1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include "qssgrenderer_p.h"
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(const Item2DData &data)
83{
84 const auto foundIt = std::find_if(first: item2DDataList.begin(), last: item2DDataList.end(), pred: [&data](const Item2DData &i2dd) {
85 return i2dd.layer == data.layer && i2dd.item == data.item;
86 });
87
88 if (foundIt != item2DDataList.end()) {
89 // Update existing entry
90 *foundIt = data;
91 return;
92 }
93
94 item2DDataList.push_back(x: data);
95}
96
97void QSSGRenderer::populateItem2DDataMapForLayer(const QSSGRenderLayer &layer, Item2DDataMap &item2DDataMap) const
98{
99 item2DDataMap.clear();
100 for (const auto &item2dData : std::as_const(t: item2DDataList)) {
101 if (item2dData.layer == &layer)
102 item2DDataMap[item2dData.item] = item2dData;
103 }
104}
105
106void QSSGRenderer::releaseItem2DData(const QSSGRenderItem2D &item2D)
107{
108 for (auto it = item2DDataList.begin(); it != item2DDataList.end(); /* no increment */) {
109 if (it->item == &item2D) {
110 delete it->rpd;
111 delete it->renderer;
112 it = item2DDataList.erase(position: it);
113 } else {
114 ++it;
115 }
116 }
117}
118
119void QSSGRenderer::releaseItem2DData(const QSSGRenderLayer &layer)
120{
121 for (auto it = item2DDataList.begin(); it != item2DDataList.end(); /* no increment */) {
122 if (it->layer == &layer) {
123 delete it->rpd;
124 delete it->renderer;
125 it = item2DDataList.erase(position: it);
126 } else {
127 ++it;
128 }
129 }
130}
131
132QSSGRenderer::QSSGRenderer() = default;
133
134QSSGRenderer::~QSSGRenderer()
135{
136 m_contextInterface = nullptr;
137 releaseCachedResources();
138}
139
140void QSSGRenderer::cleanupUnreferencedBuffers(QSSGRenderLayer *inLayer)
141{
142 // Now check for unreferenced buffers and release them if necessary
143 m_contextInterface->bufferManager()->cleanupUnreferencedBuffers(frameId: m_frameCount, layer: inLayer);
144}
145
146void QSSGRenderer::resetResourceCounters(QSSGRenderLayer *inLayer)
147{
148 m_contextInterface->bufferManager()->resetUsageCounters(frameId: m_frameCount, layer: inLayer);
149}
150
151bool QSSGRenderer::prepareLayerForRender(QSSGRenderLayer &inLayer)
152{
153 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
154 Q_ASSERT(theRenderData);
155
156 // Need to check if the world root node is dirty and if we need to trigger
157 // a reindex of the world root node.
158 Q_ASSERT(inLayer.rootNode);
159 if (inLayer.rootNode->isDirty(dirtyFlag: QSSGRenderRoot::DirtyFlag::TreeDirty))
160 inLayer.rootNode->reindex(); // Clears TreeDirty flag
161
162 beginLayerRender(inLayer&: *theRenderData);
163 theRenderData->resetForFrame();
164 theRenderData->prepareForRender();
165 endLayerRender();
166 return theRenderData->layerPrepResult.flags.wasDirty();
167}
168
169// Phase 1: prepare. Called when the renderpass is not yet started on the command buffer.
170void QSSGRenderer::rhiPrepare(QSSGRenderLayer &inLayer)
171{
172 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
173 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
174
175 const auto layerPrepResult = theRenderData->layerPrepResult;
176 if (layerPrepResult.isLayerVisible()) {
177 ///
178 QSSGRhiContext *rhiCtx = contextInterface()->rhiContext().get();
179 QSSG_ASSERT(rhiCtx->isValid() && rhiCtx->rhi()->isRecordingFrame(), return);
180 beginLayerRender(inLayer&: *theRenderData);
181 theRenderData->maybeProcessLightmapBaking();
182 // Process active passes. "PreMain" passes are individual passes
183 // that does can and should be done in the rhi prepare phase.
184 // It is assumed that passes are sorted in the list with regards to
185 // execution order.
186 const auto &activePasses = theRenderData->activePasses;
187 for (const auto &pass : activePasses) {
188 pass->renderPrep(renderer&: *this, data&: *theRenderData);
189 if (pass->passType() == QSSGRenderPass::Type::Standalone)
190 pass->renderPass(renderer&: *this);
191 }
192
193 endLayerRender();
194 }
195}
196
197// Phase 2: render. Called within an active renderpass on the command buffer.
198void QSSGRenderer::rhiRender(QSSGRenderLayer &inLayer)
199{
200 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
201 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
202 if (theRenderData->layerPrepResult.isLayerVisible()) {
203 beginLayerRender(inLayer&: *theRenderData);
204 const auto &activePasses = theRenderData->activePasses;
205 for (const auto &pass : activePasses) {
206 if (pass->passType() == QSSGRenderPass::Type::Main || pass->passType() == QSSGRenderPass::Type::Extension)
207 pass->renderPass(renderer&: *this);
208 }
209 endLayerRender();
210 }
211}
212
213template<typename Container>
214static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
215{
216 const auto &rhiCtx = rci.rhiContext();
217 if (!rhiCtx->isValid())
218 return;
219
220 const auto &bufferManager = rci.bufferManager();
221
222 for (const auto &resource : resources) {
223 if (resource->type == QSSGRenderGraphObject::Type::Geometry) {
224 auto geometry = static_cast<QSSGRenderGeometry*>(resource);
225 bufferManager->releaseGeometry(geometry);
226 } else if (resource->type == QSSGRenderGraphObject::Type::Model) {
227 auto model = static_cast<QSSGRenderModel*>(resource);
228 QSSGRhiContextPrivate::get(q: rhiCtx.get())->cleanupDrawCallData(model);
229 delete model->particleBuffer;
230 } else if (resource->type == QSSGRenderGraphObject::Type::TextureData || resource->type == QSSGRenderGraphObject::Type::Skin) {
231 static_assert(std::is_base_of_v<QSSGRenderTextureData, QSSGRenderSkin>, "QSSGRenderSkin is expected to be a QSSGRenderTextureData type!");
232 auto textureData = static_cast<QSSGRenderTextureData *>(resource);
233 bufferManager->releaseTextureData(data: textureData);
234 } else if (resource->type == QSSGRenderGraphObject::Type::RenderExtension) {
235 auto *rext = static_cast<QSSGRenderExtension *>(resource);
236 bufferManager->releaseExtensionResult(rext: *rext);
237 } else if (resource->type == QSSGRenderGraphObject::Type::ModelInstance) {
238 auto *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx.get());
239 auto *table = static_cast<QSSGRenderInstanceTable *>(resource);
240 rhiCtxD->releaseInstanceBuffer(instanceTable: table);
241 } else if (resource->type == QSSGRenderGraphObject::Type::Item2D) {
242 auto *item2D = static_cast<QSSGRenderItem2D *>(resource);
243 rci.renderer()->releaseItem2DData(item2D: *item2D);
244 }
245
246 // ### There might be more types that need to be supported
247
248 delete resource;
249 }
250}
251
252void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources)
253{
254 cleanupResourcesImpl(rci: *m_contextInterface, resources);
255 resources.clear();
256}
257
258void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources)
259{
260 cleanupResourcesImpl(rci: *m_contextInterface, resources);
261 resources.clear();
262}
263
264QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer)
265{
266 if (layer.renderData == nullptr)
267 layer.renderData = new QSSGLayerRenderData(layer, *this);
268
269 return layer.renderData;
270}
271
272void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material)
273{
274 m_materialClearDirty.insert(value: material);
275}
276
277static QByteArray logPrefix() { return QByteArrayLiteral("mesh default material pipeline-- "); }
278
279
280QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipelineImpl(QSSGSubsetRenderable &renderable,
281 QSSGShaderLibraryManager &shaderLibraryManager,
282 QSSGShaderCache &shaderCache,
283 QSSGProgramGenerator &shaderProgramGenerator,
284 const QSSGShaderDefaultMaterialKeyProperties &shaderKeyProperties,
285 const QSSGShaderFeatures &featureSet,
286 QByteArray &shaderString)
287{
288 shaderString = logPrefix();
289 QSSGShaderDefaultMaterialKey theKey(renderable.shaderDescription);
290
291 // This is not a cheap operation. This function assumes that it will not be
292 // hit for every material for every model in every frame (except of course
293 // for materials that got changed). In practice this is ensured by the
294 // cheaper-to-lookup cache in getShaderPipelineForDefaultMaterial().
295 theKey.toString(ioString&: shaderString, inProperties: shaderKeyProperties);
296
297 // Check the in-memory, per-QSSGShaderCache (and so per-QQuickWindow)
298 // runtime cache. That may get cleared upon an explicit call to
299 // QQuickWindow::releaseResources(), but will otherwise store all
300 // encountered shader pipelines in any View3D in the window.
301 if (const auto &maybePipeline = shaderCache.tryGetRhiShaderPipeline(inKey: shaderString, inFeatures: featureSet))
302 return maybePipeline;
303
304 // Check if there's a pre-built (offline generated) shader for available.
305 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: featureSet));
306 const QQsbCollection::EntryMap &pregenEntries = shaderLibraryManager.m_preGeneratedShaderEntries;
307 if (!pregenEntries.isEmpty()) {
308 const auto foundIt = pregenEntries.constFind(value: QQsbCollection::Entry(qsbcKey));
309 if (foundIt != pregenEntries.cend())
310 return shaderCache.newPipelineFromPregenerated(inKey: shaderString, inFeatures: featureSet, entry: *foundIt, obj: renderable.material);
311 }
312
313 // Try the persistent (disk-based) cache then.
314 if (const auto &maybePipeline = shaderCache.tryNewPipelineFromPersistentCache(qsbcKey, inKey: shaderString, inFeatures: featureSet))
315 return maybePipeline;
316
317 // Otherwise, build new shader code and run the resulting shaders through
318 // the shader conditioning pipeline.
319 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(renderable.getMaterial());
320 QSSGMaterialVertexPipeline vertexPipeline(shaderProgramGenerator,
321 shaderKeyProperties,
322 material.adapter);
323
324 return QSSGMaterialShaderGenerator::generateMaterialRhiShader(inShaderKeyPrefix: logPrefix(),
325 vertexGenerator&: vertexPipeline,
326 key: renderable.shaderDescription,
327 inProperties: shaderKeyProperties,
328 inFeatureSet: featureSet,
329 inMaterial: renderable.material,
330 inLights: renderable.lights,
331 inFirstImage: renderable.firstImage,
332 shaderLibraryManager,
333 theCache&: shaderCache);
334}
335
336QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipeline(QSSGRenderer &renderer,
337 QSSGSubsetRenderable &inRenderable,
338 const QSSGShaderFeatures &inFeatureSet)
339{
340 auto *currentLayer = renderer.m_currentLayer;
341 auto &generatedShaderString = currentLayer->generatedShaderString;
342 const auto &m_contextInterface = renderer.m_contextInterface;
343 const auto &theCache = m_contextInterface->shaderCache();
344 const auto &shaderProgramGenerator = m_contextInterface->shaderProgramGenerator();
345 const auto &shaderLibraryManager = m_contextInterface->shaderLibraryManager();
346 return QSSGRendererPrivate::generateRhiShaderPipelineImpl(renderable&: inRenderable, shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *theCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties: currentLayer->defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: generatedShaderString);
347}
348
349void QSSGRenderer::beginFrame(QSSGRenderLayer &layer, bool allowRecursion)
350{
351 const bool executeBeginFrame = !(allowRecursion && (m_activeFrameRef++ != 0));
352 if (executeBeginFrame) {
353 m_contextInterface->perFrameAllocator()->reset();
354 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(&layer));
355 resetResourceCounters(inLayer: &layer);
356 }
357}
358
359bool QSSGRenderer::endFrame(QSSGRenderLayer &layer, bool allowRecursion)
360{
361 const bool executeEndFrame = !(allowRecursion && (--m_activeFrameRef != 0));
362 if (executeEndFrame) {
363 cleanupUnreferencedBuffers(inLayer: &layer);
364
365 // We need to do this endFrame(), as the material nodes might not exist after this!
366 for (auto *matObj : std::as_const(t&: m_materialClearDirty)) {
367 if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) {
368 static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty();
369 } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
370 matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
371 matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
372 static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty();
373 }
374 }
375 m_materialClearDirty.clear();
376
377 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(&layer));
378
379 ++m_frameCount;
380 }
381
382 return executeEndFrame;
383}
384
385QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickAll(const QSSGRenderContextInterface &ctx,
386 const QSSGRenderLayer &layer,
387 const QSSGRenderRay &ray)
388{
389 const auto &bufferManager = ctx.bufferManager();
390 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer());
391 PickResultList pickResults;
392 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
393 getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults);
394 // Things are rendered in a particular order and we need to respect that ordering.
395 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
396 return lhs.m_distanceSq < rhs.m_distanceSq;
397 });
398 return pickResults;
399}
400
401QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPick(const QSSGRenderContextInterface &ctx,
402 const QSSGRenderLayer &layer,
403 const QSSGRenderRay &ray,
404 QSSGRenderNode *target)
405{
406 const auto &bufferManager = ctx.bufferManager();
407 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer());
408
409 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
410 PickResultList pickResults;
411 if (target)
412 intersectRayWithSubsetRenderable(layer, bufferManager&: *bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults);
413 else
414 getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults);
415
416 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
417 return lhs.m_distanceSq < rhs.m_distanceSq;
418 });
419 return pickResults;
420}
421
422QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickSubset(const QSSGRenderLayer &layer,
423 QSSGBufferManager &bufferManager,
424 const QSSGRenderRay &ray,
425 QVarLengthArray<QSSGRenderNode*> subset)
426{
427 QSSGRendererPrivate::PickResultList pickResults;
428 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
429
430 for (auto target : subset)
431 intersectRayWithSubsetRenderable(layer, bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults);
432
433 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
434 return lhs.m_distanceSq < rhs.m_distanceSq;
435 });
436 return pickResults;
437}
438
439void QSSGRendererPrivate::setGlobalPickingEnabled(QSSGRenderer &renderer, bool isEnabled)
440{
441 renderer.m_globalPickingEnabled = isEnabled;
442}
443
444void QSSGRendererPrivate::setRenderContextInterface(QSSGRenderer &renderer, QSSGRenderContextInterface *ctx)
445{
446 renderer.m_contextInterface = ctx;
447}
448
449void QSSGRendererPrivate::setSgRenderContext(QSSGRenderer &renderer, QSGRenderContext *sgRenderCtx)
450{
451 renderer.m_qsgRenderContext = sgRenderCtx;
452}
453
454QSGRenderContext *QSSGRendererPrivate::getSgRenderContext(const QSSGRenderer &renderer)
455{
456 return renderer.m_qsgRenderContext.data();
457}
458
459const std::unique_ptr<QSSGRhiQuadRenderer> &QSSGRenderer::rhiQuadRenderer() const
460{
461 if (!m_rhiQuadRenderer)
462 m_rhiQuadRenderer = std::make_unique<QSSGRhiQuadRenderer>();
463
464 return m_rhiQuadRenderer;
465}
466
467const std::unique_ptr<QSSGRhiCubeRenderer> &QSSGRenderer::rhiCubeRenderer() const
468{
469 if (!m_rhiCubeRenderer)
470 m_rhiCubeRenderer = std::make_unique<QSSGRhiCubeRenderer>();
471
472 return m_rhiCubeRenderer;
473
474}
475
476void QSSGRenderer::beginSubLayerRender(QSSGLayerRenderData &inLayer)
477{
478 inLayer.saveRenderState(renderer: *this);
479 m_currentLayer = nullptr;
480}
481
482void QSSGRenderer::endSubLayerRender(QSSGLayerRenderData &inLayer)
483{
484 inLayer.restoreRenderState(renderer&: *this);
485 m_currentLayer = &inLayer;
486}
487
488void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
489{
490 m_currentLayer = &inLayer;
491}
492void QSSGRenderer::endLayerRender()
493{
494 m_currentLayer = nullptr;
495}
496
497using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
498static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
499{
500 if (QSSGRenderGraphObject::isRenderable(type: node.type))
501 renderables.push_back(t: &node);
502
503 for (const auto &child : node.children)
504 dfs(node: child, renderables);
505}
506
507void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer,
508 QSSGBufferManager &bufferManager,
509 const QSSGRenderRay &ray,
510 bool inPickEverything,
511 PickResultList &outIntersectionResult)
512{
513 RenderableList renderables;
514 for (const auto &childNode : layer.children)
515 dfs(node: childNode, renderables);
516
517 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
518 const auto &pickableObject = renderables.at(idx);
519 if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable))
520 intersectRayWithSubsetRenderable(layer, bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult);
521 }
522}
523
524void QSSGRendererPrivate::intersectRayWithSubsetRenderable(const QSSGRenderLayer &layer,
525 QSSGBufferManager &bufferManager,
526 const QSSGRenderRay &inRay,
527 const QSSGRenderNode &node,
528 PickResultList &outIntersectionResultList)
529{
530 if (!layer.renderData)
531 return;
532
533 const auto *renderData = layer.renderData;
534
535 // Item2D's requires special handling
536 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
537 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
538 intersectRayWithItem2D(layer, inRay, item2D, outIntersectionResultList);
539 return;
540 }
541
542 if (node.type != QSSGRenderGraphObject::Type::Model)
543 return;
544
545 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
546
547 // We have to have a guard here, as the meshes are usually loaded on the render thread,
548 // and we assume all meshes are loaded before picking and none are removed, which
549 // is usually true, except for custom geometry which can be updated at any time. So this
550 // guard should really only be locked whenever a custom geometry buffer is being updated
551 // on the render thread. Still naughty though because this can block the render thread.
552 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
553 auto mesh = bufferManager.getMeshForPicking(model);
554 if (!mesh)
555 return;
556
557 const auto &subMeshes = mesh->subsets;
558 QSSGBounds3 modelBounds;
559 for (const auto &subMesh : subMeshes)
560 modelBounds.include(b: subMesh.bounds);
561
562 if (modelBounds.isEmpty())
563 return;
564
565 const bool instancing = model.instancing(); // && instancePickingEnabled
566 int instanceCount = instancing ? model.instanceTable->count() : 1;
567
568 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
569
570 QMatrix4x4 modelTransform;
571 if (instancing) {
572 const auto &[localInstanceTransform, globalInstanceTransform] = renderData->getInstanceTransforms(node: model);
573 modelTransform = globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * localInstanceTransform;
574 } else {
575 modelTransform = renderData->getGlobalTransform(node: model);
576 }
577 auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay);
578
579 auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds);
580
581 // If we don't intersect with the model at all, then there's no need to go furher down!
582 if (!hit.intersects())
583 continue;
584
585 // Check each submesh to find the closest intersection point
586 float minRayLength = std::numeric_limits<float>::max();
587 QSSGRenderRay::IntersectionResult intersectionResult;
588 QVector<QSSGRenderRay::IntersectionResult> results;
589
590 int subset = 0;
591 int resultSubset = 0;
592 for (const auto &subMesh : subMeshes) {
593 QSSGRenderRay::IntersectionResult result;
594 if (!subMesh.bvhRoot.isNull()) {
595 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData);
596 if (hit.intersects()) {
597 results.clear();
598 inRay.intersectWithBVH(data: rayData, bvh: static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, intersections&: results);
599 float subMeshMinRayLength = std::numeric_limits<float>::max();
600 for (const auto &subMeshResult : std::as_const(t&: results)) {
601 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
602 result = subMeshResult;
603 subMeshMinRayLength = result.rayLengthSquared;
604 }
605 }
606 }
607 } else {
608 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds);
609 if (hit.intersects())
610 result = QSSGRenderRay::createIntersectionResult(data: rayData, hit);
611 }
612 if (result.intersects && result.rayLengthSquared < minRayLength) {
613 intersectionResult = result;
614 minRayLength = intersectionResult.rayLengthSquared;
615 resultSubset = subset;
616 }
617 subset++;
618 }
619
620 if (intersectionResult.intersects)
621 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model,
622 .m_distanceSq: intersectionResult.rayLengthSquared,
623 .m_localUVCoords: intersectionResult.relXY,
624 .m_scenePosition: intersectionResult.scenePosition,
625 .m_localPosition: intersectionResult.localPosition,
626 .m_faceNormal: intersectionResult.faceNormal,
627 .m_sceneNormal: intersectionResult.sceneFaceNormal,
628 .m_subset: resultSubset,
629 .m_instanceIndex: instanceIndex
630 });
631 }
632}
633
634void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderLayer &layer,
635 const QSSGRenderRay &inRay,
636 const QSSGRenderItem2D &item2D,
637 PickResultList &outIntersectionResultList)
638{
639 const auto &globalTransform = layer.renderData->getGlobalTransform(node: item2D);
640
641 // Get the plane (and normal) that the item 2D is on
642 const QVector3D p0 = QSSGRenderNode::getGlobalPos(globalTransform);
643 const QVector3D normal = -QSSGRenderNode::getDirection(globalTransform);
644
645 const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal);
646 float intersectionTime = 0;
647 if (d > 1e-6f) {
648 const QVector3D p0l0 = p0 - inRay.origin;
649 intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d;
650 if (intersectionTime >= 0) {
651 // Intersection
652 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
653 const QMatrix4x4 inverseGlobalTransform = globalTransform.inverted();
654 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint);
655 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
656 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D,
657 .m_distanceSq: intersectionTime * intersectionTime,
658 .m_localUVCoords: qmlCoordinate,
659 .m_scenePosition: intersectionPoint,
660 .m_localPosition: localIntersectionPoint,
661 .m_faceNormal: -normal, .m_sceneNormal: -normal });
662 }
663 }
664}
665
666QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer,
667 QSSGSubsetRenderable &inRenderable,
668 const QSSGShaderFeatures &inFeatureSet)
669{
670 auto *m_currentLayer = renderer.m_currentLayer;
671 QSSG_ASSERT(m_currentLayer != nullptr, return {});
672
673 // This function is the main entry point for retrieving the shaders for a
674 // default material, and is called for every material for every model in
675 // every frame. Therefore, like with custom materials, employ a first level
676 // cache (a simple hash table), with a key that's quick to
677 // generate/hash/compare. Even though there are other levels of caching in
678 // the components that get invoked from here, those may not be suitable
679 // performance wise. So bail out right here as soon as possible.
680 auto &shaderMap = m_currentLayer->shaderMap;
681
682 QElapsedTimer timer;
683 timer.start();
684
685 QSSGRhiShaderPipelinePtr shaderPipeline;
686
687 // This just references inFeatureSet and inRenderable.shaderDescription -
688 // cheap to construct and is good enough for the find()
689 QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(),
690 inFeatureSet,
691 inRenderable.shaderDescription);
692 auto it = shaderMap.find(key: skey);
693 if (it == shaderMap.end()) {
694 Q_TRACE_SCOPE(QSSG_generateShader);
695 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
696 shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet);
697 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
698 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
699 skey.detach();
700 // insert it no matter what, no point in trying over and over again
701 shaderMap.insert(key: skey, value: shaderPipeline);
702 } else {
703 shaderPipeline = it.value();
704 }
705
706 if (shaderPipeline != nullptr) {
707 if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty())
708 m_currentLayer->ensureCachedCameraDatas();
709 }
710
711 const auto &rhiContext = renderer.m_contextInterface->rhiContext();
712 QSSGRhiContextStats::get(rhiCtx&: *rhiContext).registerMaterialShaderGenerationTime(ms: timer.elapsed());
713
714 return shaderPipeline;
715}
716
717QT_END_NAMESPACE
718

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