Qt 6.x
The Qt SDK
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
5#include "qquick3dobject_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
18
19static constexpr char qtQQ3DWAPropName[] { "_qtquick3dWindowAttachment" };
20
23{
24}
25
26// Should be deleted by QQuick3DWindowAttachment to ensure it's done
27// on the render thread.
29{
31 if (wattached)
33}
34
36{
37 if (window == m_window)
38 return;
39
40 if (window != m_window) {
41 if (wattached) {
42 // Unregister from old windows attached object
44 wattached = nullptr;
45 }
47 if (m_window) {
49 if (wattached)
51 }
52
54 }
55}
56
58{
59 return m_window;
60}
61
63{
66}
67
69{
71}
72
74{
77
78 if (auto front = m_nodeMap[item]) {
79 auto *po = QQuick3DObjectPrivate::get(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
90{
91
92}
93
95{
96
97}
98
100{
101
102}
103
105{
107 for (auto object : dirtyList) {
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(bounds.minimum, bounds.maximum);
115 }
117 }
118}
119
121{
122 auto it = std::begin(dirtyResources);
123 const auto end = std::end(dirtyResources);
124 bool ret = false;
125 for (; it != end; ++it)
126 ret |= updateResources(it);
127
128 return ret;
129}
130
132{
133 auto it = std::begin(dirtyNodes);
134 const auto end = std::end(dirtyNodes);
135 for (; it != end; ++it)
136 updateNodes(it);
137}
138
140{
141 auto it = std::begin(dirtyExtensions);
142 const auto end = std::end(dirtyExtensions);
143 for (; it != end; ++it)
144 updateExtensions(it);
145}
146
148{
149 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(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(itemPriv->spatialNode);
165 if (itemPriv->spatialNode) {
166 m_nodeMap.insert(itemPriv->spatialNode, resourceObject);
167 if (itemPriv->spatialNode->type == QSSGRenderGraphObject::Type::ResourceLoader) {
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
181{
182 QQuick3DObjectPrivate *itemPriv = QQuick3DObjectPrivate::get(spatialNode);
183 quint32 dirty = itemPriv->dirtyAttributes;
184 itemPriv->dirtyAttributes = 0;
185 QSSGRenderGraphObject *oldNode = itemPriv->spatialNode;
186 itemPriv->spatialNode = spatialNode->updateSpatialNode(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(itemPriv->spatialNode, spatialNode);
192 if (itemPriv->type == QQuick3DObjectPrivate::Type::Item2D && itemPriv->spatialNode != oldNode)
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 *>(spatialNode->parentItem());
200 if (nodeParent) {
201 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(
203 if (parentGraphNode) {
204 graphNode->parent->removeChild(*graphNode);
205 parentGraphNode->addChild(*graphNode);
206 }
207 }
208 }
209
210 if (graphNode && graphNode->parent == nullptr) {
211 QQuick3DNode *nodeParent = qobject_cast<QQuick3DNode *>(spatialNode->parentItem());
212 if (nodeParent) {
213 QSSGRenderNode *parentGraphNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(nodeParent)->spatialNode);
214 if (!parentGraphNode) {
215 // The parent spatial node hasn't been created yet
216 auto parentNode = QQuick3DObjectPrivate::get(nodeParent);
217 parentNode->spatialNode = nodeParent->updateSpatialNode(parentNode->spatialNode);
218 if (parentNode->spatialNode)
219 m_nodeMap.insert(parentNode->spatialNode, nodeParent);
220 parentGraphNode = static_cast<QSSGRenderNode *>(parentNode->spatialNode);
221 }
222 if (parentGraphNode)
223 parentGraphNode->addChild(*graphNode);
224 } else {
225 QQuick3DViewport *viewParent = qobject_cast<QQuick3DViewport *>(spatialNode->parent());
226 if (viewParent) {
227 auto sceneRoot = QQuick3DObjectPrivate::get(viewParent->scene());
228 if (!sceneRoot->spatialNode) // must have a scene root spatial node first
229 sceneRoot->spatialNode = viewParent->scene()->updateSpatialNode(sceneRoot->spatialNode);
230 if (sceneRoot->spatialNode) {
231 m_nodeMap.insert(sceneRoot->spatialNode, viewParent->scene());
232 static_cast<QSSGRenderNode *>(sceneRoot->spatialNode)->addChild(*graphNode);
233 }
234 }
235 }
236 }
237}
238
240{
241 return m_nodeMap[node];
242}
243
245{
246
247 QQuick3DWindowAttachment *wa = nullptr;
248 if (auto aProperty = window.property(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.
257 }
258
259 return wa;
260}
261
263{
265 sharedResourceRemoved = false;
266 for (auto node : std::as_const(cleanupNodeList)) {
267 // Remove "spatial" nodes from scenegraph
268 if (QSSGRenderGraphObject::isNodeType(node->type)) {
269 QSSGRenderNode *spatialNode = static_cast<QSSGRenderNode *>(node);
270 spatialNode->removeFromGraph();
271 }
272
273 if (node->type == QQuick3DObjectPrivate::Type::Item2D) {
275 } else if (node->type == QQuick3DObjectPrivate::Type::Image2D) {
276 auto image = static_cast<QSSGRenderImage *>(node);
277 if (image && image->m_qsgTexture != nullptr ) {
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(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
291 if (node->type == QSSGRenderGraphObject::Type::ResourceLoader)
293 } else {
294 delete node;
295 }
296 }
297
298 // Nodes are now "cleaned up" so clear the cleanup list
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(updateList)->prevDirtyItem = &updateList;
314
315 QQuick3DObject *item = updateList;
316 while (item) {
317 // Different processing for resource nodes vs hierarchical nodes etc.
319 // handle hierarchical nodes
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(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)
344 // handle hierarchical nodes
347 item = updateList;
348 }
349}
350
351void QQuick3DSceneManager::updateExtensions(QQuick3DObject **listHead)
352{
353 const auto updateDirtyExtensionNode = [this](QQuick3DObject *extension) {
355 po->dirtyAttributes = 0; // Not used, but we should still reset it.
357 po->spatialNode = extension->updateSpatialNode(node);
358 if (po->spatialNode)
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(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)
374 // handle hierarchical nodes
375 updateDirtyExtensionNode(item);
377 item = updateList;
378 }
379}
380
382{
383 for (auto it = std::begin(dirtyResources), end = std::end(dirtyResources); it != end; ++it) {
385 while (next) {
386 next->preSync();
388 }
389 }
390
391 for (auto it = std::begin(dirtyNodes), end = std::end(dirtyNodes); it != end; ++it) {
393 while (next) {
394 next->preSync();
396 }
397 }
398
399 for (auto it = std::begin(dirtyExtensions), end = std::end(dirtyExtensions); it != end; ++it) {
401 while (next) {
402 next->preSync();
404 }
405 }
406}
407
411
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)
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(rc, &QSGRenderContext::releaseCachedResourcesRequested, this, &QQuick3DWindowAttachment::onReleaseCachedResources, Qt::DirectConnection);
426 connect(rc, &QSGRenderContext::invalidated, this, &QQuick3DWindowAttachment::onInvalidated, Qt::DirectConnection);
427 }
428
429 // We put this in the back of the queue to allow any clean-up of resources to happen first.
431 // afterAnimating is emitted on the main thread.
432 connect(window, &QQuickWindow::afterAnimating, this, &QQuick3DWindowAttachment::preSync);
433 // afterFrameEnd is emitted on render thread.
435 }
436}
437
439{
440 for (auto manager: sceneManagerCleanupQueue) {
441 sceneManagers.removeOne(manager);
442 delete manager;
443 }
444 // remaining sceneManagers should also be removed
445 qDeleteAll(sceneManagers);
446 qDeleteAll(resourceCleanupQueue);
447 qDeleteAll(pendingResourceCleanupQueue);
448
449 QSSG_CHECK_X(!m_rci || m_rci.use_count() == 1, "RCI has unexpected reference count!");
450}
451
453{
454 for (auto &sceneManager : std::as_const(sceneManagers))
455 sceneManager->preSync();
456}
457
458// Called from the render thread
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(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();
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();
495 }
496}
497
499{
500 // Terminate old scene managers
501 for (auto manager: sceneManagerCleanupQueue) {
502 sceneManagers.removeOne(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(sceneManagers))
512 sharedUpdateNeeded |= sceneManager->cleanupNodes();
513
514 // Resources
515 for (auto &sceneManager : std::as_const(sceneManagers))
516 sharedUpdateNeeded |= sceneManager->updateDirtyResourceNodes();
517 // Spatial Nodes
518 for (auto &sceneManager : std::as_const(sceneManagers))
519 sceneManager->updateDirtySpatialNodes();
520 for (auto &sceneManager : std::as_const(sceneManagers))
521 sceneManager->updateDiryExtensions();
522 // Bounding Boxes
523 for (auto &sceneManager : std::as_const(sceneManagers))
524 sceneManager->updateBoundingBoxes(*m_rci->bufferManager());
525 // Resource Loaders
526 for (auto &sceneManager : std::as_const(sceneManagers))
527 resourceLoaders.unite(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(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(pendingResourceCleanupQueue))
538 resourceCleanupQueue.insert(pr);
539 pendingResourceCleanupQueue.clear();
540
541 return sharedUpdateNeeded;
542}
543
545{
546 for (const auto &sm : std::as_const(sceneManagers))
547 sm->requestUpdate();
548}
549
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;
557}
558
560{
561 if (!sceneManagers.contains(&manager))
562 sceneManagers.push_back(&manager);
563}
564
566{
567 sceneManagers.removeOne(&manager);
568}
569
571{
573 pendingResourceCleanupQueue.push_back(obj);
574}
575
577{
578 if (!sceneManagerCleanupQueue.contains(manager))
579 sceneManagerCleanupQueue.push_back(manager);
580}
581
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:956
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
Definition qlist.h:74
bool removeOne(const AT &t)
Definition qlist.h:581
void push_back(parameter_type t)
Definition qlist.h:672
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
QVariant property(const char *name) const
Returns the value of the object's name property.
Definition qobject.cpp:4187
bool setProperty(const char *name, const QVariant &value)
Sets the value of the object's name property to value.
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
void deleteLater()
\threadsafe
Definition qobject.cpp:2352
QSSGRenderGraphObject * updateSpatialNode(QSSGRenderGraphObject *node) override
QPointer< QQuick3DSceneManager > sceneManager
QQuick3DObject ** prevDirtyItem
QQuick3DObject * nextDirtyItem
static QQuick3DObjectPrivate * get(QQuick3DObject *item)
QSSGRenderGraphObject * spatialNode
\qmltype Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DObject \inherits QtObject
QQuick3DObject * parent
\qmlproperty Object3D QtQuick3D::Object3D::parent This property holds the parent of the Object3D in a...
virtual QSSGRenderGraphObject * updateSpatialNode(QSSGRenderGraphObject *node)
void setWindow(QQuickWindow *window)
void updateDirtyResource(QQuick3DObject *resourceObject)
QSet< QSSGRenderGraphObject * > resourceLoaders
QQuick3DSceneManager(QObject *parent=nullptr)
QPointer< QQuick3DWindowAttachment > wattached
QList< QQuick3DObject * > dirtyBoundingBoxList
QQuick3DObject * dirtyNodes[size_t(NodePriority::Count)]
static QQuick3DWindowAttachment * getOrSetWindowAttachment(QQuickWindow &window)
QQuick3DObject * lookUpNode(const QSSGRenderGraphObject *node) const
QQuick3DObject * dirtyExtensions[size_t(ExtensionPriority::Count)]
void dirtyItem(QQuick3DObject *item)
QQuick3DObject * dirtyResources[size_t(ResourcePriority::Count)]
QList< QSSGRenderGraphObject * > cleanupNodeList
void updateBoundingBoxes(QSSGBufferManager &mgr)
QHash< const QSSGRenderGraphObject *, QQuick3DObject * > m_nodeMap
void updateDirtySpatialNode(QQuick3DNode *spatialNode)
void cleanup(QSSGRenderGraphObject *item)
QQuick3DNode * scene
void unregisterSceneManager(QQuick3DSceneManager &manager)
Q_INVOKABLE bool synchronize(QSet< QSSGRenderGraphObject * > &resourceLoaders)
void registerSceneManager(QQuick3DSceneManager &manager)
QQuickWindow * window() const
void queueForCleanup(QSSGRenderGraphObject *obj)
QQuick3DWindowAttachment(QQuickWindow *window)
QQuick3DWindowAttachment.
void setRci(const std::shared_ptr< QSSGRenderContextInterface > &rciptr)
static QQuickWindowPrivate * get(QQuickWindow *c)
QSGRenderContext * context
\qmltype Window \instantiates QQuickWindow \inqmlmodule QtQuick
void releaseCachedResourcesRequested()
Class representing 3D range or axis aligned bounding box.
QVector3D minimum
QVector3D maximum
QSSGBounds3 getModelBounds(const QSSGRenderModel *model) const
Definition qset.h:18
qsizetype size() const
Definition qset.h:50
bool remove(const T &value)
Definition qset.h:63
QSet< T > & unite(const QSet< T > &other)
Definition qset.h:225
iterator insert(const T &value)
Definition qset.h:155
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
void extension()
[6]
Definition dialogs.cpp:230
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
short next
Definition keywords.cpp:445
Combined button and popup list for selecting options.
@ DirectConnection
Definition image.cpp:4
return ret
GLuint GLuint end
GLuint object
[3]
GLenum type
GLhandleARB obj
[2]
static QT_BEGIN_NAMESPACE constexpr char qtQQ3DWAPropName[]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QSSG_GUARD_X(cond, msg)
#define QSSG_CHECK_X(cond, msg)
#define Q_EMIT
#define emit
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:45
QSqlQueryModel * model
[16]
QGraphicsItem * item
aWidget window() -> setWindowTitle("New Window Title")
[2]
QNetworkAccessManager manager
bool contains(const AT &t) const noexcept
Definition qlist.h:44
static Q_REQUIRED_RESULT constexpr bool isNodeType(Type type) Q_DECL_NOTHROW
static constexpr bool isExtension(Type type) noexcept
static Q_REQUIRED_RESULT constexpr bool hasGraphicsResources(Type type) Q_DECL_NOTHROW
void addChild(QSSGRenderNode &inChild)
QSSGRenderNode * parent
void removeChild(QSSGRenderNode &inChild)
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent