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