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