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
qquick3dscenemanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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
10
11#include <QtQuick/QQuickWindow>
12
13#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
14#include <ssg/qssgrendercontextcore.h>
15#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
16#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgrenderroot_p.h>
20#include <QtQuick3DRuntimeRender/ssg/qssgrenderextensions.h>
21#include <QtQuick3DRuntimeRender/private/qssgrenderuserpass_p.h>
22
23#include <QtQuick3DUtils/private/qssgassert_p.h>
24
26
28
29// NOTE: Another indiraction to try to clean-up resource in a nice way.
30QSSGCleanupObject::QSSGCleanupObject(std::shared_ptr<QSSGRenderContextInterface> rci, QList<QSSGRenderGraphObject *> resourceCleanupQueue, QQuickWindow *window)
31 : QObject(window)
32 , m_rci(rci)
33 , m_window(window)
34 , m_resourceCleanupQueue(resourceCleanupQueue)
35{
36 Q_ASSERT(window != nullptr && rci); // We need a window and a rci for this!
37
38 if (QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context) {
39 connect(rc, &QSGRenderContext::releaseCachedResourcesRequested, this, &QSSGCleanupObject::cleanupResources, Qt::DirectConnection);
40 connect(rc, &QSGRenderContext::invalidated, this, &QSSGCleanupObject::cleanupResources, Qt::DirectConnection);
41 } else {
42 qWarning() << "No QSGRenderContext, cannot cleanup resources!";
43 }
44}
45
47{
48 QSSG_CHECK(QSSG_DEBUG_COND(m_resourceCleanupQueue.isEmpty()));
49}
50
52{
53 m_rci->renderer()->cleanupResources(m_resourceCleanupQueue);
54 deleteLater();
55}
56
57static constexpr char qtQQ3DWAPropName[] { "_qtquick3dWindowAttachment" };
58
59static void detachDirtyList(QQuick3DObject **head, size_t count)
60{
61 for (size_t i = 0; i < count; ++i) {
62 QQuick3DObject *item = head[i];
63 while (item) {
64 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item);
65 QQuick3DObject *next = p->nextDirtyItem;
66 p->prevDirtyItem = nullptr;
67 p->nextDirtyItem = nullptr;
68 item = next;
69 }
70 head[i] = nullptr;
71 }
72}
73
74QQuick3DSceneManager::QQuick3DSceneManager(QObject *parent)
75 : QObject(parent)
76{
77}
78
79// Should be deleted by QQuick3DWindowAttachment to ensure it's done
80// on the render thread.
81QQuick3DSceneManager::~QQuick3DSceneManager()
82{
83 cleanupNodes();
84 if (wattached)
85 wattached->unregisterSceneManager(*this);
86
87 // Objects still on one of our dirty lists hold a 'prevDirtyItem' pointer into the arrays
88 // below, which are freed together with this manager. Detach them now so a later
89 // removeFromDirtyList(), e.g. when the front-end object is itself destroyed, is a safe
90 // no-op instead of writing through a stale pointer. The normal per-frame drain only
91 // happens for active managers in synchronize(), not for one that was queued for cleanup.
92 //
93 // NOTE!!!: If these dirty objects are shared they will be re-queued by the surviving manager,
94 // but if they are not shared they will be dropped on the floor, which is fine, because they're not
95 // active and will either be destroyed or added to a new scene.
96 // The re-adoption happens because the destroyed() signal of this manager is connected to
97 // QQuick3DViewport::updateSceneManagerForImportScene(), which calls refSceneManager() on any
98 // surviving view, which in turn calls dirty() on any shared objects, which adds them to the
99 // dirty list if they're not already in one.
100 detachDirtyList(dirtyResources, std::size(dirtyResources));
101 detachDirtyList(dirtyNodes, std::size(dirtyNodes));
102 detachDirtyList(dirtyExtensions, std::size(dirtyExtensions));
103}
104
105void QQuick3DSceneManager::setWindow(QQuickWindow *window)
106{
107 if (window == m_window)
108 return;
109
110 if (window != m_window) {
111 if (wattached) {
112 // Unregister from old windows attached object
113 wattached->unregisterSceneManager(*this);
114 wattached = nullptr;
115 }
116 m_window = window;
117 if (m_window) {
118 wattached = getOrSetWindowAttachment(*m_window);
119 if (wattached)
120 wattached->registerSceneManager(*this);
121 }
122
123 emit windowChanged();
124 }
125}
126
127QQuickWindow *QQuick3DSceneManager::window()
128{
129 return m_window;
130}
131
132void QQuick3DSceneManager::dirtyItem(QQuick3DObject *item)
133{
134 Q_UNUSED(item);
135 emit needsUpdate();
136}
137
138void QQuick3DSceneManager::requestUpdate()
139{
140 emit needsUpdate();
141}
142
143void QQuick3DSceneManager::cleanup(QSSGRenderGraphObject *item)
144{
145 cleanupNodeList.insert(item);
146
147 if (auto front = m_nodeMap[item]) {
148 auto *po = QQuick3DObjectPrivate::get(front);
149 sharedResourceRemoved |= po->sharedResource;
150 po->spatialNode = nullptr;
151
152 // The front-end object is no longer reachable (destroyed) so make sure we don't return it
153 // when doing a node look-up.
154 m_nodeMap[item] = nullptr;
155 }
156}
157
158void QQuick3DSceneManager::polishItems()
159{
160
161}
162
163void QQuick3DSceneManager::forcePolish()
164{
165
166}
167
168void QQuick3DSceneManager::sync()
169{
170
171}
172
173void QQuick3DSceneManager::updateBoundingBoxes(QSSGBufferManager &mgr)
174{
175 if (lightmapSourceTracker.isDirty) {
176 mgr.setLightmapSource(lightmapSourceTracker.lightmapSource);
177 lightmapSourceTracker.isDirty = false;
178 }
179
180 for (auto *object : std::as_const(dirtyBoundingBoxList)) {
181 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(object);
182 if (itemPriv->sceneManager == nullptr)
183 continue;
184 auto model = static_cast<QSSGRenderModel *>(itemPriv->spatialNode);
185 if (model) {
186 QSSGBounds3 bounds = mgr.getModelBounds(model);
187 static_cast<QQuick3DModel *>(object)->setBounds(bounds.minimum, bounds.maximum);
188 }
189 }
190
191 dirtyBoundingBoxList.clear();
192}
193
194QQuick3DSceneManager::SyncResult QQuick3DSceneManager::updateDirtyResourceNodes()
195{
196 SyncResult ret = SyncResultFlag::None;
197
198 auto it = std::begin(dirtyResources);
199 const auto end = std::end(dirtyResources);
200 for (; it != end; ++it)
201 ret |= updateResources(it);
202
203 return ret;
204}
205
206void QQuick3DSceneManager::updateDirtySpatialNodes()
207{
208 auto it = std::begin(dirtyNodes);
209 const auto end = std::end(dirtyNodes);
210 for (; it != end; ++it)
211 updateNodes(it);
212}
213
214QQuick3DSceneManager::SyncResult QQuick3DSceneManager::updateDiryExtensions()
215{
216 SyncResult ret = SyncResultFlag::None;
217
218 auto it = std::begin(dirtyExtensions);
219 const auto end = std::end(dirtyExtensions);
220 for (; it != end; ++it)
221 ret |= updateExtensions(it);
222
223 return ret;
224}
225
226QQuick3DSceneManager::SyncResult QQuick3DSceneManager::updateDirtyResourceSecondPass()
227{
228 const auto updateDirtyResourceNode = [this](QQuick3DObject *resource) {
229 QQuick3DObjectPrivate *po = QQuick3DObjectPrivate::get(resource);
230 po->dirtyAttributes = 0; // Not used, but we should still reset it.
231 po->secondaryUpdateRequested = false;
232 QSSGRenderGraphObject *node = po->spatialNode;
233 po->spatialNode = resource->updateSpatialNode(node);
234 if (po->spatialNode)
235 m_nodeMap.insert(po->spatialNode, resource);
236 return po->sharedResource ? SyncResultFlag::SharedResourcesDirty : SyncResultFlag::None;
237 };
238
239 SyncResult res = SyncResultFlag::None;
240 auto it = dirtySecondPassResources.constBegin();
241 const auto end = dirtySecondPassResources.constEnd();
242 for (; it != end; ++it)
243 res |= updateDirtyResourceNode(*it);
244
245 // Expectation is that we won't get here often, for other updates the
246 // backend nodes should have been realized and we won't get here, so
247 // just release space used by the set.
248 dirtySecondPassResources = {};
249
250 return res;
251}
252
253void QQuick3DSceneManager::updateDirtyResource(QQuick3DObject *resourceObject)
254{
255 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(resourceObject);
256 quint32 dirty = itemPriv->dirtyAttributes;
257 Q_UNUSED(dirty);
258 itemPriv->dirtyAttributes = 0;
259 QSSGRenderGraphObject *oldNode = itemPriv->spatialNode;
260 QSSGRenderGraphObject *newNode = resourceObject->updateSpatialNode(itemPriv->spatialNode);
261
262 const bool backendNodeChanged = oldNode != newNode;
263
264 // If the backend resource object changed, we need to clean up the old one.
265 if (oldNode && backendNodeChanged)
266 cleanup(oldNode);
267
268 // NOTE: cleanup() will remove the item from the node map and set the spatial node to nullptr.
269 // so we need to set it here.
270 itemPriv->spatialNode = newNode;
271
272 if (itemPriv->spatialNode) {
273 m_nodeMap.insert(itemPriv->spatialNode, resourceObject);
274 if (itemPriv->spatialNode->type == QSSGRenderGraphObject::Type::ResourceLoader) {
275 resourceLoaders.insert(itemPriv->spatialNode);
276 } else if (itemPriv->spatialNode->type == QQuick3DObjectPrivate::Type::Image2D && backendNodeChanged) {
277 ++inputHandlingEnabled;
278 } else if (QSSGRenderGraphObjectUtils::isUserRenderPass(itemPriv->type) && itemPriv->spatialNode) {
279 auto *userRenderPass = static_cast<QSSGRenderUserPass *>(itemPriv->spatialNode);
280 if (const auto idx = userRenderPasses.indexOf(userRenderPass); idx == -1) {
281 userRenderPasses.push_back(userRenderPass);
282 userRenderPassesDirty = true;
283 }
284 }
285 }
286
287 if (itemPriv->hasFlag(QQuick3DObjectPrivate::Flags::RequiresSecondaryUpdate) && itemPriv->secondaryUpdateRequested)
288 dirtySecondPassResources.insert(resourceObject);
289
290 // resource nodes dont go in the tree, so we dont need to parent them
291}
292
293void QQuick3DSceneManager::updateDirtySpatialNode(QQuick3DNode *spatialNode)
294{
295 const auto prepNewSpatialNode = [this](QSSGRenderNode *node) {
296 if (node) {
297 node->rootNodeRef = wattached->rootNode()->rootNodeRef;
298 Q_ASSERT(QSSGRenderRoot::get(node->rootNodeRef) != nullptr);
299 }
300 };
301
302 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(spatialNode);
303 quint32 dirty = itemPriv->dirtyAttributes;
304 itemPriv->dirtyAttributes = 0;
305 QSSGRenderGraphObject *oldNode = itemPriv->spatialNode;
306 QSSGRenderGraphObject *newNode = spatialNode->updateSpatialNode(oldNode);
307
308 const bool backendNodeChanged = oldNode != newNode;
309
310 // If the backend node changed, we need to clean up the old one.
311 if (oldNode && backendNodeChanged)
312 cleanup(oldNode);
313
314 // NOTE: cleanup() will remove the item from the node map and set the spatial node to nullptr.
315 // so we need to set it here.
316 itemPriv->spatialNode = newNode;
317 prepNewSpatialNode(static_cast<QSSGRenderNode *>(itemPriv->spatialNode));
318
319 // NOTE: We always update the node map, as we can end-up with the a node map where the mapping
320 // has been 'disconnected', e.g., the front-end object removed from the scene only to be later
321 // re-used.
322 if (itemPriv->spatialNode) {
323 m_nodeMap.insert(itemPriv->spatialNode, spatialNode);
324 if (itemPriv->type == QQuick3DObjectPrivate::Type::Item2D && itemPriv->spatialNode != oldNode)
325 ++inputHandlingEnabled;
326 }
327
328 QSSGRenderNode *graphNode = static_cast<QSSGRenderNode *>(itemPriv->spatialNode);
329
330 if (graphNode && graphNode->parent && dirty & QQuick3DObjectPrivate::ParentChanged) {
331 QQuick3DNode *nodeParent = qobject_cast<QQuick3DNode *>(spatialNode->parentItem());
332 if (nodeParent) {
333 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(
334 QQuick3DObjectPrivate::get(nodeParent)->spatialNode);
335 if (parentGraphNode) {
336 graphNode->parent->removeChild(*graphNode);
337 parentGraphNode->addChild(*graphNode);
338 }
339 }
340 }
341
342 if (graphNode && graphNode->parent == nullptr) {
343 QQuick3DNode *nodeParent = qobject_cast<QQuick3DNode *>(spatialNode->parentItem());
344 if (nodeParent) {
345 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(nodeParent)->spatialNode);
346 if (!parentGraphNode) {
347 // The parent spatial node hasn't been created yet
348 auto parentNode = QQuick3DObjectPrivate::get(nodeParent);
349 parentNode->spatialNode = nodeParent->updateSpatialNode(parentNode->spatialNode);
350 if (parentNode->spatialNode)
351 m_nodeMap.insert(parentNode->spatialNode, nodeParent);
352 parentGraphNode = static_cast<QSSGRenderNode *>(parentNode->spatialNode);
353 prepNewSpatialNode(parentGraphNode);
354 }
355 if (parentGraphNode)
356 parentGraphNode->addChild(*graphNode);
357 } else {
358 QQuick3DViewport *viewParent = qobject_cast<QQuick3DViewport *>(spatialNode->parent());
359 if (viewParent) {
360 auto sceneRoot = QQuick3DObjectPrivate::get(viewParent->scene());
361 if (!sceneRoot->spatialNode) // must have a scene root spatial node first
362 sceneRoot->spatialNode = viewParent->scene()->updateSpatialNode(sceneRoot->spatialNode);
363 if (sceneRoot->spatialNode) {
364 auto *sceneRootNode = static_cast<QSSGRenderNode *>(sceneRoot->spatialNode);
365 prepNewSpatialNode(sceneRootNode);
366 m_nodeMap.insert(sceneRootNode, viewParent->scene());
367 sceneRootNode->addChild(*graphNode);
368 }
369 }
370 }
371 }
372}
373
374QQuick3DObject *QQuick3DSceneManager::lookUpNode(const QSSGRenderGraphObject *node) const
375{
376 return m_nodeMap[const_cast<QSSGRenderGraphObject *>(node)];
377}
378
379QQuick3DWindowAttachment *QQuick3DSceneManager::getOrSetWindowAttachment(QQuickWindow &window)
380{
381
382 QQuick3DWindowAttachment *wa = nullptr;
383 if (auto aProperty = window.property(qtQQ3DWAPropName); aProperty.isValid())
384 wa = aProperty.value<QQuick3DWindowAttachment *>();
385
386 if (!wa) {
387 // WindowAttachment will not be created under 'window'.
388 // It should be deleted after all the cleanups related with 'window',
389 // otherwise some resourses deleted after it, will not be cleaned correctly.
390 wa = new QQuick3DWindowAttachment(&window);
391 window.setProperty(qtQQ3DWAPropName, QVariant::fromValue(wa));
392 }
393
394 return wa;
395}
396
397QQuick3DSceneManager::SyncResult QQuick3DSceneManager::cleanupNodes()
398{
399 SyncResult res = sharedResourceRemoved ? SyncResultFlag::SharedResourcesDirty : SyncResultFlag::None;
400 // Reset the shared resource removed value.
401 sharedResourceRemoved = false;
402 for (auto node : std::as_const(cleanupNodeList)) {
403 // Remove "spatial" nodes from scenegraph
404 if (QSSGRenderGraphObject::isNodeType(node->type)) {
405 QSSGRenderNode *spatialNode = static_cast<QSSGRenderNode *>(node);
406 spatialNode->removeFromGraph();
407 }
408
409 if (node->type == QQuick3DObjectPrivate::Type::Item2D) {
410 --inputHandlingEnabled;
411 } else if (node->type == QQuick3DObjectPrivate::Type::Image2D) {
412 auto image = static_cast<QSSGRenderImage *>(node);
413 if (image && image->m_qsgTexture != nullptr ) {
414 --inputHandlingEnabled;
415 }
416 }
417
418 // Remove all nodes from the node map because they will no
419 // longer be usable from this point from the frontend
420 m_nodeMap.remove(node);
421
422 if (QSSGRenderGraphObjectUtils::hasInternalFlag(*node, QSSGRenderGraphObjectUtils::InternalFlags::AutoRegisterExtension)) {
423 autoRegisteredExtensions.removeAll(node);
424 autoRegisteredExtensionsDirty = true;
425 }
426
427 if (QSSGRenderGraphObjectUtils::isUserRenderPass(node->type)) {
428 auto *userRenderPass = static_cast<QSSGRenderUserPass *>(node);
429 if (qsizetype idx = userRenderPasses.indexOf(userRenderPass); idx != -1) {
430 userRenderPasses.remove(idx);
431 userRenderPassesDirty = true;
432 }
433 }
434
435 // Some nodes will trigger resource cleanups that need to
436 // happen at a specified time (when graphics backend is active)
437 // So build another queue for graphics assets marked for removal
438 if (node->hasGraphicsResources()) {
439 wattached->queueForCleanup(node);
440 if (node->type == QSSGRenderGraphObject::Type::ResourceLoader)
441 resourceLoaders.remove(node);
442 } else {
443 delete node;
444 }
445 }
446
447 // Nodes are now "cleaned up" so clear the cleanup list
448 cleanupNodeList.clear();
449
450 return res;
451}
452
453QQuick3DSceneManager::SyncResult QQuick3DSceneManager::updateResources(QQuick3DObject **listHead)
454{
455 SyncResult res = SyncResultFlag::None;
456
457 // Detach the current list head first, and consume all reachable entries.
458 // New entries may be added to the new list while traversing, which will be
459 // visited on the next updateDirtyNodes() call.
460 bool hasSharedResources = false;
461 QQuick3DObject *updateList = *listHead;
462 *listHead = nullptr;
463 if (updateList)
464 QQuick3DObjectPrivate::get(updateList)->prevDirtyItem = &updateList;
465
466 QQuick3DObject *item = updateList;
467 while (item) {
468 // Different processing for resource nodes vs hierarchical nodes etc.
469 Q_ASSERT(!QSSGRenderGraphObject::isNodeType(QQuick3DObjectPrivate::get(item)->type) || !QSSGRenderGraphObject::isExtension(QQuick3DObjectPrivate::get(item)->type));
470 // handle hierarchical nodes
471 auto *po = QQuick3DObjectPrivate::get(item);
472 // Unlink before calling updateSpatialNode() below: if that call re-enters dirty() on
473 // this very object (e.g. via a QML binding evaluated as a side effect of the update),
474 // dirty() needs prevDirtyItem to already be null so it re-links the object via its own
475 // addToDirtyList() call, onto the (already emptied) scene manager list.
476 po->removeFromDirtyList();
477 updateDirtyResource(item);
478 hasSharedResources |= po->sharedResource;
479 item = updateList;
480 }
481
482 if (hasSharedResources)
483 res |= SyncResultFlag::SharedResourcesDirty;
484
485 return res;
486}
487
488void QQuick3DSceneManager::updateNodes(QQuick3DObject **listHead)
489{
490 // Detach the current list head first, and consume all reachable entries.
491 // New entries may be added to the new list while traversing, which will be
492 // visited on the next updateDirtyNodes() call.
493 QQuick3DObject *updateList = *listHead;
494 *listHead = nullptr;
495 if (updateList)
496 QQuick3DObjectPrivate::get(updateList)->prevDirtyItem = &updateList;
497
498 QQuick3DObject *item = updateList;
499 while (item) {
500 // Different processing for resource nodes vs hierarchical nodes (anything that's _not_ a resource)
501 Q_ASSERT(QSSGRenderGraphObject::isNodeType(QQuick3DObjectPrivate::get(item)->type));
502 // handle hierarchical nodes
503 // See the matching comment in updateResources() about the removeFromDirtyList()/
504 // updateSpatialNode() ordering.
505 QQuick3DObjectPrivate::get(item)->removeFromDirtyList();
506 updateDirtySpatialNode(static_cast<QQuick3DNode *>(item));
507 item = updateList;
508 }
509}
510
511QQuick3DSceneManager::SyncResult QQuick3DSceneManager::updateExtensions(QQuick3DObject **listHead)
512{
513 SyncResult ret = SyncResultFlag::None;
514
515 const auto updateDirtyExtensionNode = [this, &ret](QQuick3DObject *extension) {
516 QQuick3DObjectPrivate *po = QQuick3DObjectPrivate::get(extension);
517 po->dirtyAttributes = 0; // Not used, but we should still reset it.
518 QSSGRenderGraphObject *oldNode = po->spatialNode;
519 QSSGRenderGraphObject *newNode = extension->updateSpatialNode(oldNode);
520
521 const bool backendNodeChanged = oldNode != newNode;
522
523 if (backendNodeChanged)
524 ret |= SyncResultFlag::ExtensionsDiry;
525
526 // If the backend node changed, we need to clean up the old one.
527 if (oldNode && backendNodeChanged)
528 cleanup(oldNode);
529
530 // NOTE: cleanup() will remove the item from the node map and set the spatial node to nullptr.
531 // so we need to set it here.
532 po->spatialNode = newNode;
533
534 if (po->spatialNode)
535 m_nodeMap.insert(po->spatialNode, extension);
536
537 if (newNode && QSSGRenderGraphObjectUtils::hasInternalFlag(*newNode, QSSGRenderGraphObjectUtils::InternalFlags::AutoRegisterExtension)) {
538 const bool shouldInsert = autoRegisteredExtensions.indexOf(static_cast<QSSGRenderExtension *>(newNode)) == -1;
539 if (shouldInsert) {
540 autoRegisteredExtensions.push_back(static_cast<QSSGRenderExtension *>(newNode));
541 autoRegisteredExtensionsDirty = true;
542 }
543 }
544 };
545
546 // Detach the current list head first, and consume all reachable entries.
547 // New entries may be added to the new list while traversing, which will be
548 // visited on the next updateDirtyNodes() call.
549 QQuick3DObject *updateList = *listHead;
550 *listHead = nullptr;
551 if (updateList)
552 QQuick3DObjectPrivate::get(updateList)->prevDirtyItem = &updateList;
553
554 QQuick3DObject *item = updateList;
555 while (item) {
556 // Different processing for resource nodes vs hierarchical nodes (anything that's _not_ a resource)
557 Q_ASSERT(QSSGRenderGraphObject::isExtension(QQuick3DObjectPrivate::get(item)->type));
558 // handle hierarchical nodes
559 // See the matching comment in updateResources() about the removeFromDirtyList()/
560 // updateSpatialNode() ordering.
561 QQuick3DObjectPrivate::get(item)->removeFromDirtyList();
562 updateDirtyExtensionNode(item);
563 item = updateList;
564 }
565
566 return ret;
567}
568
569void QQuick3DSceneManager::preSync()
570{
571 for (auto it = std::begin(dirtyResources), end = std::end(dirtyResources); it != end; ++it) {
572 QQuick3DObject *next = *it;
573 while (next) {
574 next->preSync();
575 next = QQuick3DObjectPrivate::get(next)->nextDirtyItem;
576 }
577 }
578
579 for (auto it = std::begin(dirtyNodes), end = std::end(dirtyNodes); it != end; ++it) {
580 QQuick3DObject *next = *it;
581 while (next) {
582 next->preSync();
583 next = QQuick3DObjectPrivate::get(next)->nextDirtyItem;
584 }
585 }
586
587 for (auto it = std::begin(dirtyExtensions), end = std::end(dirtyExtensions); it != end; ++it) {
588 QQuick3DObject *next = *it;
589 while (next) {
590 next->preSync();
591 next = QQuick3DObjectPrivate::get(next)->nextDirtyItem;
592 }
593 }
594}
595
596////////
597/// QQuick3DWindowAttachment
598////////
599
600QQuick3DWindowAttachment::QQuick3DWindowAttachment(QQuickWindow *window)
601 : m_window(window)
602 , m_rootNode(new QSSGRenderRoot)
603{
604 if (window) {
605 // Act when the application calls window->releaseResources() and the
606 // render loop emits the corresponding signal in order to forward the
607 // event to us as well. (do not confuse with other release-resources
608 // type of functions, this is about dropping pipeline and other resource
609 // caches than can be automatically recreated if needed on the next frame)
610 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
611 QSGRenderContext *rc = wd->context;
612 if (QSSG_GUARD_X(rc, "QQuickWindow has no QSGRenderContext, this should not happen")) {
613 // QSGRenderContext signals are emitted on the render thread, if there is one; use DirectConnection
614 connect(rc, &QSGRenderContext::releaseCachedResourcesRequested, this, &QQuick3DWindowAttachment::onReleaseCachedResources, Qt::DirectConnection);
615 connect(rc, &QSGRenderContext::invalidated, this, &QQuick3DWindowAttachment::onInvalidated, Qt::DirectConnection);
616 }
617
618 // Do a final lifetime eval when the window is destroyed.
619 connect(window, &QQuickWindow::destroyed, this, &QQuick3DWindowAttachment::evaluateEol);
620 // afterAnimating is emitted on the main thread.
621 connect(window, &QQuickWindow::afterAnimating, this, &QQuick3DWindowAttachment::preSync);
622 // afterFrameEnd is emitted on render thread.
623 connect(window, &QQuickWindow::afterFrameEnd, this, &QQuick3DWindowAttachment::cleanupResources, Qt::DirectConnection);
624 }
625}
626
627QQuick3DWindowAttachment::~QQuick3DWindowAttachment()
628{
629 for (auto *manager : std::as_const(sceneManagerCleanupQueue)) {
630 sceneManagers.removeOne(manager);
631 delete manager;
632 }
633 // remaining sceneManagers should also be removed
634 qDeleteAll(sceneManagers);
635 QSSG_CHECK(QSSG_DEBUG_COND(resourceCleanupQueue.isEmpty()));
636
637 if (!pendingResourceCleanupQueue.isEmpty()) {
638 // Let's try to recover from this situation. Most likely this is some loader usage
639 // situation, where all View3Ds have been destroyed while the window still lives and might
640 // eventually just create a new View3D. We cannot release the resources here, as we'll need
641 // to do that on the render thread. Instaed we'll transform the pendingResourceCleanupQueue
642 // to a cleanup object that can be called on the render thread.
643 if (m_rci && m_window) {
644 new QSSGCleanupObject(m_rci, std::move(pendingResourceCleanupQueue), m_window);
645 QMetaObject::invokeMethod(m_window, &QQuickWindow::releaseResources, Qt::QueuedConnection);
646 } else {
647 qWarning() << "Pending resource cleanup queue not empty, but no RCI or window to clean it up!";
648 }
649 }
650
651 if (m_window)
652 m_window->setProperty(qtQQ3DWAPropName, QVariant());
653
654 delete m_rootNode;
655}
656
657void QQuick3DWindowAttachment::preSync()
658{
659 for (auto &sceneManager : std::as_const(sceneManagers))
660 sceneManager->preSync();
661}
662
663// Called from the render thread
664void QQuick3DWindowAttachment::cleanupResources()
665{
666 // Pass the scene managers list of resources marked for
667 // removal to the render context for deletion
668 // The render context will take ownership of the nodes
669 // and clear the list
670
671 // In special cases there is no rci because synchronize() is never called.
672 // This can happen when running with the software backend of Qt Quick.
673 // Handle this gracefully.
674 if (!m_rci)
675 return;
676
677 // Check if there's orphaned resources that needs to be
678 // cleaned out first.
679 if (resourceCleanupQueue.size() != 0)
680 m_rci->renderer()->cleanupResources(resourceCleanupQueue);
681}
682
683// Called on the render thread, if there is one
684void QQuick3DWindowAttachment::onReleaseCachedResources()
685{
686 if (m_rci)
687 m_rci->releaseCachedResources();
688 Q_EMIT releaseCachedResources();
689}
690
691void QQuick3DWindowAttachment::onInvalidated()
692{
693 // Find all objects that have graphics resources and queue them
694 // for cleanup.
695 // 1. We'll need to manually go through the nodes of each scene manager and mark
696 // objects with graphics resources for deletion.
697 // The scene graph is invalidated so we need to release graphics resources now, on the render thread.
698 for (auto &sceneManager : std::as_const(sceneManagers)) {
699 const auto objects = sceneManager->m_nodeMap.keys();
700 for (QSSGRenderGraphObject *obj : objects) {
701 if (obj->hasGraphicsResources())
702 sceneManager->cleanup(obj);
703 }
704 }
705
706 // Start: follow the normal clean-up procedure
707 for (auto &sceneManager : std::as_const(sceneManagers))
708 sceneManager->cleanupNodes();
709
710 for (QSSGRenderLayer *layers : std::as_const(pendingLayerCleanupQueue))
711 delete layers;
712 pendingLayerCleanupQueue.clear();
713
714 for (const auto &pr : std::as_const(pendingResourceCleanupQueue))
715 resourceCleanupQueue.insert(pr);
716 pendingResourceCleanupQueue.clear();
717
718 // NOTE!: It's essential that we release the cached resources before we
719 // call cleanupResources(), to avoid the expensive bookkeeping code involved
720 // for models. This is achieved by dropping the data to avoid expensive look-ups; it's going away anyways.
721 // (In the future, if/when cleanupDrawCallData() is improved, then this step might not be necessary).
722 onReleaseCachedResources();
723
724 cleanupResources();
725 // end
726
727 // If the SG RenderContex is invalidated and we're the only one holding onto the SSG
728 // RenderContextInterface then just release it. If the application is not going down
729 // a new RCI will be created/set during the next sync.
730 if (m_rci.use_count() == 1) {
731 m_rci.reset();
732 emit renderContextInterfaceChanged();
733 }
734}
735
736QQuick3DWindowAttachment::SyncResult QQuick3DWindowAttachment::synchronize(QSet<QSSGRenderGraphObject *> &resourceLoaders)
737{
738 // Terminate old scene managers
739 for (auto *manager : std::as_const(sceneManagerCleanupQueue)) {
740 sceneManagers.removeOne(manager);
741 delete manager;
742 }
743 // Terminate old scene managers
744 sceneManagerCleanupQueue = {};
745
746 SyncResult syncResult = SyncResultFlag::None;
747
748 // Cleanup
749 for (auto &sceneManager : std::as_const(sceneManagers))
750 syncResult |= sceneManager->cleanupNodes();
751
752 for (QSSGRenderLayer *layers : std::as_const(pendingLayerCleanupQueue))
753 delete layers;
754 pendingLayerCleanupQueue.clear();
755
756 // Resources
757 for (auto &sceneManager : std::as_const(sceneManagers))
758 syncResult |= sceneManager->updateDirtyResourceNodes();
759 // Spatial Nodes
760 for (auto &sceneManager : std::as_const(sceneManagers))
761 sceneManager->updateDirtySpatialNodes();
762 for (auto &sceneManager : std::as_const(sceneManagers))
763 syncResult |= sceneManager->updateDiryExtensions();
764 for (auto &sceneManager : std::as_const(sceneManagers))
765 syncResult |= sceneManager->updateDirtyResourceSecondPass();
766 // Bounding Boxes
767 for (auto &sceneManager : std::as_const(sceneManagers))
768 sceneManager->updateBoundingBoxes(*m_rci->bufferManager());
769 // Resource Loaders
770 for (auto &sceneManager : std::as_const(sceneManagers))
771 resourceLoaders.unite(sceneManager->resourceLoaders);
772
773 if ((syncResult & SyncResultFlag::SharedResourcesDirty)) {
774 // We know there are shared resources in the scene, so notify the "world".
775 // Ideally we should be more targeted, but for now this will do the job.
776 for (auto &sceneManager : std::as_const(sceneManagers))
777 sceneManager->requestUpdate();
778 }
779
780 // Prepare pending (adopted) resources for clean-up (will happen as a result of afterFrameEnd()).
781 for (const auto &pr : std::as_const(pendingResourceCleanupQueue))
782 resourceCleanupQueue.insert(pr);
783 pendingResourceCleanupQueue.clear();
784
785 return syncResult;
786}
787
788void QQuick3DWindowAttachment::requestUpdate()
789{
790 for (const auto &sm : std::as_const(sceneManagers))
791 sm->requestUpdate();
792}
793
794void QQuick3DWindowAttachment::evaluateEol()
795{
796 // NOTE: We'll re-iterate this list either on the next sync or if there's no sceneManagers left.
797 // See: sync and dtor.
798 for (QQuick3DSceneManager *manager : std::as_const(sceneManagerCleanupQueue))
799 sceneManagers.removeOne(manager);
800
801 // Keep the attachment, and the QSSGRenderRoot it owns, alive for as long as the window is,
802 // even when no scene managers remain. Nodes reference the root via rootNodeRef (e.g. imported
803 // scenes shared across views), and those references must stay valid across View3D create and
804 // destroy cycles within the same window; deleting the root here would leave them dangling.
805 // When the window goes away the attachment is deleted through the QQuickWindow::destroyed ->
806 // evaluateEol connection set up in the constructor
807 // (NOTE: QPointers are cleared out before the destroyed signal is sent).
808 if (sceneManagers.isEmpty() && m_window.isNull())
809 delete this;
810}
811
812QQuickWindow *QQuick3DWindowAttachment::window() const { return m_window; }
813
814void QQuick3DWindowAttachment::setRci(const std::shared_ptr<QSSGRenderContextInterface> &rciptr)
815{
816 QSSG_CHECK_X(m_rci == nullptr || m_rci.use_count() == 1, "Old render context was not released!");
817 m_rci = rciptr;
818
819 // For convenience we'll also set the SG rendere context here.
820 if (m_rci && m_window)
821 QSSGRendererPrivate::setSgRenderContext(*m_rci->renderer(), QQuickWindowPrivate::get(m_window)->context);
822
823 emit renderContextInterfaceChanged();
824}
825
826void QQuick3DWindowAttachment::registerSceneManager(QQuick3DSceneManager &manager)
827{
828 if (!sceneManagers.contains(&manager))
829 sceneManagers.push_back(&manager);
830}
831
832void QQuick3DWindowAttachment::unregisterSceneManager(QQuick3DSceneManager &manager)
833{
834 sceneManagers.removeOne(&manager);
835}
836
837void QQuick3DWindowAttachment::queueForCleanup(QSSGRenderGraphObject *obj)
838{
839 Q_ASSERT(obj->hasGraphicsResources());
840 pendingResourceCleanupQueue.push_back(obj);
841}
842
843void QQuick3DWindowAttachment::queueForCleanup(QQuick3DSceneManager *manager)
844{
845 if (!sceneManagerCleanupQueue.contains(manager))
846 sceneManagerCleanupQueue.push_back(manager);
847}
848
849void QQuick3DWindowAttachment::queueForCleanup(QSSGRenderLayer *layer)
850{
851 if (QSSGRenderRoot *lr = QSSGRenderRoot::get(layer->rootNodeRef); lr != m_rootNode) {
852 qWarning("Attempted to queue layer for cleanup that is not part of the current window!");
853 return;
854 }
855
856 if (!pendingLayerCleanupQueue.contains(layer)) {
857 layer->removeFromGraph();
858 pendingLayerCleanupQueue.push_back(layer);
859 }
860}
861
862QT_END_NAMESPACE
Q_INVOKABLE void cleanupResources()
Combined button and popup list for selecting options.
static void detachDirtyList(QQuick3DObject **head, size_t count)
static constexpr char qtQQ3DWAPropName[]