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