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
27#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
28#include <QtQuick3DUtils/private/qssgdataref_p.h>
29#include <QtQuick3DUtils/private/qssgutils_p.h>
30#include <QtQuick3DUtils/private/qssgassert_p.h>
31#include <qtquick3d_tracepoints_p.h>
32
33#include <QtCore/QMutexLocker>
34#include <QtCore/QBitArray>
35
36#include <cstdlib>
37#include <algorithm>
38#include <limits>
39
40/*
41 Rendering is done is several steps, these are:
42
43 1. \l{QSSGRenderer::beginFrame(){beginFrame()} - set's up the renderer to start a new frame.
44
45 2. Now that the renderer is reset, values for the \l{QSSGRenderer::setViewport}{viewport}, \l{QSSGRenderer::setDpr}{dpr},
46 \l{QSSGRenderer::setScissorRect}{scissorRect} etc. should be updated.
47
48 3. \l{QSSGRenderer::prepareLayerForRender()} - At this stage the scene tree will be traversed
49 and state for the renderer needed to render gets collected. This includes, but is not limited to,
50 calculating global transforms, loading of meshes, preparing materials and setting up the rendering
51 steps needed for the frame (opaque and transparent pass etc.)
52 If the there are custom \l{QQuick3DRenderExtension}{render extensions} added to to \l{View3D::extensions}{View3D}
53 then they will get their first chance to modify or react to the collected data here.
54 If the users have implemented the virtual function \l{QSSGRenderExtension::prepareData()}{prepareData} it will be
55 called after all active nodes have been collected and had their global data updated, but before any mesh or material
56 has been loaded.
57
58 4. \l{QSSGRenderer::rhiPrepare()} - Starts rendering necessary sub-scenes and prepare resources.
59 Sub-scenes, or sub-passes that are to be done in full, will be done at this stage.
60
61 5. \l{QSSGRenderer::rhiRender()} - Renders the scene to the main target.
62
63 6. \l{QSSGRenderer::endFrame()} - Marks the frame as done and cleans-up dirty states and
64 uneeded resources.
65*/
66
67QT_BEGIN_NAMESPACE
68
69struct QSSGRenderableImage;
70struct QSSGSubsetRenderable;
71
72void QSSGRenderer::releaseCachedResources()
73{
74 m_rhiQuadRenderer.reset();
75 m_rhiCubeRenderer.reset();
76}
77
78QSSGRenderer::QSSGRenderer() = default;
79
80QSSGRenderer::~QSSGRenderer()
81{
82 m_contextInterface = nullptr;
83 releaseCachedResources();
84}
85
86void QSSGRenderer::cleanupUnreferencedBuffers(QSSGRenderLayer *inLayer)
87{
88 // Now check for unreferenced buffers and release them if necessary
89 m_contextInterface->bufferManager()->cleanupUnreferencedBuffers(frameId: m_frameCount, layer: inLayer);
90}
91
92void QSSGRenderer::resetResourceCounters(QSSGRenderLayer *inLayer)
93{
94 m_contextInterface->bufferManager()->resetUsageCounters(frameId: m_frameCount, layer: inLayer);
95}
96
97bool QSSGRenderer::prepareLayerForRender(QSSGRenderLayer &inLayer)
98{
99 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
100 Q_ASSERT(theRenderData);
101 beginLayerRender(inLayer&: *theRenderData);
102 theRenderData->resetForFrame();
103 theRenderData->prepareForRender();
104 endLayerRender();
105 return theRenderData->layerPrepResult.flags.wasDirty();
106}
107
108// Phase 1: prepare. Called when the renderpass is not yet started on the command buffer.
109void QSSGRenderer::rhiPrepare(QSSGRenderLayer &inLayer)
110{
111 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
112 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
113
114 const auto layerPrepResult = theRenderData->layerPrepResult;
115 if (layerPrepResult.isLayerVisible()) {
116 ///
117 QSSGRhiContext *rhiCtx = contextInterface()->rhiContext().get();
118 QSSG_ASSERT(rhiCtx->isValid() && rhiCtx->rhi()->isRecordingFrame(), return);
119 theRenderData->maybeBakeLightmap();
120 beginLayerRender(inLayer&: *theRenderData);
121 // Process active passes. "PreMain" passes are individual passes
122 // that does can and should be done in the rhi prepare phase.
123 // It is assumed that passes are sorted in the list with regards to
124 // execution order.
125 const auto &activePasses = theRenderData->activePasses;
126 for (const auto &pass : activePasses) {
127 pass->renderPrep(renderer&: *this, data&: *theRenderData);
128 if (pass->passType() == QSSGRenderPass::Type::Standalone)
129 pass->renderPass(renderer&: *this);
130 }
131
132 endLayerRender();
133 }
134}
135
136// Phase 2: render. Called within an active renderpass on the command buffer.
137void QSSGRenderer::rhiRender(QSSGRenderLayer &inLayer)
138{
139 QSSGLayerRenderData *theRenderData = getOrCreateLayerRenderData(layer&: inLayer);
140 QSSG_ASSERT(theRenderData && !theRenderData->renderedCameras.isEmpty(), return);
141 if (theRenderData->layerPrepResult.isLayerVisible()) {
142 beginLayerRender(inLayer&: *theRenderData);
143 const auto &activePasses = theRenderData->activePasses;
144 for (const auto &pass : activePasses) {
145 if (pass->passType() == QSSGRenderPass::Type::Main || pass->passType() == QSSGRenderPass::Type::Extension)
146 pass->renderPass(renderer&: *this);
147 }
148 endLayerRender();
149 }
150}
151
152template<typename Container>
153static void cleanupResourcesImpl(const QSSGRenderContextInterface &rci, const Container &resources)
154{
155 const auto &rhiCtx = rci.rhiContext();
156 if (!rhiCtx->isValid())
157 return;
158
159 const auto &bufferManager = rci.bufferManager();
160
161 for (const auto &resource : resources) {
162 if (resource->type == QSSGRenderGraphObject::Type::Geometry) {
163 auto geometry = static_cast<QSSGRenderGeometry*>(resource);
164 bufferManager->releaseGeometry(geometry);
165 } else if (resource->type == QSSGRenderGraphObject::Type::Model) {
166 auto model = static_cast<QSSGRenderModel*>(resource);
167 QSSGRhiContextPrivate::get(q: rhiCtx.get())->cleanupDrawCallData(model);
168 delete model->particleBuffer;
169 } else if (resource->type == QSSGRenderGraphObject::Type::TextureData || resource->type == QSSGRenderGraphObject::Type::Skin) {
170 static_assert(std::is_base_of_v<QSSGRenderTextureData, QSSGRenderSkin>, "QSSGRenderSkin is expected to be a QSSGRenderTextureData type!");
171 auto textureData = static_cast<QSSGRenderTextureData *>(resource);
172 bufferManager->releaseTextureData(data: textureData);
173 } else if (resource->type == QSSGRenderGraphObject::Type::RenderExtension) {
174 auto *rext = static_cast<QSSGRenderExtension *>(resource);
175 bufferManager->releaseExtensionResult(rext: *rext);
176 } else if (resource->type == QSSGRenderGraphObject::Type::ModelInstance) {
177 auto *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx.get());
178 auto *table = static_cast<QSSGRenderInstanceTable *>(resource);
179 rhiCtxD->releaseInstanceBuffer(instanceTable: table);
180 }
181
182 // ### There might be more types that need to be supported
183
184 delete resource;
185 }
186}
187
188void QSSGRenderer::cleanupResources(QList<QSSGRenderGraphObject *> &resources)
189{
190 cleanupResourcesImpl(rci: *m_contextInterface, resources);
191 resources.clear();
192}
193
194void QSSGRenderer::cleanupResources(QSet<QSSGRenderGraphObject *> &resources)
195{
196 cleanupResourcesImpl(rci: *m_contextInterface, resources);
197 resources.clear();
198}
199
200QSSGLayerRenderData *QSSGRenderer::getOrCreateLayerRenderData(QSSGRenderLayer &layer)
201{
202 if (layer.renderData == nullptr)
203 layer.renderData = new QSSGLayerRenderData(layer, *this);
204
205 return layer.renderData;
206}
207
208void QSSGRenderer::addMaterialDirtyClear(QSSGRenderGraphObject *material)
209{
210 m_materialClearDirty.insert(value: material);
211}
212
213static QByteArray logPrefix() { return QByteArrayLiteral("mesh default material pipeline-- "); }
214
215
216QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipelineImpl(QSSGSubsetRenderable &renderable,
217 QSSGShaderLibraryManager &shaderLibraryManager,
218 QSSGShaderCache &shaderCache,
219 QSSGProgramGenerator &shaderProgramGenerator,
220 const QSSGShaderDefaultMaterialKeyProperties &shaderKeyProperties,
221 const QSSGShaderFeatures &featureSet,
222 QByteArray &shaderString)
223{
224 shaderString = logPrefix();
225 QSSGShaderDefaultMaterialKey theKey(renderable.shaderDescription);
226
227 // This is not a cheap operation. This function assumes that it will not be
228 // hit for every material for every model in every frame (except of course
229 // for materials that got changed). In practice this is ensured by the
230 // cheaper-to-lookup cache in getShaderPipelineForDefaultMaterial().
231 theKey.toString(ioString&: shaderString, inProperties: shaderKeyProperties);
232
233 // Check the in-memory, per-QSSGShaderCache (and so per-QQuickWindow)
234 // runtime cache. That may get cleared upon an explicit call to
235 // QQuickWindow::releaseResources(), but will otherwise store all
236 // encountered shader pipelines in any View3D in the window.
237 if (const auto &maybePipeline = shaderCache.tryGetRhiShaderPipeline(inKey: shaderString, inFeatures: featureSet))
238 return maybePipeline;
239
240 // Check if there's a pre-built (offline generated) shader for available.
241 const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: shaderString, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: featureSet));
242 const QQsbCollection::EntryMap &pregenEntries = shaderLibraryManager.m_preGeneratedShaderEntries;
243 if (!pregenEntries.isEmpty()) {
244 const auto foundIt = pregenEntries.constFind(value: QQsbCollection::Entry(qsbcKey));
245 if (foundIt != pregenEntries.cend())
246 return shaderCache.newPipelineFromPregenerated(inKey: shaderString, inFeatures: featureSet, entry: *foundIt, obj: renderable.material);
247 }
248
249 // Try the persistent (disk-based) cache then.
250 if (const auto &maybePipeline = shaderCache.tryNewPipelineFromPersistentCache(qsbcKey, inKey: shaderString, inFeatures: featureSet))
251 return maybePipeline;
252
253 // Otherwise, build new shader code and run the resulting shaders through
254 // the shader conditioning pipeline.
255 const auto &material = static_cast<const QSSGRenderDefaultMaterial &>(renderable.getMaterial());
256 QSSGMaterialVertexPipeline vertexPipeline(shaderProgramGenerator,
257 shaderKeyProperties,
258 material.adapter);
259
260 return QSSGMaterialShaderGenerator::generateMaterialRhiShader(inShaderKeyPrefix: logPrefix(),
261 vertexGenerator&: vertexPipeline,
262 key: renderable.shaderDescription,
263 inProperties: shaderKeyProperties,
264 inFeatureSet: featureSet,
265 inMaterial: renderable.material,
266 inLights: renderable.lights,
267 inFirstImage: renderable.firstImage,
268 shaderLibraryManager,
269 theCache&: shaderCache);
270}
271
272QSSGRhiShaderPipelinePtr QSSGRendererPrivate::generateRhiShaderPipeline(QSSGRenderer &renderer,
273 QSSGSubsetRenderable &inRenderable,
274 const QSSGShaderFeatures &inFeatureSet)
275{
276 auto &m_currentLayer = renderer.m_currentLayer;
277 auto &m_generatedShaderString = renderer.m_generatedShaderString;
278 const auto &m_contextInterface = renderer.m_contextInterface;
279 const auto &theCache = m_contextInterface->shaderCache();
280 const auto &shaderProgramGenerator = m_contextInterface->shaderProgramGenerator();
281 const auto &shaderLibraryManager = m_contextInterface->shaderLibraryManager();
282 return QSSGRendererPrivate::generateRhiShaderPipelineImpl(renderable&: inRenderable, shaderLibraryManager&: *shaderLibraryManager, shaderCache&: *theCache, shaderProgramGenerator&: *shaderProgramGenerator, shaderKeyProperties: m_currentLayer->defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: m_generatedShaderString);
283}
284
285void QSSGRenderer::beginFrame(QSSGRenderLayer &layer, bool allowRecursion)
286{
287 const bool executeBeginFrame = !(allowRecursion && (m_activeFrameRef++ != 0));
288 if (executeBeginFrame) {
289 m_contextInterface->perFrameAllocator()->reset();
290 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), start(&layer));
291 resetResourceCounters(inLayer: &layer);
292 }
293}
294
295bool QSSGRenderer::endFrame(QSSGRenderLayer &layer, bool allowRecursion)
296{
297 const bool executeEndFrame = !(allowRecursion && (--m_activeFrameRef != 0));
298 if (executeEndFrame) {
299 cleanupUnreferencedBuffers(inLayer: &layer);
300
301 // We need to do this endFrame(), as the material nodes might not exist after this!
302 for (auto *matObj : std::as_const(t&: m_materialClearDirty)) {
303 if (matObj->type == QSSGRenderGraphObject::Type::CustomMaterial) {
304 static_cast<QSSGRenderCustomMaterial *>(matObj)->clearDirty();
305 } else if (matObj->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
306 matObj->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
307 matObj->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
308 static_cast<QSSGRenderDefaultMaterial *>(matObj)->clearDirty();
309 }
310 }
311 m_materialClearDirty.clear();
312
313 QSSGRHICTX_STAT(m_contextInterface->rhiContext().get(), stop(&layer));
314
315 ++m_frameCount;
316 }
317
318 return executeEndFrame;
319}
320
321QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickAll(const QSSGRenderContextInterface &ctx,
322 const QSSGRenderLayer &layer,
323 const QSSGRenderRay &ray)
324{
325 const auto &bufferManager = ctx.bufferManager();
326 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer());
327 PickResultList pickResults;
328 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
329 getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults);
330 // Things are rendered in a particular order and we need to respect that ordering.
331 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
332 return lhs.m_distanceSq < rhs.m_distanceSq;
333 });
334 return pickResults;
335}
336
337QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPick(const QSSGRenderContextInterface &ctx,
338 const QSSGRenderLayer &layer,
339 const QSSGRenderRay &ray,
340 QSSGRenderNode *target)
341{
342 const auto &bufferManager = ctx.bufferManager();
343 const bool isGlobalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(renderer: *ctx.renderer());
344
345 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
346 PickResultList pickResults;
347 if (target)
348 intersectRayWithSubsetRenderable(bufferManager&: *bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults);
349 else
350 getLayerHitObjectList(layer, bufferManager&: *bufferManager, ray, inPickEverything: isGlobalPickingEnabled, outIntersectionResult&: pickResults);
351
352 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
353 return lhs.m_distanceSq < rhs.m_distanceSq;
354 });
355 return pickResults;
356}
357
358QSSGRendererPrivate::PickResultList QSSGRendererPrivate::syncPickSubset(const QSSGRenderLayer &layer,
359 QSSGBufferManager &bufferManager,
360 const QSSGRenderRay &ray,
361 QVarLengthArray<QSSGRenderNode*> subset)
362{
363 QSSGRendererPrivate::PickResultList pickResults;
364 Q_ASSERT(layer.getGlobalState(QSSGRenderNode::GlobalState::Active));
365
366 for (auto target : subset)
367 intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *target, outIntersectionResultList&: pickResults);
368
369 std::stable_sort(first: pickResults.begin(), last: pickResults.end(), comp: [](const QSSGRenderPickResult &lhs, const QSSGRenderPickResult &rhs) {
370 return lhs.m_distanceSq < rhs.m_distanceSq;
371 });
372 return pickResults;
373}
374
375void QSSGRendererPrivate::setGlobalPickingEnabled(QSSGRenderer &renderer, bool isEnabled)
376{
377 renderer.m_globalPickingEnabled = isEnabled;
378}
379
380void QSSGRendererPrivate::setRenderContextInterface(QSSGRenderer &renderer, QSSGRenderContextInterface *ctx)
381{
382 renderer.m_contextInterface = ctx;
383}
384
385const std::unique_ptr<QSSGRhiQuadRenderer> &QSSGRenderer::rhiQuadRenderer() const
386{
387 if (!m_rhiQuadRenderer)
388 m_rhiQuadRenderer = std::make_unique<QSSGRhiQuadRenderer>();
389
390 return m_rhiQuadRenderer;
391}
392
393const std::unique_ptr<QSSGRhiCubeRenderer> &QSSGRenderer::rhiCubeRenderer() const
394{
395 if (!m_rhiCubeRenderer)
396 m_rhiCubeRenderer = std::make_unique<QSSGRhiCubeRenderer>();
397
398 return m_rhiCubeRenderer;
399
400}
401
402void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
403{
404 m_currentLayer = &inLayer;
405}
406void QSSGRenderer::endLayerRender()
407{
408 m_currentLayer = nullptr;
409}
410
411using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
412static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
413{
414 if (QSSGRenderGraphObject::isRenderable(type: node.type))
415 renderables.push_back(t: &node);
416
417 for (const auto &child : node.children)
418 dfs(node: child, renderables);
419}
420
421void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer,
422 QSSGBufferManager &bufferManager,
423 const QSSGRenderRay &ray,
424 bool inPickEverything,
425 PickResultList &outIntersectionResult)
426{
427 RenderableList renderables;
428 for (const auto &childNode : layer.children)
429 dfs(node: childNode, renderables);
430
431 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
432 const auto &pickableObject = renderables.at(idx);
433 if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable))
434 intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult);
435 }
436}
437
438void QSSGRendererPrivate::intersectRayWithSubsetRenderable(QSSGBufferManager &bufferManager,
439 const QSSGRenderRay &inRay,
440 const QSSGRenderNode &node,
441 PickResultList &outIntersectionResultList)
442{
443 // Item2D's requires special handling
444 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
445 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
446 intersectRayWithItem2D(inRay, item2D, outIntersectionResultList);
447 return;
448 }
449
450 if (node.type != QSSGRenderGraphObject::Type::Model)
451 return;
452
453 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
454
455 // We have to have a guard here, as the meshes are usually loaded on the render thread,
456 // and we assume all meshes are loaded before picking and none are removed, which
457 // is usually true, except for custom geometry which can be updated at any time. So this
458 // guard should really only be locked whenever a custom geometry buffer is being updated
459 // on the render thread. Still naughty though because this can block the render thread.
460 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
461 auto mesh = bufferManager.getMeshForPicking(model);
462 if (!mesh)
463 return;
464
465 const auto &subMeshes = mesh->subsets;
466 QSSGBounds3 modelBounds;
467 for (const auto &subMesh : subMeshes)
468 modelBounds.include(b: subMesh.bounds);
469
470 if (modelBounds.isEmpty())
471 return;
472
473 const bool instancing = model.instancing(); // && instancePickingEnabled
474 int instanceCount = instancing ? model.instanceTable->count() : 1;
475
476 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
477
478 QMatrix4x4 modelTransform;
479 if (instancing) {
480 modelTransform = model.globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * model.localInstanceTransform;
481 } else {
482 modelTransform = model.globalTransform;
483 }
484 auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay);
485
486 auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds);
487
488 // If we don't intersect with the model at all, then there's no need to go furher down!
489 if (!hit.intersects())
490 continue;
491
492 // Check each submesh to find the closest intersection point
493 float minRayLength = std::numeric_limits<float>::max();
494 QSSGRenderRay::IntersectionResult intersectionResult;
495 QVector<QSSGRenderRay::IntersectionResult> results;
496
497 int subset = 0;
498 int resultSubset = 0;
499 for (const auto &subMesh : subMeshes) {
500 QSSGRenderRay::IntersectionResult result;
501 if (!subMesh.bvhRoot.isNull()) {
502 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData);
503 if (hit.intersects()) {
504 results.clear();
505 inRay.intersectWithBVH(data: rayData, bvh: static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, intersections&: results);
506 float subMeshMinRayLength = std::numeric_limits<float>::max();
507 for (const auto &subMeshResult : std::as_const(t&: results)) {
508 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
509 result = subMeshResult;
510 subMeshMinRayLength = result.rayLengthSquared;
511 }
512 }
513 }
514 } else {
515 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds);
516 if (hit.intersects())
517 result = QSSGRenderRay::createIntersectionResult(data: rayData, hit);
518 }
519 if (result.intersects && result.rayLengthSquared < minRayLength) {
520 intersectionResult = result;
521 minRayLength = intersectionResult.rayLengthSquared;
522 resultSubset = subset;
523 }
524 subset++;
525 }
526
527 if (intersectionResult.intersects)
528 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model,
529 .m_distanceSq: intersectionResult.rayLengthSquared,
530 .m_localUVCoords: intersectionResult.relXY,
531 .m_scenePosition: intersectionResult.scenePosition,
532 .m_localPosition: intersectionResult.localPosition,
533 .m_faceNormal: intersectionResult.faceNormal,
534 .m_subset: resultSubset,
535 .m_instanceIndex: instanceIndex
536 });
537 }
538}
539
540void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderRay &inRay, const QSSGRenderItem2D &item2D, PickResultList &outIntersectionResultList)
541{
542 // Get the plane (and normal) that the item 2D is on
543 const QVector3D p0 = item2D.getGlobalPos();
544 const QVector3D normal = -item2D.getDirection();
545
546 const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal);
547 float intersectionTime = 0;
548 if (d > 1e-6f) {
549 const QVector3D p0l0 = p0 - inRay.origin;
550 intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d;
551 if (intersectionTime >= 0) {
552 // Intersection
553 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
554 const QMatrix4x4 inverseGlobalTransform = item2D.globalTransform.inverted();
555 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint);
556 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
557 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D,
558 .m_distanceSq: intersectionTime * intersectionTime,
559 .m_localUVCoords: qmlCoordinate,
560 .m_scenePosition: intersectionPoint,
561 .m_localPosition: localIntersectionPoint,
562 .m_faceNormal: -normal });
563 }
564 }
565}
566
567QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer,
568 QSSGSubsetRenderable &inRenderable,
569 const QSSGShaderFeatures &inFeatureSet)
570{
571 auto *m_currentLayer = renderer.m_currentLayer;
572 QSSG_ASSERT(m_currentLayer != nullptr, return {});
573
574 // This function is the main entry point for retrieving the shaders for a
575 // default material, and is called for every material for every model in
576 // every frame. Therefore, like with custom materials, employ a first level
577 // cache (a simple hash table), with a key that's quick to
578 // generate/hash/compare. Even though there are other levels of caching in
579 // the components that get invoked from here, those may not be suitable
580 // performance wise. So bail out right here as soon as possible.
581 auto &shaderMap = m_currentLayer->shaderMap;
582
583 QElapsedTimer timer;
584 timer.start();
585
586 QSSGRhiShaderPipelinePtr shaderPipeline;
587
588 // This just references inFeatureSet and inRenderable.shaderDescription -
589 // cheap to construct and is good enough for the find()
590 QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(),
591 inFeatureSet,
592 inRenderable.shaderDescription);
593 auto it = shaderMap.find(key: skey);
594 if (it == shaderMap.end()) {
595 Q_TRACE_SCOPE(QSSG_generateShader);
596 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
597 shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet);
598 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
599 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
600 skey.detach();
601 // insert it no matter what, no point in trying over and over again
602 shaderMap.insert(key: skey, value: shaderPipeline);
603 } else {
604 shaderPipeline = it.value();
605 }
606
607 if (shaderPipeline != nullptr) {
608 if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty())
609 m_currentLayer->ensureCachedCameraDatas();
610 }
611
612 const auto &rhiContext = renderer.m_contextInterface->rhiContext();
613 QSSGRhiContextStats::get(rhiCtx&: *rhiContext).registerMaterialShaderGenerationTime(ms: timer.elapsed());
614
615 return shaderPipeline;
616}
617
618QT_END_NAMESPACE
619

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