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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtquick3d/src/quick3d/qquick3dscenemanager.cpp