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 *currentLayer = renderer.m_currentLayer;
277 auto &generatedShaderString = currentLayer->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: currentLayer->defaultMaterialShaderKeyProperties, featureSet: inFeatureSet, shaderString&: 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::beginSubLayerRender(QSSGLayerRenderData &inLayer)
403{
404 inLayer.saveRenderState(renderer: *this);
405 m_currentLayer = nullptr;
406}
407
408void QSSGRenderer::endSubLayerRender(QSSGLayerRenderData &inLayer)
409{
410 inLayer.restoreRenderState(renderer&: *this);
411 m_currentLayer = &inLayer;
412}
413
414void QSSGRenderer::beginLayerRender(QSSGLayerRenderData &inLayer)
415{
416 m_currentLayer = &inLayer;
417}
418void QSSGRenderer::endLayerRender()
419{
420 m_currentLayer = nullptr;
421}
422
423using RenderableList = QVarLengthArray<const QSSGRenderNode *>;
424static void dfs(const QSSGRenderNode &node, RenderableList &renderables)
425{
426 if (QSSGRenderGraphObject::isRenderable(type: node.type))
427 renderables.push_back(t: &node);
428
429 for (const auto &child : node.children)
430 dfs(node: child, renderables);
431}
432
433void QSSGRendererPrivate::getLayerHitObjectList(const QSSGRenderLayer &layer,
434 QSSGBufferManager &bufferManager,
435 const QSSGRenderRay &ray,
436 bool inPickEverything,
437 PickResultList &outIntersectionResult)
438{
439 RenderableList renderables;
440 for (const auto &childNode : layer.children)
441 dfs(node: childNode, renderables);
442
443 for (int idx = renderables.size() - 1; idx >= 0; --idx) {
444 const auto &pickableObject = renderables.at(idx);
445 if (inPickEverything || pickableObject->getLocalState(stateFlag: QSSGRenderNode::LocalState::Pickable))
446 intersectRayWithSubsetRenderable(bufferManager, inRay: ray, node: *pickableObject, outIntersectionResultList&: outIntersectionResult);
447 }
448}
449
450void QSSGRendererPrivate::intersectRayWithSubsetRenderable(QSSGBufferManager &bufferManager,
451 const QSSGRenderRay &inRay,
452 const QSSGRenderNode &node,
453 PickResultList &outIntersectionResultList)
454{
455 // Item2D's requires special handling
456 if (node.type == QSSGRenderGraphObject::Type::Item2D) {
457 const QSSGRenderItem2D &item2D = static_cast<const QSSGRenderItem2D &>(node);
458 intersectRayWithItem2D(inRay, item2D, outIntersectionResultList);
459 return;
460 }
461
462 if (node.type != QSSGRenderGraphObject::Type::Model)
463 return;
464
465 const QSSGRenderModel &model = static_cast<const QSSGRenderModel &>(node);
466
467 // We have to have a guard here, as the meshes are usually loaded on the render thread,
468 // and we assume all meshes are loaded before picking and none are removed, which
469 // is usually true, except for custom geometry which can be updated at any time. So this
470 // guard should really only be locked whenever a custom geometry buffer is being updated
471 // on the render thread. Still naughty though because this can block the render thread.
472 QMutexLocker mutexLocker(bufferManager.meshUpdateMutex());
473 auto mesh = bufferManager.getMeshForPicking(model);
474 if (!mesh)
475 return;
476
477 const auto &subMeshes = mesh->subsets;
478 QSSGBounds3 modelBounds;
479 for (const auto &subMesh : subMeshes)
480 modelBounds.include(b: subMesh.bounds);
481
482 if (modelBounds.isEmpty())
483 return;
484
485 const bool instancing = model.instancing(); // && instancePickingEnabled
486 int instanceCount = instancing ? model.instanceTable->count() : 1;
487
488 for (int instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
489
490 QMatrix4x4 modelTransform;
491 if (instancing) {
492 modelTransform = model.globalInstanceTransform * model.instanceTable->getTransform(index: instanceIndex) * model.localInstanceTransform;
493 } else {
494 modelTransform = model.globalTransform;
495 }
496 auto rayData = QSSGRenderRay::createRayData(globalTransform: modelTransform, ray: inRay);
497
498 auto hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: modelBounds);
499
500 // If we don't intersect with the model at all, then there's no need to go furher down!
501 if (!hit.intersects())
502 continue;
503
504 // Check each submesh to find the closest intersection point
505 float minRayLength = std::numeric_limits<float>::max();
506 QSSGRenderRay::IntersectionResult intersectionResult;
507 QVector<QSSGRenderRay::IntersectionResult> results;
508
509 int subset = 0;
510 int resultSubset = 0;
511 for (const auto &subMesh : subMeshes) {
512 QSSGRenderRay::IntersectionResult result;
513 if (!subMesh.bvhRoot.isNull()) {
514 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bvhRoot->boundingData);
515 if (hit.intersects()) {
516 results.clear();
517 inRay.intersectWithBVH(data: rayData, bvh: static_cast<const QSSGMeshBVHNode *>(subMesh.bvhRoot), mesh, intersections&: results);
518 float subMeshMinRayLength = std::numeric_limits<float>::max();
519 for (const auto &subMeshResult : std::as_const(t&: results)) {
520 if (subMeshResult.rayLengthSquared < subMeshMinRayLength) {
521 result = subMeshResult;
522 subMeshMinRayLength = result.rayLengthSquared;
523 }
524 }
525 }
526 } else {
527 hit = QSSGRenderRay::intersectWithAABBv2(data: rayData, bounds: subMesh.bounds);
528 if (hit.intersects())
529 result = QSSGRenderRay::createIntersectionResult(data: rayData, hit);
530 }
531 if (result.intersects && result.rayLengthSquared < minRayLength) {
532 intersectionResult = result;
533 minRayLength = intersectionResult.rayLengthSquared;
534 resultSubset = subset;
535 }
536 subset++;
537 }
538
539 if (intersectionResult.intersects)
540 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &model,
541 .m_distanceSq: intersectionResult.rayLengthSquared,
542 .m_localUVCoords: intersectionResult.relXY,
543 .m_scenePosition: intersectionResult.scenePosition,
544 .m_localPosition: intersectionResult.localPosition,
545 .m_faceNormal: intersectionResult.faceNormal,
546 .m_subset: resultSubset,
547 .m_instanceIndex: instanceIndex
548 });
549 }
550}
551
552void QSSGRendererPrivate::intersectRayWithItem2D(const QSSGRenderRay &inRay, const QSSGRenderItem2D &item2D, PickResultList &outIntersectionResultList)
553{
554 // Get the plane (and normal) that the item 2D is on
555 const QVector3D p0 = item2D.getGlobalPos();
556 const QVector3D normal = -item2D.getDirection();
557
558 const float d = QVector3D::dotProduct(v1: inRay.direction, v2: normal);
559 float intersectionTime = 0;
560 if (d > 1e-6f) {
561 const QVector3D p0l0 = p0 - inRay.origin;
562 intersectionTime = QVector3D::dotProduct(v1: p0l0, v2: normal) / d;
563 if (intersectionTime >= 0) {
564 // Intersection
565 const QVector3D intersectionPoint = inRay.origin + inRay.direction * intersectionTime;
566 const QMatrix4x4 inverseGlobalTransform = item2D.globalTransform.inverted();
567 const QVector3D localIntersectionPoint = QSSGUtils::mat44::transform(m: inverseGlobalTransform, v: intersectionPoint);
568 const QVector2D qmlCoordinate(localIntersectionPoint.x(), -localIntersectionPoint.y());
569 outIntersectionResultList.push_back(t: QSSGRenderPickResult { .m_hitObject: &item2D,
570 .m_distanceSq: intersectionTime * intersectionTime,
571 .m_localUVCoords: qmlCoordinate,
572 .m_scenePosition: intersectionPoint,
573 .m_localPosition: localIntersectionPoint,
574 .m_faceNormal: -normal });
575 }
576 }
577}
578
579QSSGRhiShaderPipelinePtr QSSGRendererPrivate::getShaderPipelineForDefaultMaterial(QSSGRenderer &renderer,
580 QSSGSubsetRenderable &inRenderable,
581 const QSSGShaderFeatures &inFeatureSet)
582{
583 auto *m_currentLayer = renderer.m_currentLayer;
584 QSSG_ASSERT(m_currentLayer != nullptr, return {});
585
586 // This function is the main entry point for retrieving the shaders for a
587 // default material, and is called for every material for every model in
588 // every frame. Therefore, like with custom materials, employ a first level
589 // cache (a simple hash table), with a key that's quick to
590 // generate/hash/compare. Even though there are other levels of caching in
591 // the components that get invoked from here, those may not be suitable
592 // performance wise. So bail out right here as soon as possible.
593 auto &shaderMap = m_currentLayer->shaderMap;
594
595 QElapsedTimer timer;
596 timer.start();
597
598 QSSGRhiShaderPipelinePtr shaderPipeline;
599
600 // This just references inFeatureSet and inRenderable.shaderDescription -
601 // cheap to construct and is good enough for the find()
602 QSSGShaderMapKey skey = QSSGShaderMapKey(QByteArray(),
603 inFeatureSet,
604 inRenderable.shaderDescription);
605 auto it = shaderMap.find(key: skey);
606 if (it == shaderMap.end()) {
607 Q_TRACE_SCOPE(QSSG_generateShader);
608 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
609 shaderPipeline = QSSGRendererPrivate::generateRhiShaderPipeline(renderer, inRenderable, inFeatureSet);
610 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inRenderable.material.profilingId);
611 // make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
612 skey.detach();
613 // insert it no matter what, no point in trying over and over again
614 shaderMap.insert(key: skey, value: shaderPipeline);
615 } else {
616 shaderPipeline = it.value();
617 }
618
619 if (shaderPipeline != nullptr) {
620 if (m_currentLayer && !m_currentLayer->renderedCameras.isEmpty())
621 m_currentLayer->ensureCachedCameraDatas();
622 }
623
624 const auto &rhiContext = renderer.m_contextInterface->rhiContext();
625 QSSGRhiContextStats::get(rhiCtx&: *rhiContext).registerMaterialShaderGenerationTime(ms: timer.elapsed());
626
627 return shaderPipeline;
628}
629
630QT_END_NAMESPACE
631

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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