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