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