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
165 if (Q_UNLIKELY(!node || (node->h.version() != version)))
167
168 const bool activeDirty = node->isDirty(DirtyFlag::ActiveDirty);
169 const bool pickableDirty = node->isDirty(DirtyFlag::PickableDirty);
170 const bool importedDirty = node->isDirty(DirtyFlag::ImportDirty);
171
172 const bool updateState = activeDirty || pickableDirty || importedDirty;
173
174 if (updateState) {
175 const QSSGRenderNode *parent = node->parent;
176 const bool hasParent = (parent != nullptr);
177 const bool globallyActive = node->getLocalState(LocalState::Active) && (!hasParent || parent->getGlobalState(GlobalState::Active));
178 node->flags = globallyActive ? (node->flags | FlagT(GlobalState::Active)) : (node->flags & ~FlagT(GlobalState::Active));
179 const bool globallyPickable = node->getLocalState(LocalState::Pickable) || (hasParent && parent->getGlobalState(GlobalState::Pickable));
180 node->flags = globallyPickable ? (node->flags | FlagT(GlobalState::Pickable)) : (node->flags & ~FlagT(GlobalState::Pickable));
181 const bool globallyImported = node->getLocalState(LocalState::Imported) || (hasParent && parent->getGlobalState(GlobalState::Imported));
182 node->flags = globallyImported ? (node->flags | FlagT(GlobalState::Imported)) : (node->flags & ~FlagT(GlobalState::Imported));
183
184 // Clear dirty flags (Active, Pickable, Imported)
185 node->clearDirty(ClearDirtyMask);
186 }
187
188 return GlobalStateResult(FlagT(activeDirty) | (FlagT(pickableDirty) << 1));
189}
190
191bool QSSGRenderDataHelpers::calcInstanceTransforms(QSSGRenderNode *node,
192 const VersionType version,
193 QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms,
194 QSSGGlobalRenderNodeData::InstanceTransformStore &instanceTransforms)
195{
196 if (Q_UNLIKELY(!node || (node->h.version() != version)))
197 return false;
198
199 constexpr bool retval = true;
200
201 // NOTE: We've already calculated the global states and transforms at this point
202 // so if the node isn't active we don't need to do anything.
203 // We're also assuming the node list is stored depth first order.
204 const auto idx = node->h.index();
205 QSSGRenderNode *parent = node->parent;
206 if (parent && parent->type != QSSGRenderGraphObject::Type::Layer && node->getLocalState(QSSGRenderNode::LocalState::Active)) {
207 const auto pidx = parent->h.index();
208 const auto &pGlobalTransform = globalTransforms[pidx];
209 QSSGRenderNode *instanceRoot = node->instanceRoot;
210 if (instanceRoot == node) {
211 instanceTransforms[idx] = { node->localTransform, pGlobalTransform };
212 } else if (instanceRoot) {
213 auto &[nodeInstanceLocalTransform, nodeInstanceGlobalTransform] = instanceTransforms[idx];
214 auto &[instanceRootLocalTransform, instanceRootGlobalTransform] = instanceTransforms[instanceRoot->h.index()];
215 nodeInstanceGlobalTransform = instanceRootGlobalTransform;
216 //### technically O(n^2) -- we could cache localInstanceTransform if every node in the
217 // tree is guaranteed to have the same instance root. That would require an API change.
218 nodeInstanceLocalTransform = node->localTransform;
219 auto *p = parent;
220 while (p) {
221 if (p == instanceRoot) {
222 nodeInstanceLocalTransform = instanceRootLocalTransform * nodeInstanceLocalTransform;
223 break;
224 }
225 nodeInstanceLocalTransform = p->localTransform * nodeInstanceLocalTransform;
226 p = p->parent;
227 }
228 } else {
229 // By default, we do magic: translation is applied to the global instance transform,
230 // while scale/rotation is local
231 QMatrix4x4 globalInstanceTransform = globalTransforms[pidx];
232 QMatrix4x4 localInstanceTransform = node->localTransform;
233 auto &localInstanceMatrix = *reinterpret_cast<float (*)[4][4]>(localInstanceTransform.data());
234 QVector3D localPos{localInstanceMatrix[3][0], localInstanceMatrix[3][1], localInstanceMatrix[3][2]};
235 localInstanceMatrix[3][0] = 0;
236 localInstanceMatrix[3][1] = 0;
237 localInstanceMatrix[3][2] = 0;
238 globalInstanceTransform = pGlobalTransform;
239 globalInstanceTransform.translate(localPos);
240 instanceTransforms[idx] = { localInstanceTransform, globalInstanceTransform };
241 }
242 } else {
243 instanceTransforms[idx] = { node->localTransform, {} };
244 }
245
246 return retval;
247}
248
249QSSGGlobalRenderNodeData::QSSGGlobalRenderNodeData(QSSGRenderRoot *root)
250#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
251 : m_threadPool(new QThreadPool)
252 , m_rootNode(root)
253#else
254 : m_rootNode(root)
255#endif // QT_CONFIG(thread)
256{
257
258}
259
260QSSGGlobalRenderNodeData::~QSSGGlobalRenderNodeData()
261{
262
263}
264
265void QSSGGlobalRenderNodeData::reindex()
266{
267 if (m_rootNode) {
268 quint32 dfsIdx = 0;
269 m_nodeCount = 0;
270
271 // Bump the version to invalidate all existing handles. This ensures nodes don't start
272 // accessing stale data.
273 ++m_version;
274
275 // Version 0 means "not yet indexed" (default-constructed handle), so skip it.
276 // NOTE: This can happen if the version wraps around.
277 if (Q_UNLIKELY(m_version == 0)) {
278 qCDebug(lcLayerData) << "Version wrap around detected, resetting to 1.";
279 ++m_version;
280 }
281
282 ::reindex(m_rootNode, m_version, dfsIdx, m_nodeCount);
283
284 // Actual storage size (Some nodes, like the layers, will all use index 0).
285 // NOTE: This can differ from the node count, as nodes are collected for each
286 // layer. Since layers can reference nodes outside their view the node list
287 // will contain duplicate nodes when import scene is used!
288 m_size = dfsIdx + 1;
289
290 globalTransforms.resize(m_size, QMatrix4x4{ Qt::Uninitialized });
291 globalOpacities.resize(m_size, 1.0f);
292 instanceTransforms.resize(m_size, { QMatrix4x4{ Qt::Uninitialized }, QMatrix4x4{ Qt::Uninitialized } });
293 normalMatrices.resize(m_size, QMatrix3x3{});
294 meshes.resize(m_size, nullptr);
295 materials.resize(m_size, {});
296
297 collectNodes(m_rootNode);
298 // NOTE: If the tree was dirty we force a full rebuild of the global transforms etc. since
299 // the stored data is invalid for the new index order.
300 updateGlobalState();
301 }
302}
303
304void QSSGGlobalRenderNodeData::invalidate()
305{
306 m_rootNode = nullptr;
307}
308
309QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(QSSGRenderNodeHandle h, const QMatrix4x4 &defaultValue) const
310{
311 // Ensure we have an valid index.
312 const bool hasId = h.hasId();
313 const bool validVersion = hasId && (h.version() == m_version);
314 const auto index = h.index();
315
316 // NOTE: In effect we are returning the local transform here in some cases, which
317 // is why we don't assert or have hints about the likelyhood of branching here.
318 if (!validVersion || !(globalTransforms.size() > index))
319 return defaultValue;
320
321 return globalTransforms[index];
322}
323
324QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(QSSGRenderNodeHandle h) const
325{
326 return getGlobalTransform(h, QMatrix4x4{ Qt::Uninitialized });
327}
328
329QMatrix4x4 QSSGGlobalRenderNodeData::getGlobalTransform(const QSSGRenderNode &node) const
330{
331 return getGlobalTransform(node.h, node.localTransform);
332}
333
334float QSSGGlobalRenderNodeData::getGlobalOpacity(QSSGRenderNodeHandle h, float defaultValue) const
335{
336 const bool hasId = h.hasId();
337 const bool validVersion = hasId && (h.version() == m_version);
338 const auto index = h.index();
339
340 if (!validVersion || !(globalOpacities.size() > index))
341 return defaultValue;
342
343 return globalOpacities[index];
344}
345
346float QSSGGlobalRenderNodeData::getGlobalOpacity(const QSSGRenderNode &node) const
347{
348 return getGlobalOpacity(node.h, node.localOpacity);
349}
350
351#if QT_CONFIG(thread)
352#ifdef Q_OS_WASM
353QThreadPool *QSSGGlobalRenderNodeData::threadPool() const { return nullptr; }
354#else
355QThreadPool *QSSGGlobalRenderNodeData::threadPool() const { return m_threadPool.get(); }
356#endif
357#endif // QT_CONFIG(thread)
358
359QSSGGlobalRenderNodeData::LayerNodeView QSSGGlobalRenderNodeData::getLayerNodeView(QSSGRenderLayerHandle h) const
360{
361 const bool hasId = h.hasId();
362 const bool validVersion = hasId && (h.version() == m_version);
363 const auto index = h.index();
364
365 if (!validVersion || !(layerNodes.size() > index))
366 return { };
367
368 auto &section = layerNodes[index];
369
370 return { nodes.data() + section.offset, qsizetype(section.size) };
371}
372
373QSSGGlobalRenderNodeData::LayerNodeView QSSGGlobalRenderNodeData::getLayerNodeView(const QSSGRenderLayer &layer) const
374{
375 return getLayerNodeView(layer.lh);
376}
377
378void QSSGGlobalRenderNodeData::collectNodes(QSSGRenderRoot *rootNode)
379{
380 // 1. Collect all the nodes and create views into the node storage for each layer
381 // 2. Update the global state
382 // 3. Update the global data
383 Q_ASSERT(rootNode != nullptr);
384
385 nodes.clear();
386 nodes.resize(m_nodeCount, nullptr);
387 layerNodes.clear();
388
389 size_t idx = 0;
390 quint32 layerIdx = 0;
391 for (QSSGRenderNode &chld : rootNode->children) {
392 Q_ASSERT(chld.type == QSSGRenderNode::Type::Layer);
393 QSSGRenderLayer *layer = static_cast<QSSGRenderLayer *>(&chld);
394 const size_t offset = idx;
395 collectLayerChildNodes<Discard::None, Insert::Indexed>(layer, nodes, idx);
396 layer->lh = QSSGRenderLayerHandle(layer->h.context(), m_version, layerIdx++);
397 layerNodes.emplace_back(LayerNodeSection{offset , idx - offset});
398 }
399
400 nodes.resize(idx /* idx == next_idx == size */);
401}
402
403void QSSGGlobalRenderNodeData::updateGlobalState()
404{
405 // Update the active and pickable state
406 for (QSSGRenderNode *node : nodes)
407 QSSGRenderDataHelpers::updateGlobalNodeState(node, m_version);
408
409 // Recalculate ALL the global transforms and opacities.
410 for (QSSGRenderNode *node : nodes)
411 calcGlobalNodeDataIndexedImpl<QSSGRenderDataHelpers::Strategy::Initial>(node, m_version, globalTransforms, globalOpacities);
412
413 // FIXME: We shouldn't need to re-create all the instance transforms even when instancing isn't used...
414 for (QSSGRenderNode *node : nodes)
415 QSSGRenderDataHelpers::calcInstanceTransforms(node, m_version, globalTransforms, instanceTransforms);
416}
417
418QSSGGlobalRenderNodeData::InstanceTransforms QSSGGlobalRenderNodeData::getInstanceTransforms(const QSSGRenderNode &node) const
419{
420 return getInstanceTransforms(node.h);
421}
422
423QSSGGlobalRenderNodeData::InstanceTransforms QSSGGlobalRenderNodeData::getInstanceTransforms(QSSGRenderNodeHandle h) const
424{
425 const bool hasId = h.hasId();
426 const bool validVersion = hasId && (h.version() == m_version);
427 const auto index = h.index();
428
429 if (!validVersion || !(instanceTransforms.size() > index))
430 return { };
431
432 return instanceTransforms[index];
433}
434
435QSSGRenderModelData::QSSGRenderModelData(const QSSGGlobalRenderNodeDataPtr &globalNodeData)
436 : m_gnd(globalNodeData)
437 , m_version(globalNodeData->version())
438{
439
440}
441
442QMatrix3x3 QSSGRenderModelData::getNormalMatrix(QSSGRenderNodeHandle h, QMatrix3x3 defaultValue) const
443{
444 const bool hasId = h.hasId();
445 const bool validVersion = hasId && (h.version() == m_gnd->version());
446 const auto index = h.index();
447 if (!validVersion || !(m_gnd->normalMatrices.size() > index))
448 return defaultValue;
449
450 return m_gnd->normalMatrices[index];
451}
452
453QMatrix3x3 QSSGRenderModelData::getNormalMatrix(const QSSGRenderModel &model) const
454{
455 return getNormalMatrix(model.h, QMatrix3x3{ Qt::Uninitialized });
456}
457
458QSSGRenderMesh *QSSGRenderModelData::getMesh(const QSSGRenderModel &model) const
459{
460 const bool hasId = model.h.hasId();
461 const bool validVersion = hasId && (model.h.version() == m_gnd->version());
462 const auto index = model.h.index();
463
464 if (!validVersion || !(m_gnd->meshes.size() > index))
465 return nullptr;
466
467 return m_gnd->meshes[index];
468}
469
470QSSGRenderModelData::MaterialList QSSGRenderModelData::getMaterials(const QSSGRenderModel &model) const
471{
472 const bool hasId = model.h.hasId();
473 const bool validVersion = hasId && (model.h.version() == m_gnd->version());
474 const auto index = model.h.index();
475
476 if (!validVersion || !(m_gnd->materials.size() > index))
477 return {};
478
479 return m_gnd->materials[index];
480}
481
482QSSGRenderModelData::ModelViewProjections QSSGRenderModelData::getModelViewProjection(const QSSGRenderModel &model) const
483{
484 return getModelViewProjection(model.h);
485}
486
488{
489 const bool hasId = h.hasId();
490 const bool validVersion = hasId && (h.version() == m_version);
491 const auto index = h.index();
492
493 if (!validVersion || !(modelViewProjections.size() > index))
494 return {};
495
496 return modelViewProjections[index];
497}
498
499void QSSGRenderModelData::prepareMeshData(const QSSGModelsView &models, QSSGRenderer *renderer)
500{
501 const auto &bufferManager = renderer->contextInterface()->bufferManager();
502
503 const bool globalPickingEnabled = QSSGRendererPrivate::isGlobalPickingEnabled(*renderer);
504
505 for (auto *model : models) {
506 // It's up to the BufferManager to employ the appropriate caching mechanisms, so
507 // loadMesh() is expected to be fast if already loaded. Note that preparing
508 // the same QSSGRenderModel in different QQuickWindows (possible when a
509 // scene is shared between View3Ds where the View3Ds belong to different
510 // windows) leads to a different QSSGRenderMesh since the BufferManager is,
511 // very correctly, per window, and so per scenegraph render thread.
512
513 // Ensure we have a mesh
514 if (auto *theMesh = bufferManager->loadMesh(*model)) {
515 m_gnd->meshes[model->h.index()] = theMesh;
516 // Completely transparent models cannot be pickable. But models with completely
517 // transparent materials still are. This allows the artist to control pickability
518 // in a somewhat fine-grained style.
519 const float modelGlobalOpacity = m_gnd->getGlobalOpacity(*model);
520 const bool canModelBePickable = (modelGlobalOpacity > QSSGRendererPrivate::minimumRenderOpacity)
521 && (globalPickingEnabled
522 || model->getGlobalState(QSSGRenderModel::GlobalState::Pickable));
523 if (canModelBePickable) {
524 // Check if there is BVH data, if not generate it
525 if (!theMesh->bvh) {
526 const QSSGMesh::Mesh mesh = bufferManager->loadLightmapMesh(*model);
527
528 if (mesh.isValid())
529 theMesh->bvh = bufferManager->loadMeshBVH(mesh);
530 else if (model->geometry)
531 theMesh->bvh = bufferManager->loadMeshBVH(model->geometry);
532 else if (!model->meshPath.isNull())
533 theMesh->bvh = bufferManager->loadMeshBVH(model->meshPath);
534
535 if (theMesh->bvh) {
536 const auto &roots = theMesh->bvh->roots();
537 for (qsizetype i = 0, end = qsizetype(roots.size()); i < end; ++i)
538 theMesh->subsets[i].bvhRoot = roots[i];
539 }
540 }
541 }
542 } else {
543 m_gnd->meshes[model->h.index()] = nullptr;
544 }
545 }
546
547 // Now is the time to kick off the vertex/index buffer updates for all the
548 // new meshes (and their submeshes). This here is the last possible place
549 // to kick this off because the rest of the rendering pipeline will only
550 // see the individual sub-objects as "renderable objects".
551 bufferManager->commitBufferResourceUpdates();
552}
553
554void QSSGRenderModelData::prepareMaterials(const QSSGModelsView &models)
555{
556 for (auto *model : models)
557 m_gnd->materials[model->h.index()] = model->materials;
558}
559
560void QSSGRenderModelData::updateModelData(QSSGModelsView &models, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
561{
562 const bool versionChanged = m_version != m_gnd->version();
563 if (versionChanged)
564 m_version = m_gnd->version();
565
566 const QMatrix4x4 defaultModelViewProjection;
567
568 // Resize per-layer MVP store to the GND storage size so import-scene shared nodes
569 // never alias each other's slots (GND indices are stable and unique across all layers).
570 modelViewProjections.resize(m_gnd->storageSize(), { defaultModelViewProjection, defaultModelViewProjection });
571
572#if QT_CONFIG(thread)
573 QThreadPool *threadPool = (static_cast<size_t>(models.size()) >= m_threadPoolThreshold)
574 ? m_gnd->threadPool() : nullptr;
575#endif
576
577#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
578 bool matrixesDone = false;
579 bool mvpsDone = false;
580#endif
581
582 // - Normal matrices (stored in GND, shared across layers)
583 const auto doNormalMatrices = [&]() {
584 for (const QSSGRenderModel *model : std::as_const(models)) {
585 auto &normalMatrix = m_gnd->normalMatrices[model->h.index()];
586 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[model->h.index()];
587 QSSGRenderNode::calculateNormalMatrix(globalTransform, normalMatrix);
588 }
589#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
590 if (threadPool) {
591 std::lock_guard<std::mutex> guard(m_mutex);
592 matrixesDone = true;
593 m_cv.notify_all();
594 }
595#endif
596 };
597
598 // - MVPs (per-layer, indexed by GND node index)
599 const auto doMVPs = [&]() {
600 for (const QSSGRenderModel *model : std::as_const(models)) {
601 int mvpCount = 0;
602 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[model->h.index()];
603 auto &mvp = modelViewProjections[model->h.index()];
604 for (const QSSGRenderCameraData &cameraData : renderCameraData)
605 QSSGRenderNode::calculateMVP(globalTransform, cameraData.viewProjection, mvp[mvpCount++]);
606 }
607#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
608 if (threadPool) {
609 std::lock_guard<std::mutex> guard(m_mutex);
610 mvpsDone = true;
611 m_cv.notify_all();
612 }
613#endif
614 };
615
616#if QT_CONFIG(thread)
617#define qssgTryThreadedStart(func)
618 if (!threadPool) {
619 func();
620 } else if (!threadPool->tryStart(func)) {
621 qWarning("Unable to start thread for %s!", #func);
622 func();
623 }
624#else
625#define qssgTryThreadedStart(func)
626 func();
627#endif // QT_CONFIG(thread)
628
629 qssgTryThreadedStart(doNormalMatrices);
630 qssgTryThreadedStart(doMVPs);
631
632 // While we are waiting for the threads to finish, we can also prepare and update
633 // the materials and meshes.
634 prepareMaterials(models);
635 prepareMeshData(models, renderer);
636
637 // Wait for the threads to finish
638#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
639 if (threadPool) {
640 std::unique_lock<std::mutex> guard(m_mutex);
641 m_cv.wait(guard, [&]() { return matrixesDone && mvpsDone; });
642 }
643#endif
644}
645
646quint32 QSSGRenderModelData::getThreadPoolThreshold()
647{
648 // The threshold is the minimum number of models in a layer to consider using the thread pool for per-model calculations.
649 const int ret = qEnvironmentVariableIntValue("QSSG_MODEL_DATA_THREAD_POOL_THRESHOLD");
650 return (ret > 0) ? quint32(ret) : 32u;
651}
652
653bool QSSGRenderDataHelpers::updateGlobalNodeDataIndexed(QSSGRenderNode *node, const VersionType version, QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms, QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
654{
655 return calcGlobalNodeDataIndexedImpl<Strategy::Update>(node, version, globalTransforms, globalOpacities);
656}
657
658bool QSSGRenderDataHelpers::calcGlobalVariablesIndexed(QSSGRenderNode *node, const VersionType version, QSSGGlobalRenderNodeData::GlobalTransformStore &globalTransforms, QSSGGlobalRenderNodeData::GlobalOpacityStore &globalOpacities)
659{
660 return calcGlobalNodeDataIndexedImpl<Strategy::Initial>(node, version, globalTransforms, globalOpacities);
661}
662
664{
665 const auto foundIt = item2DRenderers.find(&item);
666 return (foundIt != item2DRenderers.cend()) ? foundIt->second : Item2DRenderer{};
667}
668
670{
671 const bool hasId = h.hasId();
672 const bool validVersion = hasId && (h.version() == m_version);
673 const auto index = h.index();
674
675 if (!validVersion || !(modelViewProjections.size() > index))
676 return {};
677
678 return modelViewProjections[index];
679}
680
682{
683 return getModelViewProjection(item.h);
684}
685
686QSSGRenderItem2DData::QSSGRenderItem2DData(const QSSGGlobalRenderNodeDataPtr &globalNodeData)
687{
688 m_gnd = globalNodeData;
689 m_version = globalNodeData->version();
690}
691
696
697void QSSGRenderItem2DData::updateItem2DData(QSSGItem2DsView &items, QSSGRenderer *renderer, const QSSGRenderCameraDataList &renderCameraData)
698{
699 const auto itemCount = size_t(items.size());
700
701 if (itemCount == 0)
702 return;
703
704 const bool versionChanged = m_version != m_gnd->version();
705 if (versionChanged)
706 m_version = m_gnd->version();
707
708 const QMatrix4x4 defaultModelViewProjection;
709
710 const auto &rhiCtx = renderer->contextInterface()->rhiContext();
711
712 // Resize to GND storage size so import-scene shared nodes use their stable GND indices.
713 modelViewProjections.resize(m_gnd->storageSize(), { defaultModelViewProjection, defaultModelViewProjection });
714
715 const auto &clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
716
717 // - MVPs (indexed by GND node index)
718 const auto doMVPs = [&]() {
719 for (const QSSGRenderItem2D *item : std::as_const(items)) {
720 int mvpCount = 0;
721 const QMatrix4x4 &globalTransform = m_gnd->globalTransforms[item->h.index()];
722 auto &mvps = modelViewProjections[item->h.index()];
723 for (const QSSGRenderCameraData &cameraData : renderCameraData) {
724 const QMatrix4x4 &mvp = cameraData.viewProjection * globalTransform;
725 mvps[mvpCount++] = clipSpaceCorrMatrix * mvp * flipMatrix;
726 }
727 }
728 };
729
730 doMVPs();
731
732 // Check that we have a renderer and that it hasn't changed (would indicate a context change)
733 // and we need to update all the data.
734 QSGRenderContext *sgRc = QSSGRendererPrivate::getSgRenderContext(*renderer);
735 const bool contextChanged = (item2DRenderContext && item2DRenderContext != sgRc);
736 item2DRenderContext = sgRc;
737
738 for (const QSSGRenderItem2D *theItem2D : std::as_const(items)) {
739 auto item2DRenderer = getItem2DRenderer(*theItem2D);
740 if (contextChanged) {
741 delete item2DRenderer;
742 // NOTE: Should already be "cleared" as the QObject is deleted
743 // but we do it here for clarity.
744 item2DRenderer.clear();
745 }
746
747 if (!item2DRenderer) {
748 item2DRenderer = sgRc->createRenderer(QSGRendererInterface::RenderMode3D);
749 QObject::connect(item2DRenderer, SIGNAL(sceneGraphChanged()), theItem2D->m_frontEndObject, SLOT(update()));
750 }
751
752 if (item2DRenderer->rootNode() != theItem2D->m_rootNode) {
753 item2DRenderer->setRootNode(theItem2D->m_rootNode);
754 theItem2D->m_rootNode->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
755 item2DRenderer->nodeChanged(theItem2D->m_rootNode, QSGNode::DirtyForceUpdate); // Force render list update.
756 }
757
758 item2DRenderers[theItem2D] = item2DRenderer;
759 }
760}
761
762void QSSGRenderItem2DData::releaseRenderData(const QSSGRenderItem2D &item)
763{
764 const auto foundIt = item2DRenderers.find(&item);
765 if (foundIt != item2DRenderers.cend()) {
766 delete foundIt->second;
767 item2DRenderers.erase(foundIt);
768
769 // Removing an item should trigger a reindex of the remaining items
770 ++m_version;
771 }
772}
773
775{
776 for (auto &it : item2DRenderers)
777 delete it.second;
778 item2DRenderers.clear();
779 item2DRenderContext = nullptr;
780 modelViewProjections.clear();
781 m_version = 0;
782}
783
784QT_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)