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
qssgrenderdata.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#if QT_CONFIG(thread)
9#include <QtCore/qthreadpool.h>
10#endif // QT_CONFIG(thread)
11
12#include <QtCore/qtenvironmentvariables.h>
13
14#include <QtQuick/private/qsgcontext_p.h>
15#include <QtQuick/private/qsgrenderer_p.h>
16
17#include "graphobjects/qssgrenderroot_p.h"
18#include "graphobjects/qssgrenderlayer_p.h"
19#include "graphobjects/qssgrenderitem2d_p.h"
21#include "qssgrenderer_p.h"
22#include "resourcemanager/qssgrenderbuffermanager_p.h"
23
24QT_BEGIN_NAMESPACE
25
26Q_STATIC_LOGGING_CATEGORY(lcLayerData, "qt.quick3d.render.layerdata")
27
28static void reindexChildNodes(QSSGRenderNode &node, const QSSGRenderNodeVersionType version, quint32 &dfsIdx, size_t &count)
29{
30 Q_ASSERT(node.type != QSSGRenderNode::Type::Layer);
31 if (node.type != QSSGRenderNode::Type::Layer) {
32 // Note: In the case of import scenes the version and index might already
33 // have been set. We therefore assume nodes with same version is already
34 // indexed.
35 if (node.h.version() != version)
36 node.h = QSSGRenderNodeHandle(0, version, dfsIdx);
37 for (QSSGRenderNode &chld : node.children)
38 reindexChildNodes(chld, version, ++dfsIdx, ++count);
39 }
40}
41
42static void reindexLayerChildNodes(QSSGRenderLayer &layer, const QSSGRenderNodeVersionType version, quint32 &dfsIdx, size_t &count)
43{
44 Q_ASSERT(layer.type == QSSGRenderNode::Type::Layer);
45 if (layer.type == QSSGRenderNode::Type::Layer) {
46 layer.h = QSSGRenderNodeHandle(0, version, 0); // Layer nodes are always indexed at 0;
47 for (QSSGRenderNode &chld : layer.children)
48 reindexChildNodes(chld, version, ++dfsIdx, ++count);
49 }
50}
51
52static void reindex(QSSGRenderRoot *rootNode, const QSSGRenderNodeVersionType version, quint32 &dfsIdx, size_t &count)
53{
54 if (rootNode) {
55 Q_ASSERT(rootNode->type == QSSGRenderNode::Type::Root);
56 Q_ASSERT(dfsIdx == 0);
57 rootNode->h = QSSGRenderNodeHandle(0, version, 0);
58 for (QSSGRenderNode &chld : rootNode->children) // These should be layer nodes
59 reindexLayerChildNodes(static_cast<QSSGRenderLayer &>(chld), version, dfsIdx, count);
60 }
61}
62
63enum class Insert { Back, Indexed };
64template <Insert insert = Insert::Back>
65static void collectChildNodesFirst(QSSGRenderNode &node, QSSGGlobalRenderNodeData::NodeStore &outList, [[maybe_unused]] size_t &idx)
66{
67 Q_ASSERT_X(node.type != QSSGRenderNode::Type::Layer, Q_FUNC_INFO, "Unexpected Layer node in child list!");
68 if constexpr (insert == Insert::Indexed)
69 outList[idx++] = &node;
70 else
71 outList.push_back(&node);
72 for (QSSGRenderNode &chld : node.children)
73 collectChildNodesFirst<insert>(chld, outList, idx);
74}
75
76template <Insert insert = Insert::Back>
77static void collectChildNodesSecond(QSSGRenderNode &node, QSSGGlobalRenderNodeData::NodeStore &outList, [[maybe_unused]] size_t &idx)
78{
79 if (node.getGlobalState(QSSGRenderNode::GlobalState::Active)) {
80 if constexpr (insert == Insert::Indexed)
81 outList[idx++] = &node;
82 else
83 outList.push_back(&node);
84 for (QSSGRenderNode &chld : node.children)
85 collectChildNodesSecond<insert>(chld, outList, idx);
86 }
87}
88
89enum class Discard { None, Inactive };
90template <Discard discard = Discard::None, Insert insert = Insert::Back>
91static void collectLayerChildNodes(QSSGRenderLayer *layer, QSSGGlobalRenderNodeData::NodeStore &outList, [[maybe_unused]] size_t &idx)
92{
93 Q_ASSERT(layer->type == QSSGRenderNode::Type::Layer);
94 if (layer) {
95 for (QSSGRenderNode &chld : layer->children) {
96 if constexpr (discard == Discard::Inactive)
97 collectChildNodesSecond<insert>(chld, outList, idx);
98 else
99 collectChildNodesFirst<insert>(chld, outList, idx);
100 }
101 }
102
103 if constexpr (insert == Insert::Back)
104 idx = outList.size();
105}
106
107template <QSSGRenderDataHelpers::Strategy Strategy>
108static bool calcGlobalNodeDataIndexedImpl(QSSGRenderNode *node,
109 const QSSGRenderNodeVersionType version,
110 QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms,
111 QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
112{
113 using DirtyFlag = QSSGRenderNode::DirtyFlag;
114 using FlagT = QSSGRenderNode::FlagT;
115 constexpr DirtyFlag TransformAndOpacityDirty = DirtyFlag(FlagT(DirtyFlag::TransformDirty) | FlagT(DirtyFlag::OpacityDirty));
116
117 if (Q_UNLIKELY(!node || (node->h.version() != version)))
118 return false;
119
120 constexpr bool forcedRebuild = (Strategy == QSSGRenderDataHelpers::Strategy::Initial);
121 bool retval = forcedRebuild || node->isDirty(TransformAndOpacityDirty);
122
123 // NOTE 1: Nodes shared across windows will be marked with a stick dirty flag.
124 // Officially this isn't supported, but in practice it can happen so
125 // we try to be nice and try to keep things moving (literally).
126 // NOTE 2: We don't change the return value, as that would interfere with the progressive AA.
127 const bool alwaysDirty = node->isDirty(DirtyFlag::StickyDirty);
128
129 if (retval || alwaysDirty) {
130 const auto idx = node->h.index();
131
132 auto &globalTransform = globalTransforms[idx];
133 auto &globalOpacity = globalOpacities[idx];
134 globalOpacity = node->localOpacity;
135 globalTransform = node->localTransform;
136
137 if (QSSGRenderNode *parent = node->parent) {
138 const auto pidx = parent->h.index();
139 const auto pOpacity = globalOpacities[pidx];
140 globalOpacity *= pOpacity;
141
142 if (parent->type != QSSGRenderGraphObject::Type::Layer) {
143 const auto pTransform = globalTransforms[pidx];
144 globalTransform = pTransform * node->localTransform;
145 }
146 }
147 // Clear dirty flags (Transform, Opacity, Active, Pickable)
148 node->clearDirty(TransformAndOpacityDirty);
149 }
150
151 // We always clear dirty in a reasonable manner but if we aren't active
152 // there is no reason to tell the universe if we are dirty or not.
153 return retval;
154}
155
156QSSGRenderDataHelpers::GlobalStateResult QSSGRenderDataHelpers::updateGlobalNodeState(QSSGRenderNode *node, const VersionType version)
157{
158 using LocalState = QSSGRenderNode::LocalState;
159 using GlobalState = QSSGRenderNode::GlobalState;
160 using DirtyFlag = QSSGRenderNode::DirtyFlag;
161 using FlagT = QSSGRenderNode::FlagT;
162
163 constexpr DirtyFlag ClearDirtyMask = DirtyFlag(FlagT(DirtyFlag::ActiveDirty) | FlagT(DirtyFlag::PickableDirty) | FlagT(DirtyFlag::ImportDirty));
164
168
169 if (Q_UNLIKELY(!node || (node->h.version() != version)))
171
172 const bool activeDirty = node->isDirty(DirtyFlag::ActiveDirty);
173 const bool pickableDirty = node->isDirty(DirtyFlag::PickableDirty);
174 const bool importedDirty = node->isDirty(DirtyFlag::ImportDirty);
175 // There's nothing more to do if the tag is dirty, but we still need to clear the dirty flag.
176 const bool tagDirty = node->isDirty(DirtyFlag::TagDirty);
177 node->clearDirty(DirtyFlag::TagDirty);
178
179 const bool updateState = activeDirty || pickableDirty || importedDirty;
180
181 if (updateState) {
182 const QSSGRenderNode *parent = node->parent;
183 const bool hasParent = (parent != nullptr);
184 const bool globallyActive = node->getLocalState(LocalState::Active) && (!hasParent || parent->getGlobalState(GlobalState::Active));
185 node->flags = globallyActive ? (node->flags | FlagT(GlobalState::Active)) : (node->flags & ~FlagT(GlobalState::Active));
186 const bool globallyPickable = node->getLocalState(LocalState::Pickable) || (hasParent && parent->getGlobalState(GlobalState::Pickable));
187 node->flags = globallyPickable ? (node->flags | FlagT(GlobalState::Pickable)) : (node->flags & ~FlagT(GlobalState::Pickable));
188 const bool globallyImported = node->getLocalState(LocalState::Imported) || (hasParent && parent->getGlobalState(GlobalState::Imported));
189 node->flags = globallyImported ? (node->flags | FlagT(GlobalState::Imported)) : (node->flags & ~FlagT(GlobalState::Imported));
190
191 // Clear dirty flags (Active, Pickable, Imported)
192 node->clearDirty(ClearDirtyMask);
193 }
194
195 return GlobalStateResult(FlagT(activeChangeFlag[activeDirty])
196 | FlagT(pickableChangeFlag[pickableDirty])
197 | FlagT(tagChangeFlag[tagDirty]));
198}
199
200bool QSSGRenderDataHelpers::calcInstanceTransforms(QSSGRenderNode *node,
201 const VersionType version,
202 QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms,
203 QSSGGlobalRenderNodeData::InstanceTransformStore &instanceTransforms)
204{
205 if (Q_UNLIKELY(!node || (node->h.version() != version)))
206 return false;
207
208 constexpr bool retval = true;
209
210 // NOTE: We've already calculated the global states and transforms at this point
211 // so if the node isn't active we don't need to do anything.
212 // We're also assuming the node list is stored depth first order.
213 const auto idx = node->h.index();
214 QSSGRenderNode *parent = node->parent;
215 if (parent && parent->type != QSSGRenderGraphObject::Type::Layer && node->getLocalState(QSSGRenderNode::LocalState::Active)) {
216 const auto pidx = parent->h.index();
217 const auto &pGlobalTransform = globalTransforms[pidx];
218 QSSGRenderNode *instanceRoot = node->instanceRoot;
219 if (instanceRoot == node) {
220 instanceTransforms[idx] = { node->localTransform, pGlobalTransform };
221 } else if (instanceRoot) {
222 auto &[nodeInstanceLocalTransform, nodeInstanceGlobalTransform] = instanceTransforms[idx];
223 auto &[instanceRootLocalTransform, instanceRootGlobalTransform] = instanceTransforms[instanceRoot->h.index()];
224 nodeInstanceGlobalTransform = instanceRootGlobalTransform;
225 //### technically O(n^2) -- we could cache localInstanceTransform if every node in the
226 // tree is guaranteed to have the same instance root. That would require an API change.
227 nodeInstanceLocalTransform = node->localTransform;
228 auto *p = parent;
229 while (p) {
230 if (p == instanceRoot) {
231 nodeInstanceLocalTransform = instanceRootLocalTransform * nodeInstanceLocalTransform;
232 break;
233 }
234 nodeInstanceLocalTransform = p->localTransform * nodeInstanceLocalTransform;
235 p = p->parent;
236 }
237 } else {
238 // By default, we do magic: translation is applied to the global instance transform,
239 // while scale/rotation is local
240 QMatrix4x4 globalInstanceTransform = globalTransforms[pidx];
241 QMatrix4x4 localInstanceTransform = node->localTransform;
242 auto &localInstanceMatrix = *reinterpret_cast<float (*)[4][4]>(localInstanceTransform.data());
243 QVector3D localPos{localInstanceMatrix[3][0], localInstanceMatrix[3][1], localInstanceMatrix[3][2]};
244 localInstanceMatrix[3][0] = 0;
245 localInstanceMatrix[3][1] = 0;
246 localInstanceMatrix[3][2] = 0;
247 globalInstanceTransform = pGlobalTransform;
248 globalInstanceTransform.translate(localPos);
249 instanceTransforms[idx] = { localInstanceTransform, globalInstanceTransform };
250 }
251 } else {
252 instanceTransforms[idx] = { node->localTransform, {} };
253 }
254
255 return retval;
256}
257
258QSSGGlobalRenderNodeData::QSSGGlobalRenderNodeData(QSSGRenderRoot *root)
259#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
260 : m_threadPool(new QThreadPool)
261 , m_rootNode(root)
262#else
263 : m_rootNode(root)
264#endif // QT_CONFIG(thread)
265{
266
267}
268
269QSSGGlobalRenderNodeData::~QSSGGlobalRenderNodeData()
270{
271
272}
273
274void QSSGGlobalRenderNodeData::reindex()
275{
276 if (m_rootNode) {
277 quint32 dfsIdx = 0;
278 m_nodeCount = 0;
279
280 // Bump the version to invalidate all existing handles. This ensures nodes don't start
281 // accessing stale data.
282 ++m_version;
283
284 // Version 0 means "not yet indexed" (default-constructed handle), so skip it.
285 // NOTE: This can happen if the version wraps around.
286 if (Q_UNLIKELY(m_version == 0)) {
287 qCDebug(lcLayerData) << "Version wrap around detected, resetting to 1.";
288 ++m_version;
289 }
290
291 ::reindex(m_rootNode, m_version, dfsIdx, m_nodeCount);
292
293 // Actual storage size (Some nodes, like the layers, will all use index 0).
294 // NOTE: This can differ from the node count, as nodes are collected for each
295 // layer. Since layers can reference nodes outside their view the node list
296 // will contain duplicate nodes when import scene is used!
297 m_size = dfsIdx + 1;
298
299 globalTransforms.resize(m_size, QMatrix4x4{ Qt::Uninitialized });
300 globalOpacities.resize(m_size, 1.0f);
301 instanceTransforms.resize(m_size, { QMatrix4x4{ Qt::Uninitialized }, QMatrix4x4{ Qt::Uninitialized } });
302 normalMatrices.resize(m_size, QMatrix3x3{});
303 meshes.resize(m_size, nullptr);
304 materials.resize(m_size, {});
305
306 collectNodes(m_rootNode);
307 // NOTE: If the tree was dirty we force a full rebuild of the global transforms etc. since
308 // the stored data is invalid for the new index order.
309 updateGlobalState();
310 }
311}
312
313void QSSGGlobalRenderNodeData::invalidate()
314{
315 m_rootNode = nullptr;
316}
317
318QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(QSSGRenderNodeHandle h, const QMatrix4x4 &defaultValue) const
319{
320 // Ensure we have an valid index.
321 const bool hasId = h.hasId();
322 const bool validVersion = hasId && (h.version() == m_version);
323 const auto index = h.index();
324
325 // NOTE: In effect we are returning the local transform here in some cases, which
326 // is why we don't assert or have hints about the likelyhood of branching here.
327 if (!validVersion || !(globalTransforms.size() > index))
328 return defaultValue;
329
330 return globalTransforms[index];
331}
332
333QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(QSSGRenderNodeHandle h) const
334{
335 return getGlobalTransform(h, QMatrix4x4{ Qt::Uninitialized });
336}
337
338QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(const QSSGRenderNode &node) const
339{
340 return getGlobalTransform(node.h, node.localTransform);
341}
342
343float QSSGGlobalRenderNodeData::getGlobalOpacity(QSSGRenderNodeHandle h, float defaultValue) const
344{
345 const bool hasId = h.hasId();
346 const bool validVersion = hasId && (h.version() == m_version);
347 const auto index = h.index();
348
349 if (!validVersion || !(globalOpacities.size() > index))
350 return defaultValue;
351
352 return globalOpacities[index];
353}
354
355float QSSGGlobalRenderNodeData::getGlobalOpacity(const QSSGRenderNode &node) const
356{
357 return getGlobalOpacity(node.h, node.localOpacity);
358}
359
360#if QT_CONFIG(thread)
361#ifdef Q_OS_WASM
362QThreadPool *QSSGGlobalRenderNodeData::threadPool() const { return nullptr; }
363#else
364QThreadPool *QSSGGlobalRenderNodeData::threadPool() const { return m_threadPool.get(); }
365#endif
366#endif // QT_CONFIG(thread)
367
368QSSGGlobalRenderNodeData::LayerNodeView QSSGGlobalRenderNodeData::getLayerNodeView(QSSGRenderLayerHandle h) const
369{
370 const bool hasId = h.hasId();
371 const bool validVersion = hasId && (h.version() == m_version);
372 const auto index = h.index();
373
374 if (!validVersion || !(layerNodes.size() > index))
375 return { };
376
377 auto &section = layerNodes[index];
378
379 return { nodes.data() + section.offset, qsizetype(section.size) };
380}
381
382QSSGGlobalRenderNodeData::LayerNodeView QSSGGlobalRenderNodeData::getLayerNodeView(const QSSGRenderLayer &layer) const
383{
384 return getLayerNodeView(layer.lh);
385}
386
387void QSSGGlobalRenderNodeData::collectNodes(QSSGRenderRoot *rootNode)
388{
389 // 1. Collect all the nodes and create views into the node storage for each layer
390 // 2. Update the global state
391 // 3. Update the global data
392 Q_ASSERT(rootNode != nullptr);
393
394 nodes.clear();
395 nodes.resize(m_nodeCount, nullptr);
396 layerNodes.clear();
397
398 size_t idx = 0;
399 quint32 layerIdx = 0;
400 for (QSSGRenderNode &chld : rootNode->children) {
401 Q_ASSERT(chld.type == QSSGRenderNode::Type::Layer);
402 QSSGRenderLayer *layer = static_cast<QSSGRenderLayer *>(&chld);
403 const size_t offset = idx;
404 collectLayerChildNodes<Discard::None, Insert::Indexed>(layer, nodes, idx);
405 layer->lh = QSSGRenderLayerHandle(layer->h.context(), m_version, layerIdx++);
406 layerNodes.emplace_back(LayerNodeSection{offset , idx - offset});
407 }
408
409 nodes.resize(idx /* idx == next_idx == size */);
410}
411
412void QSSGGlobalRenderNodeData::updateGlobalState()
413{
414 // Update the active and pickable state
415 for (QSSGRenderNode *node : nodes)
416 QSSGRenderDataHelpers::updateGlobalNodeState(node, m_version);
417
418 // Recalculate ALL the global transforms and opacities.
419 for (QSSGRenderNode *node : nodes)
420 calcGlobalNodeDataIndexedImpl<QSSGRenderDataHelpers::Strategy::Initial>(node, m_version, globalTransforms, globalOpacities);
421
422 // FIXME: We shouldn't need to re-create all the instance transforms even when instancing isn't used...
423 for (QSSGRenderNode *node : nodes)
424 QSSGRenderDataHelpers::calcInstanceTransforms(node, m_version, globalTransforms, instanceTransforms);
425}
426
427QSSGGlobalRenderNodeData::InstanceTransforms QSSGGlobalRenderNodeData::getInstanceTransforms(const QSSGRenderNode &node) const
428{
429 return getInstanceTransforms(node.h);
430}
431
432QSSGGlobalRenderNodeData::InstanceTransforms QSSGGlobalRenderNodeData::getInstanceTransforms(QSSGRenderNodeHandle h) const
433{
434 const bool hasId = h.hasId();
435 const bool validVersion = hasId && (h.version() == m_version);
436 const auto index = h.index();
437
438 if (!validVersion || !(instanceTransforms.size() > index))
439 return { };
440
441 return instanceTransforms[index];
442}
443
444QSSGRenderModelData::QSSGRenderModelData(const QSSGGlobalRenderNodeDataPtr &globalNodeData)
445 : m_gnd(globalNodeData)
446 , m_version(globalNodeData->version())
447{
448
449}
450
451QMatrix3x3 QSSGRenderModelData::getNormalMatrix(QSSGRenderNodeHandle h, QMatrix3x3 defaultValue) const
452{
453 const bool hasId = h.hasId();
454 const bool validVersion = hasId && (h.version() == m_gnd->version());
455 const auto index = h.index();
456 if (!validVersion || !(m_gnd->normalMatrices.size() > index))
457 return defaultValue;
458
459 return m_gnd->normalMatrices[index];
460}
461
462QMatrix3x3 QSSGRenderModelData::getNormalMatrix(const QSSGRenderModel &model) const
463{
464 return getNormalMatrix(model.h, QMatrix3x3{ Qt::Uninitialized });
465}
466
467QSSGRenderMesh *QSSGRenderModelData::getMesh(const QSSGRenderModel &model) const
468{
469 const bool hasId = model.h.hasId();
470 const bool validVersion = hasId && (model.h.version() == m_gnd->version());
471 const auto index = model.h.index();
472
473 if (!validVersion || !(m_gnd->meshes.size() > index))
474 return nullptr;
475
476 return m_gnd->meshes[index];
477}
478
479QSSGRenderModelData::MaterialList QSSGRenderModelData::getMaterials(const QSSGRenderModel &model) const
480{
481 const bool hasId = model.h.hasId();
482 const bool validVersion = hasId && (model.h.version() == m_gnd->version());
483 const auto index = model.h.index();
484
485 if (!validVersion || !(m_gnd->materials.size() > index))
486 return {};
487
488 return m_gnd->materials[index];
489}
490
491QSSGRenderModelData::ModelViewProjections QSSGRenderModelData::getModelViewProjection(const QSSGRenderModel &model) const
492{
493 return getModelViewProjection(model.h);
494}
495
497{
498 const bool hasId = h.hasId();
499 const bool validVersion = hasId && (h.version() == m_version);
500 const auto index = h.index();
501
502 if (!validVersion || !(modelViewProjections.size() > index))
503 return {};
504
505 return modelViewProjections[index];
506}
507
508void QSSGRenderModelData::prepareMeshData(const QSSGModelsView &models, QSSGRenderer *renderer)
509{
510 const auto &bufferManager = renderer->contextInterface()->bufferManager();
511
512 const bool globalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*renderer);
513
514 for (auto *model : models) {
515 // It's up to the BufferManager to employ the appropriate caching mechanisms, so
516 // loadMesh() is expected to be fast if already loaded. Note that preparing
517 // the same QSSGRenderModel in different QQuickWindows (possible when a
518 // scene is shared between View3Ds where the View3Ds belong to different
519 // windows) leads to a different QSSGRenderMesh since the BufferManager is,
520 // very correctly, per window, and so per scenegraph render thread.
521
522 // Ensure we have a mesh
523 if (auto *theMesh = bufferManager->loadMesh(*model)) {
524 m_gnd->meshes[model->h.index()] = theMesh;
525 // Completely transparent models cannot be pickable. But models with completely
526 // transparent materials still are. This allows the artist to control pickability
527 // in a somewhat fine-grained style.
528 const float modelGlobalOpacity = m_gnd->getGlobalOpacity(*model);
529 const bool canModelBePickable = (modelGlobalOpacity > QSSGRendererPrivate::minimumRenderOpacity)
530 && (globalPickingEnabled
531 || model->getGlobalState(QSSGRenderModel::GlobalState::Pickable));
532 if (canModelBePickable) {
533 // Check if there is BVH data, if not generate it
534 if (!theMesh->bvh) {
535 const QSSGMesh::Mesh mesh = bufferManager->loadLightmapMesh(*model);
536
537 if (mesh.isValid())
538 theMesh->bvh = bufferManager->loadMeshBVH(mesh);
539 else if (model->geometry)
540 theMesh->bvh = bufferManager->loadMeshBVH(model->geometry);
541 else if (!model->meshPath.isNull())
542 theMesh->bvh = bufferManager->loadMeshBVH(model->meshPath);
543
544 if (theMesh->bvh) {
545 const auto &roots = theMesh->bvh->roots();
546 for (qsizetype i = 0, end = qsizetype(roots.size()); i < end; ++i)
547 theMesh->subsets[i].bvhRoot = roots[i];
548 }
549 }
550 }
551 } else {
552 m_gnd->meshes[model->h.index()] = nullptr;
553 }
554 }
555
556 // Now is the time to kick off the vertex/index buffer updates for all the
557 // new meshes (and their submeshes). This here is the last possible place
558 // to kick this off because the rest of the rendering pipeline will only
559 // see the individual sub-objects as "renderable objects".
560 bufferManager->commitBufferResourceUpdates();
561}
562
563void QSSGRenderModelData::prepareMaterials(const QSSGModelsView &models)
564{
565 for (auto *model : models)
566 m_gnd->materials[model->h.index()] = model->materials;
567}
568
569void QSSGRenderModelData::updateModelData(QSSGModelsView &models, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
570{
571 const bool versionChanged = m_version != m_gnd->version();
572 if (versionChanged)
573 m_version = m_gnd->version();
574
575 const QMatrix4x4 defaultModelViewProjection;
576
577 // Resize per-layer MVP store to the GND storage size so import-scene shared nodes
578 // never alias each other's slots (GND indices are stable and unique across all layers).
579 modelViewProjections.resize(m_gnd->storageSize(), { defaultModelViewProjection, defaultModelViewProjection });
580
581#if QT_CONFIG(thread)
582 QThreadPool *threadPool = (static_cast<size_t>(models.size()) >= m_threadPoolThreshold)
583 ? m_gnd->threadPool() : nullptr;
584#endif
585
586#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
587 bool matrixesDone = false;
588 bool mvpsDone = false;
589#endif
590
591 // - Normal matrices (stored in GND, shared across layers)
592 const auto doNormalMatrices = [&]() {
593 for (const QSSGRenderModel *model : std::as_const(models)) {
594 auto &normalMatrix = m_gnd->normalMatrices[model->h.index()];
595 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[model->h.index()];
596 QSSGRenderNode::calculateNormalMatrix(globalTransform, normalMatrix);
597 }
598#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
599 if (threadPool) {
600 std::lock_guard<std::mutex> guard(m_mutex);
601 matrixesDone = true;
602 m_cv.notify_all();
603 }
604#endif
605 };
606
607 // - MVPs (per-layer, indexed by GND node index)
608 const auto doMVPs = [&]() {
609 for (const QSSGRenderModel *model : std::as_const(models)) {
610 int mvpCount = 0;
611 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[model->h.index()];
612 auto &mvp = modelViewProjections[model->h.index()];
613 for (const QSSGRenderCameraData &cameraData : renderCameraData)
614 QSSGRenderNode::calculateMVP(globalTransform, cameraData.viewProjection, mvp[mvpCount++]);
615 }
616#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
617 if (threadPool) {
618 std::lock_guard<std::mutex> guard(m_mutex);
619 mvpsDone = true;
620 m_cv.notify_all();
621 }
622#endif
623 };
624
625#if QT_CONFIG(thread)
626#define qssgTryThreadedStart(func)
627 if (!threadPool) {
628 func();
629 } else if (!threadPool->tryStart(func)) {
630 qWarning("Unable to start thread for %s!", #func);
631 func();
632 }
633#else
634#define qssgTryThreadedStart(func)
635 func();
636#endif // QT_CONFIG(thread)
637
638 qssgTryThreadedStart(doNormalMatrices);
639 qssgTryThreadedStart(doMVPs);
640
641 // While we are waiting for the threads to finish, we can also prepare and update
642 // the materials and meshes.
643 prepareMaterials(models);
644 prepareMeshData(models, renderer);
645
646 // Wait for the threads to finish
647#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
648 if (threadPool) {
649 std::unique_lock<std::mutex> guard(m_mutex);
650 m_cv.wait(guard, [&]() { return matrixesDone && mvpsDone; });
651 }
652#endif
653}
654
655quint32 QSSGRenderModelData::getThreadPoolThreshold()
656{
657 // The threshold is the minimum number of models in a layer to consider using the thread pool for per-model calculations.
658 const int ret = qEnvironmentVariableIntValue("QSSG_MODEL_DATA_THREAD_POOL_THRESHOLD");
659 return (ret > 0) ? quint32(ret) : 32u;
660}
661
662bool QSSGRenderDataHelpers::updateGlobalNodeDataIndexed(QSSGRenderNode *node, const VersionType version, QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms, QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
663{
664 return calcGlobalNodeDataIndexedImpl<Strategy::Update>(node, version, globalTransforms, globalOpacities);
665}
666
667bool QSSGRenderDataHelpers::calcGlobalVariablesIndexed(QSSGRenderNode *node, const VersionType version, QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms, QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
668{
669 return calcGlobalNodeDataIndexedImpl<Strategy::Initial>(node, version, globalTransforms, globalOpacities);
670}
671
673{
674 const auto foundIt = item2DRenderers.find(&item);
675 return (foundIt != item2DRenderers.cend()) ? foundIt->second : Item2DRenderer{};
676}
677
679{
680 const bool hasId = h.hasId();
681 const bool validVersion = hasId && (h.version() == m_version);
682 const auto index = h.index();
683
684 if (!validVersion || !(modelViewProjections.size() > index))
685 return {};
686
687 return modelViewProjections[index];
688}
689
691{
692 return getModelViewProjection(item.h);
693}
694
695QSSGRenderItem2DData::QSSGRenderItem2DData(const QSSGGlobalRenderNodeDataPtr &globalNodeData)
696{
697 m_gnd = globalNodeData;
698 m_version = globalNodeData->version();
699}
700
705
706void QSSGRenderItem2DData::updateItem2DData(QSSGItem2DsView &items, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
707{
708 const auto itemCount = size_t(items.size());
709
710 if (itemCount == 0)
711 return;
712
713 const bool versionChanged = m_version != m_gnd->version();
714 if (versionChanged)
715 m_version = m_gnd->version();
716
717 const QMatrix4x4 defaultModelViewProjection;
718
719 const auto &rhiCtx = renderer->contextInterface()->rhiContext();
720
721 // Resize to GND storage size so import-scene shared nodes use their stable GND indices.
722 modelViewProjections.resize(m_gnd->storageSize(), { defaultModelViewProjection, defaultModelViewProjection });
723
724 const auto &clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
725
726 // - MVPs (indexed by GND node index)
727 const auto doMVPs = [&]() {
728 for (const QSSGRenderItem2D *item : std::as_const(items)) {
729 int mvpCount = 0;
730 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[item->h.index()];
731 auto &mvps = modelViewProjections[item->h.index()];
732 for (const QSSGRenderCameraData &cameraData : renderCameraData) {
733 const QMatrix4x4 &mvp = cameraData.viewProjection * globalTransform;
734 mvps[mvpCount++] = clipSpaceCorrMatrix * mvp * flipMatrix;
735 }
736 }
737 };
738
739 doMVPs();
740
741 // Check that we have a renderer and that it hasn't changed (would indicate a context change)
742 // and we need to update all the data.
743 QSGRenderContext *sgRc = QSSGRendererPrivate::getSgRenderContext(*renderer);
744 const bool contextChanged = (item2DRenderContext && item2DRenderContext != sgRc);
745 item2DRenderContext = sgRc;
746
747 for (const QSSGRenderItem2D *theItem2D : std::as_const(items)) {
748 auto item2DRenderer = getItem2DRenderer(*theItem2D);
749 if (contextChanged) {
750 delete item2DRenderer;
751 // NOTE: Should already be "cleared" as the QObject is deleted
752 // but we do it here for clarity.
753 item2DRenderer.clear();
754 }
755
756 if (!item2DRenderer) {
757 item2DRenderer = sgRc->createRenderer(QSGRendererInterface::RenderMode3D);
758 QObject::connect(item2DRenderer, SIGNAL(sceneGraphChanged()), theItem2D->m_frontEndObject, SLOT(update()));
759 }
760
761 if (item2DRenderer->rootNode() != theItem2D->m_rootNode) {
762 item2DRenderer->setRootNode(theItem2D->m_rootNode);
763 theItem2D->m_rootNode->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
764 item2DRenderer->nodeChanged(theItem2D->m_rootNode, QSGNode::DirtyForceUpdate); // Force render list update.
765 }
766
767 item2DRenderers[theItem2D] = item2DRenderer;
768 }
769}
770
771void QSSGRenderItem2DData::releaseRenderData(const QSSGRenderItem2D &item)
772{
773 const auto foundIt = item2DRenderers.find(&item);
774 if (foundIt != item2DRenderers.cend()) {
775 delete foundIt->second;
776 item2DRenderers.erase(foundIt);
777
778 // Removing an item should trigger a reindex of the remaining items
779 ++m_version;
780 }
781}
782
784{
785 for (auto &it : item2DRenderers)
786 delete it.second;
787 item2DRenderers.clear();
788 item2DRenderContext = nullptr;
789 modelViewProjections.clear();
790 m_version = 0;
791}
792
793QT_END_NAMESPACE
void releaseRenderData(const QSSGRenderItem2D &item)
void updateItem2DData(QSSGItem2DsView &items, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
Item2DRenderer getItem2DRenderer(const QSSGRenderItem2D &item) const
ModelViewProjections getModelViewProjection(const QSSGRenderItem2D &item) const
ModelViewProjections getModelViewProjection(QSSGRenderNodeHandle h) const
ModelViewProjections getModelViewProjection(QSSGRenderNodeHandle h) const
void updateModelData(QSSGModelsView &models, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
QMatrix3x3 getNormalMatrix(QSSGRenderNodeHandle h, QMatrix3x3 defaultValue) const
static bool calcGlobalNodeDataIndexedImpl(QSSGRenderNode *node, const QSSGRenderNodeVersionType version, QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms, QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
static void collectLayerChildNodes(QSSGRenderLayer *layer, QSSGGlobalRenderNodeData::NodeStore &outList, size_t &idx)
static void collectChildNodesFirst(QSSGRenderNode &node, QSSGGlobalRenderNodeData::NodeStore &outList, size_t &idx)
#define qssgTryThreadedStart(func)
static void collectChildNodesSecond(QSSGRenderNode &node, QSSGGlobalRenderNodeData::NodeStore &outList, size_t &idx)