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