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 <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
14
15#include <QtQuick3DUtils/private/qssgassert_p.h>
16
17QT_BEGIN_NAMESPACE
18
19static constexpr char qtQQ3DWAPropName[] { "_qtquick3dWindowAttachment" };
20
21QQuick3DSceneManager::QQuick3DSceneManager(QObject *parent)
22 : QObject(parent)
23{
24}
25
26// Should be deleted by QQuick3DWindowAttachment to ensure it's done
27// on the render thread.
28QQuick3DSceneManager::~QQuick3DSceneManager()
29{
30 cleanupNodes();
31 if (wattached)
32 wattached->unregisterSceneManager(manager&: *this);
33}
34
35void QQuick3DSceneManager::setWindow(QQuickWindow *window)
36{
37 if (window == m_window)
38 return;
39
40 if (window != m_window) {
41 if (wattached) {
42 // Unregister from old windows attached object
43 wattached->unregisterSceneManager(manager&: *this);
44 wattached = nullptr;
45 }
46 m_window = window;
47 if (m_window) {
48 wattached = getOrSetWindowAttachment(window&: *m_window);
49 if (wattached)
50 wattached->registerSceneManager(manager&: *this);
51 }
52
53 emit windowChanged();
54 }
55}
56
57QQuickWindow *QQuick3DSceneManager::window()
58{
59 return m_window;
60}
61
62void QQuick3DSceneManager::dirtyItem(QQuick3DObject *item)
63{
64 Q_UNUSED(item);
65 emit needsUpdate();
66}
67
68void QQuick3DSceneManager::requestUpdate()
69{
70 emit needsUpdate();
71}
72
73void QQuick3DSceneManager::cleanup(QSSGRenderGraphObject *item)
74{
75 Q_ASSERT(!cleanupNodeList.contains(item));
76 cleanupNodeList.append(t: item);
77
78 if (auto front = m_nodeMap[item]) {
79 auto *po = QQuick3DObjectPrivate::get(item: front);
80 sharedResourceRemoved |= po->sharedResource;
81 po->spatialNode = nullptr;
82 }
83
84 // The front-end object is no longer reachable (destroyed) so make sure we don't return it
85 // when doing a node look-up.
86 m_nodeMap[item] = nullptr;
87}
88
89void QQuick3DSceneManager::polishItems()
90{
91
92}
93
94void QQuick3DSceneManager::forcePolish()
95{
96
97}
98
99void QQuick3DSceneManager::sync()
100{
101
102}
103
104void QQuick3DSceneManager::updateBoundingBoxes(QSSGBufferManager &mgr)
105{
106 const QList<QQuick3DObject *> dirtyList = dirtyBoundingBoxList;
107 for (auto object : dirtyList) {
108 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(item: object);
109 if (itemPriv->sceneManager == nullptr)
110 continue;
111 auto model = static_cast<QSSGRenderModel *>(itemPriv->spatialNode);
112 if (model) {
113 QSSGBounds3 bounds = mgr.getModelBounds(model);
114 static_cast<QQuick3DModel *>(object)->setBounds(min: bounds.minimum, max: bounds.maximum);
115 }
116 dirtyBoundingBoxList.removeOne(t: object);
117 }
118}
119
120bool QQuick3DSceneManager::updateDirtyResourceNodes()
121{
122 auto it = std::begin(arr&: dirtyResources);
123 const auto end = std::end(arr&: dirtyResources);
124 bool ret = false;
125 for (; it != end; ++it)
126 ret |= updateResources(listHead: it);
127
128 return ret;
129}
130
131void QQuick3DSceneManager::updateDirtySpatialNodes()
132{
133 auto it = std::begin(arr&: dirtyNodes);
134 const auto end = std::end(arr&: dirtyNodes);
135 for (; it != end; ++it)
136 updateNodes(listHead: it);
137}
138
139void QQuick3DSceneManager::updateDiryExtensions()
140{
141 auto it = std::begin(arr&: dirtyExtensions);
142 const auto end = std::end(arr&: dirtyExtensions);
143 for (; it != end; ++it)
144 updateExtensions(listHead: it);
145}
146
147void QQuick3DSceneManager::updateDirtyResource(QQuick3DObject *resourceObject)
148{
149 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(item: resourceObject);
150 quint32 dirty = itemPriv->dirtyAttributes;
151 Q_UNUSED(dirty);
152 itemPriv->dirtyAttributes = 0;
153
154 // Check if an Image2D has either acquired or lost its SourceItem.
155 // This is used to update inputHandlingEnabled counter that View3D uses to
156 // implicitly enable or disable internal input processing.
157
158 bool beforeSourceItemValid = false;
159 if (itemPriv->spatialNode && itemPriv->spatialNode->type == QSSGRenderGraphObject::Type::Image2D) {
160 auto image = static_cast<QSSGRenderImage *>(itemPriv->spatialNode);
161 beforeSourceItemValid = image && image->m_qsgTexture != nullptr;
162 }
163
164 itemPriv->spatialNode = resourceObject->updateSpatialNode(node: itemPriv->spatialNode);
165 if (itemPriv->spatialNode) {
166 m_nodeMap.insert(key: itemPriv->spatialNode, value: resourceObject);
167 if (itemPriv->spatialNode->type == QSSGRenderGraphObject::Type::ResourceLoader) {
168 resourceLoaders.insert(value: itemPriv->spatialNode);
169 } else if (itemPriv->spatialNode->type == QSSGRenderGraphObject::Type::Image2D) {
170 auto image = static_cast<QSSGRenderImage *>(itemPriv->spatialNode);
171 bool afterSouceItemValid = image && image->m_qsgTexture != nullptr;
172 if (beforeSourceItemValid != afterSouceItemValid)
173 inputHandlingEnabled += (afterSouceItemValid) ? 1 : -1;
174 }
175 }
176
177 // resource nodes dont go in the tree, so we dont need to parent them
178}
179
180void QQuick3DSceneManager::updateDirtySpatialNode(QQuick3DNode *spatialNode)
181{
182 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(item: spatialNode);
183 quint32 dirty = itemPriv->dirtyAttributes;
184 itemPriv->dirtyAttributes = 0;
185 QSSGRenderGraphObject *oldNode = itemPriv->spatialNode;
186 itemPriv->spatialNode = spatialNode->updateSpatialNode(node: oldNode);
187 // NOTE: We always update the node map, as we can end-up with the a node map where the mapping
188 // has been 'disconnected', e.g., the front-end object removed from the scene only to be later
189 // re-used.
190 if (itemPriv->spatialNode) {
191 m_nodeMap.insert(key: itemPriv->spatialNode, value: spatialNode);
192 if (itemPriv->type == QQuick3DObjectPrivate::Type::Item2D && itemPriv->spatialNode != oldNode)
193 ++inputHandlingEnabled;
194 }
195
196 QSSGRenderNode *graphNode = static_cast<QSSGRenderNode *>(itemPriv->spatialNode);
197
198 if (graphNode && graphNode->parent && dirty & QQuick3DObjectPrivate::ParentChanged) {
199 QQuick3DNode *nodeParent = qobject_cast<QQuick3DNode *>(object: spatialNode->parentItem());
200 if (nodeParent) {
201 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(
202 QQuick3DObjectPrivate::get(item: nodeParent)->spatialNode);
203 if (parentGraphNode) {
204 graphNode->parent->removeChild(inChild&: *graphNode);
205 parentGraphNode->addChild(inChild&: *graphNode);
206 }
207 }
208 }
209
210 if (graphNode && graphNode->parent == nullptr) {
211 QQuick3DNode *nodeParent = qobject_cast<QQuick3DNode *>(object: spatialNode->parentItem());
212 if (nodeParent) {
213 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(item: nodeParent)->spatialNode);
214 if (!parentGraphNode) {
215 // The parent spatial node hasn't been created yet
216 auto parentNode = QQuick3DObjectPrivate::get(item: nodeParent);
217 parentNode->spatialNode = nodeParent->updateSpatialNode(node: parentNode->spatialNode);
218 if (parentNode->spatialNode)
219 m_nodeMap.insert(key: parentNode->spatialNode, value: nodeParent);
220 parentGraphNode = static_cast<QSSGRenderNode *>(parentNode->spatialNode);
221 }
222 if (parentGraphNode)
223 parentGraphNode->addChild(inChild&: *graphNode);
224 } else {
225 QQuick3DViewport *viewParent = qobject_cast<QQuick3DViewport *>(object: spatialNode->parent());
226 if (viewParent) {
227 auto sceneRoot = QQuick3DObjectPrivate::get(item: viewParent->scene());
228 if (!sceneRoot->spatialNode) // must have a scene root spatial node first
229 sceneRoot->spatialNode = viewParent->scene()->updateSpatialNode(node: sceneRoot->spatialNode);
230 if (sceneRoot->spatialNode) {
231 m_nodeMap.insert(key: sceneRoot->spatialNode, value: viewParent->scene());
232 static_cast<QSSGRenderNode *>(sceneRoot->spatialNode)->addChild(inChild&: *graphNode);
233 }
234 }
235 }
236 }
237}
238
239QQuick3DObject *QQuick3DSceneManager::lookUpNode(const QSSGRenderGraphObject *node) const
240{
241 return m_nodeMap[node];
242}
243
244QQuick3DWindowAttachment *QQuick3DSceneManager::getOrSetWindowAttachment(QQuickWindow &window)
245{
246
247 QQuick3DWindowAttachment *wa = nullptr;
248 if (auto aProperty = window.property(name: qtQQ3DWAPropName); aProperty.isValid())
249 wa = aProperty.value<QQuick3DWindowAttachment *>();
250
251 if (!wa) {
252 // WindowAttachment will not be created under 'window'.
253 // It should be deleted after all the cleanups related with 'window',
254 // otherwise some resourses deleted after it, will not be cleaned correctly.
255 wa = new QQuick3DWindowAttachment(&window);
256 window.setProperty(name: qtQQ3DWAPropName, value: QVariant::fromValue(value: wa));
257 }
258
259 return wa;
260}
261
262bool QQuick3DSceneManager::cleanupNodes()
263{
264 bool ret = sharedResourceRemoved;
265 sharedResourceRemoved = false;
266 for (auto node : std::as_const(t&: cleanupNodeList)) {
267 // Remove "spatial" nodes from scenegraph
268 if (QSSGRenderGraphObject::isNodeType(type: node->type)) {
269 QSSGRenderNode *spatialNode = static_cast<QSSGRenderNode *>(node);
270 spatialNode->removeFromGraph();
271 }
272
273 if (node->type == QQuick3DObjectPrivate::Type::Item2D) {
274 --inputHandlingEnabled;
275 } else if (node->type == QQuick3DObjectPrivate::Type::Image2D) {
276 auto image = static_cast<QSSGRenderImage *>(node);
277 if (image && image->m_qsgTexture != nullptr ) {
278 --inputHandlingEnabled;
279 }
280 }
281
282 // Remove all nodes from the node map because they will no
283 // longer be usable from this point from the frontend
284 m_nodeMap.remove(key: node);
285
286 // Some nodes will trigger resource cleanups that need to
287 // happen at a specified time (when graphics backend is active)
288 // So build another queue for graphics assets marked for removal
289 if (node->hasGraphicsResources()) {
290 wattached->queueForCleanup(obj: node);
291 if (node->type == QSSGRenderGraphObject::Type::ResourceLoader)
292 resourceLoaders.remove(value: node);
293 } else {
294 delete node;
295 }
296 }
297
298 // Nodes are now "cleaned up" so clear the cleanup list
299 cleanupNodeList.clear();
300
301 return ret;
302}
303
304bool QQuick3DSceneManager::updateResources(QQuick3DObject **listHead)
305{
306 // Detach the current list head first, and consume all reachable entries.
307 // New entries may be added to the new list while traversing, which will be
308 // visited on the next updateDirtyNodes() call.
309 bool ret = false;
310 QQuick3DObject *updateList = *listHead;
311 *listHead = nullptr;
312 if (updateList)
313 QQuick3DObjectPrivate::get(item: updateList)->prevDirtyItem = &updateList;
314
315 QQuick3DObject *item = updateList;
316 while (item) {
317 // Different processing for resource nodes vs hierarchical nodes etc.
318 Q_ASSERT(!QSSGRenderGraphObject::isNodeType(QQuick3DObjectPrivate::get(item)->type) || !QSSGRenderGraphObject::isExtension(QQuick3DObjectPrivate::get(item)->type));
319 // handle hierarchical nodes
320 updateDirtyResource(resourceObject: item);
321 auto *po = QQuick3DObjectPrivate::get(item);
322 ret |= po->sharedResource;
323 po->removeFromDirtyList();
324 item = updateList;
325 }
326
327 return ret;
328}
329
330void QQuick3DSceneManager::updateNodes(QQuick3DObject **listHead)
331{
332 // Detach the current list head first, and consume all reachable entries.
333 // New entries may be added to the new list while traversing, which will be
334 // visited on the next updateDirtyNodes() call.
335 QQuick3DObject *updateList = *listHead;
336 *listHead = nullptr;
337 if (updateList)
338 QQuick3DObjectPrivate::get(item: updateList)->prevDirtyItem = &updateList;
339
340 QQuick3DObject *item = updateList;
341 while (item) {
342 // Different processing for resource nodes vs hierarchical nodes (anything that's _not_ a resource)
343 Q_ASSERT(QSSGRenderGraphObject::isNodeType(QQuick3DObjectPrivate::get(item)->type));
344 // handle hierarchical nodes
345 updateDirtySpatialNode(spatialNode: static_cast<QQuick3DNode *>(item));
346 QQuick3DObjectPrivate::get(item)->removeFromDirtyList();
347 item = updateList;
348 }
349}
350
351void QQuick3DSceneManager::updateExtensions(QQuick3DObject **listHead)
352{
353 const auto updateDirtyExtensionNode = [this](QQuick3DObject *extension) {
354 QQuick3DObjectPrivate *po = QQuick3DObjectPrivate::get(item: extension);
355 po->dirtyAttributes = 0; // Not used, but we should still reset it.
356 QSSGRenderGraphObject *node = po->spatialNode;
357 po->spatialNode = extension->updateSpatialNode(node);
358 if (po->spatialNode)
359 m_nodeMap.insert(key: po->spatialNode, value: extension);
360 };
361
362 // Detach the current list head first, and consume all reachable entries.
363 // New entries may be added to the new list while traversing, which will be
364 // visited on the next updateDirtyNodes() call.
365 QQuick3DObject *updateList = *listHead;
366 *listHead = nullptr;
367 if (updateList)
368 QQuick3DObjectPrivate::get(item: updateList)->prevDirtyItem = &updateList;
369
370 QQuick3DObject *item = updateList;
371 while (item) {
372 // Different processing for resource nodes vs hierarchical nodes (anything that's _not_ a resource)
373 Q_ASSERT(QSSGRenderGraphObject::isExtension(QQuick3DObjectPrivate::get(item)->type));
374 // handle hierarchical nodes
375 updateDirtyExtensionNode(item);
376 QQuick3DObjectPrivate::get(item)->removeFromDirtyList();
377 item = updateList;
378 }
379}
380
381void QQuick3DSceneManager::preSync()
382{
383 for (auto it = std::begin(arr&: dirtyResources), end = std::end(arr&: dirtyResources); it != end; ++it) {
384 QQuick3DObject *next = *it;
385 while (next) {
386 next->preSync();
387 next = QQuick3DObjectPrivate::get(item: next)->nextDirtyItem;
388 }
389 }
390
391 for (auto it = std::begin(arr&: dirtyNodes), end = std::end(arr&: dirtyNodes); it != end; ++it) {
392 QQuick3DObject *next = *it;
393 while (next) {
394 next->preSync();
395 next = QQuick3DObjectPrivate::get(item: next)->nextDirtyItem;
396 }
397 }
398
399 for (auto it = std::begin(arr&: dirtyExtensions), end = std::end(arr&: dirtyExtensions); it != end; ++it) {
400 QQuick3DObject *next = *it;
401 while (next) {
402 next->preSync();
403 next = QQuick3DObjectPrivate::get(item: next)->nextDirtyItem;
404 }
405 }
406}
407
408////////
409/// QQuick3DWindowAttachment
410////////
411
412QQuick3DWindowAttachment::QQuick3DWindowAttachment(QQuickWindow *window)
413 : m_window(window)
414{
415 if (window) {
416 // Act when the application calls window->releaseResources() and the
417 // render loop emits the corresponding signal in order to forward the
418 // event to us as well. (do not confuse with other release-resources
419 // type of functions, this is about dropping pipeline and other resource
420 // caches than can be automatically recreated if needed on the next frame)
421 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
422 QSGRenderContext *rc = wd->context;
423 if (QSSG_GUARD_X(rc, "QQuickWindow has no QSGRenderContext, this should not happen")) {
424 // QSGRenderContext signals are emitted on the render thread, if there is one; use DirectConnection
425 connect(sender: rc, signal: &QSGRenderContext::releaseCachedResourcesRequested, context: this, slot: &QQuick3DWindowAttachment::onReleaseCachedResources, type: Qt::DirectConnection);
426 connect(sender: rc, signal: &QSGRenderContext::invalidated, context: this, slot: &QQuick3DWindowAttachment::onInvalidated, type: Qt::DirectConnection);
427 }
428
429 // We put this in the back of the queue to allow any clean-up of resources to happen first.
430 connect(sender: window, signal: &QQuickWindow::destroyed, context: this, slot: &QObject::deleteLater);
431 // afterAnimating is emitted on the main thread.
432 connect(sender: window, signal: &QQuickWindow::afterAnimating, context: this, slot: &QQuick3DWindowAttachment::preSync);
433 // afterFrameEnd is emitted on render thread.
434 connect(sender: window, signal: &QQuickWindow::afterFrameEnd, context: this, slot: &QQuick3DWindowAttachment::cleanupResources, type: Qt::DirectConnection);
435 }
436}
437
438QQuick3DWindowAttachment::~QQuick3DWindowAttachment()
439{
440 for (auto manager: sceneManagerCleanupQueue) {
441 sceneManagers.removeOne(t: manager);
442 delete manager;
443 }
444 // remaining sceneManagers should also be removed
445 qDeleteAll(c: sceneManagers);
446 qDeleteAll(c: resourceCleanupQueue);
447 qDeleteAll(c: pendingResourceCleanupQueue);
448
449 QSSG_CHECK_X(!m_rci || m_rci.use_count() == 1, "RCI has unexpected reference count!");
450}
451
452void QQuick3DWindowAttachment::preSync()
453{
454 for (auto &sceneManager : std::as_const(t&: sceneManagers))
455 sceneManager->preSync();
456}
457
458// Called from the render thread
459void QQuick3DWindowAttachment::cleanupResources()
460{
461 // Pass the scene managers list of resources marked for
462 // removal to the render context for deletion
463 // The render context will take ownership of the nodes
464 // and clear the list
465
466 // In special cases there is no rci because synchronize() is never called.
467 // This can happen when running with the software backend of Qt Quick.
468 // Handle this gracefully.
469 if (!m_rci)
470 return;
471
472 // Check if there's orphaned resources that needs to be
473 // cleaned out first.
474 if (resourceCleanupQueue.size() != 0)
475 m_rci->cleanupResources(resources&: resourceCleanupQueue);
476}
477
478// Called on the render thread, if there is one
479void QQuick3DWindowAttachment::onReleaseCachedResources()
480{
481 if (m_rci)
482 m_rci->releaseCachedResources();
483 Q_EMIT releaseCachedResources();
484}
485
486void QQuick3DWindowAttachment::onInvalidated()
487{
488 // If the SG RenderContex is invalidated and we're the only one holding onto the SSG
489 // RenderContextInterface then just release it. If the application is not going down
490 // a new RCI will be created/set during the next sync.
491 if (m_rci.use_count() == 1) {
492 onReleaseCachedResources();
493 m_rci.reset();
494 emit renderContextInterfaceChanged();
495 }
496}
497
498bool QQuick3DWindowAttachment::synchronize(QSet<QSSGRenderGraphObject *> &resourceLoaders)
499{
500 // Terminate old scene managers
501 for (auto manager: sceneManagerCleanupQueue) {
502 sceneManagers.removeOne(t: manager);
503 delete manager;
504 }
505 // Terminate old scene managers
506 sceneManagerCleanupQueue = {};
507
508 bool sharedUpdateNeeded = false;
509
510 // Cleanup
511 for (auto &sceneManager : std::as_const(t&: sceneManagers))
512 sharedUpdateNeeded |= sceneManager->cleanupNodes();
513
514 // Resources
515 for (auto &sceneManager : std::as_const(t&: sceneManagers))
516 sharedUpdateNeeded |= sceneManager->updateDirtyResourceNodes();
517 // Spatial Nodes
518 for (auto &sceneManager : std::as_const(t&: sceneManagers))
519 sceneManager->updateDirtySpatialNodes();
520 for (auto &sceneManager : std::as_const(t&: sceneManagers))
521 sceneManager->updateDiryExtensions();
522 // Bounding Boxes
523 for (auto &sceneManager : std::as_const(t&: sceneManagers))
524 sceneManager->updateBoundingBoxes(mgr&: *m_rci->bufferManager());
525 // Resource Loaders
526 for (auto &sceneManager : std::as_const(t&: sceneManagers))
527 resourceLoaders.unite(other: sceneManager->resourceLoaders);
528
529 if (sharedUpdateNeeded) {
530 // We know there are shared resources in the scene, so notify the "world".
531 // Ideally we should be more targeted, but for now this will do the job.
532 for (auto &sceneManager : std::as_const(t&: sceneManagers))
533 sceneManager->requestUpdate();
534 }
535
536 // Prepare pending (adopted) resources for clean-up (will happen as a result of afterFrameEnd()).
537 for (const auto &pr : std::as_const(t&: pendingResourceCleanupQueue))
538 resourceCleanupQueue.insert(value: pr);
539 pendingResourceCleanupQueue.clear();
540
541 return sharedUpdateNeeded;
542}
543
544void QQuick3DWindowAttachment::requestUpdate()
545{
546 for (const auto &sm : std::as_const(t&: sceneManagers))
547 sm->requestUpdate();
548}
549
550QQuickWindow *QQuick3DWindowAttachment::window() const { return m_window; }
551
552void QQuick3DWindowAttachment::setRci(const std::shared_ptr<QSSGRenderContextInterface> &rciptr)
553{
554 QSSG_CHECK_X(m_rci == nullptr || m_rci.use_count() == 1, "Old render context was not released!");
555 m_rci = rciptr;
556 emit renderContextInterfaceChanged();
557}
558
559void QQuick3DWindowAttachment::registerSceneManager(QQuick3DSceneManager &manager)
560{
561 if (!sceneManagers.contains(t: &manager))
562 sceneManagers.push_back(t: &manager);
563}
564
565void QQuick3DWindowAttachment::unregisterSceneManager(QQuick3DSceneManager &manager)
566{
567 sceneManagers.removeOne(t: &manager);
568}
569
570void QQuick3DWindowAttachment::queueForCleanup(QSSGRenderGraphObject *obj)
571{
572 Q_ASSERT(obj->hasGraphicsResources());
573 pendingResourceCleanupQueue.push_back(t: obj);
574}
575
576void QQuick3DWindowAttachment::queueForCleanup(QQuick3DSceneManager *manager)
577{
578 if (!sceneManagerCleanupQueue.contains(t: manager))
579 sceneManagerCleanupQueue.push_back(t: manager);
580}
581
582QT_END_NAMESPACE
583

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