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