1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtQuick3D/qquick3dobject.h>
5#include <QtQuick3D/private/qquick3ditem2d_p.h>
6
7#include <QtQuick/private/qquickitem_p.h>
8#include <QtQuick/private/qsgrenderer_p.h>
9#include <QtQuick/private/qquickwindow_p.h>
10
11#include <QtQuick3DRuntimeRender/private/qssgrenderitem2d_p.h>
12#include "qquick3dnode_p_p.h"
13
14#include <QtGui/rhi/qrhi.h>
15
16QT_BEGIN_NAMESPACE
17
18/*
19internal
20*/
21
22QQuick3DItem2D::QQuick3DItem2D(QQuickItem *item, QQuick3DNode *parent)
23 : QQuick3DNode(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::Item2D)), parent)
24{
25 m_contentItem = new QQuickItem();
26 m_contentItem->setObjectName(QLatin1String("parent of ") + item->objectName()); // for debugging
27 // No size is set for m_contentItem. This is intentional, otherwise item2d anchoring breaks.
28 QQuickItemPrivate::get(item: m_contentItem)->ensureSubsceneDeliveryAgent();
29 QQmlEngine::setObjectOwnership(m_contentItem, QQmlEngine::CppOwnership);
30
31 connect(sender: m_contentItem, signal: &QQuickItem::childrenChanged, context: this, slot: &QQuick3DObject::update);
32 addChildItem(item);
33}
34
35QQuick3DItem2D::~QQuick3DItem2D()
36{
37 delete m_contentItem;
38
39 // This is sketchy. Similarly to the problems QQuick3DTexture has with its
40 // QSGTexture, the same problems arise here with the QSGRenderer. The
41 // associated scenegraph resource must be destroyed on the render thread,
42 // if there is one. If the scenegraph gets invalidated, that's easy due to
43 // signals/slots, but there's no such signal if an object with Item2Ds in
44 // it gets dynamically destroyed.
45 // Here on the gui thread in this dtor there's no way to properly manage
46 // the QSG resource's releasing anymore. Rather, as QSGRenderer is a
47 // QObject, do a deleteLater(), which typically works, but is not a 100%
48 // guarantee that the object will get destroyed on the render thread
49 // eventually, since in theory it could happen that the render thread is
50 // not even running at this point anymore (if the window is closing / the
51 // app is shutting down) - although in practice that won't be an issue
52 // since that case is taken care of the sceneGraphInvalidated signal.
53 // So while unlikely, a leak may still occur under certain circumstances.
54 if (m_renderer)
55 m_renderer->deleteLater();
56}
57
58void QQuick3DItem2D::addChildItem(QQuickItem *item)
59{
60 item->setParent(m_contentItem);
61 item->setParentItem(m_contentItem);
62 QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: QQuickItemPrivate::ChangeType::Destroyed);
63 connect(sender: item, signal: &QQuickItem::enabledChanged, context: this, slot: &QQuick3DItem2D::updatePicking);
64 connect(sender: item, signal: &QQuickItem::visibleChanged, context: this, slot: &QQuick3DItem2D::updatePicking);
65 m_sourceItems.append(t: item);
66 update();
67}
68void QQuick3DItem2D::removeChildItem(QQuickItem *item)
69{
70 m_sourceItems.removeOne(t: item);
71 if (item)
72 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::ChangeType::Destroyed);
73 if (m_sourceItems.isEmpty())
74 emit allChildrenRemoved();
75 else
76 update();
77}
78
79QQuickItem *QQuick3DItem2D::contentItem() const
80{
81 return m_contentItem;
82}
83
84void QQuick3DItem2D::itemDestroyed(QQuickItem *item)
85{
86 removeChildItem(item);
87}
88
89void QQuick3DItem2D::invalidated()
90{
91 // clean up the renderer
92 if (m_renderer) {
93 delete m_renderer;
94 m_renderer = nullptr;
95 }
96}
97
98void QQuick3DItem2D::updatePicking()
99{
100 m_pickingDirty = true;
101 update();
102}
103
104QSSGRenderGraphObject *QQuick3DItem2D::updateSpatialNode(QSSGRenderGraphObject *node)
105{
106 auto *sourceItemPrivate = QQuickItemPrivate::get(item: m_contentItem);
107 QQuickWindow *window = m_contentItem->window();
108
109 if (!window) {
110 const auto &manager = QQuick3DObjectPrivate::get(item: this)->sceneManager;
111 window = manager->window();
112 }
113
114 if (!node) {
115 markAllDirty();
116 node = new QSSGRenderItem2D();
117 }
118
119 QQuick3DNode::updateSpatialNode(node);
120
121 auto itemNode = static_cast<QSSGRenderItem2D *>(node);
122 QSGRenderContext *rc = static_cast<QQuickWindowPrivate *>(QObjectPrivate::get(o: window))->context;
123
124 m_rootNode = sourceItemPrivate->rootNode();
125 if (!m_rootNode) {
126 return nullptr;
127 }
128
129 if (!m_renderer) {
130 m_renderer = rc->createRenderer(renderMode: QSGRendererInterface::RenderMode3D);
131 connect(sender: window, signal: &QQuickWindow::sceneGraphInvalidated, context: this, slot: &QQuick3DItem2D::invalidated, type: Qt::DirectConnection);
132 connect(sender: m_renderer, signal: &QSGAbstractRenderer::sceneGraphChanged, context: this, slot: &QQuick3DObject::update);
133
134 // item2D rendernode has its own render pass descriptor and it should
135 // be removed before deleting rhi context.
136 // Otherwise, rhi will complain about the unreleased resource.
137 connect(
138 sender: m_renderer,
139 signal: &QObject::destroyed,
140 context: this,
141 slot: [this]() {
142 auto itemNode = static_cast<QSSGRenderItem2D *>(QQuick3DObjectPrivate::get(item: this)->spatialNode);
143 if (itemNode) {
144 if (itemNode->m_rp) {
145 itemNode->m_rp->deleteLater();
146 itemNode->m_rp = nullptr;
147 }
148 }
149 },
150 type: Qt::DirectConnection);
151 }
152
153 {
154 // Block the sceneGraphChanged() signal. Calling nodeChanged() will emit the sceneGraphChanged()
155 // signal, which is connected to the update() slot to mark the object dirty, which could cause
156 // and constant update even if the 2D content doesn't change.
157 QSignalBlocker blocker(m_renderer);
158 m_renderer->setRootNode(m_rootNode);
159 m_rootNode->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
160 m_renderer->nodeChanged(node: m_rootNode, state: QSGNode::DirtyForceUpdate); // Force render list update.
161 }
162
163 if (m_pickingDirty) {
164 m_pickingDirty = false;
165 bool isPickable = false;
166 for (auto item : m_sourceItems) {
167 // Enable picking for Item2D if any of its child is visible and enabled.
168 if (item->isVisible() && item->isEnabled()) {
169 isPickable = true;
170 break;
171 }
172 }
173 itemNode->setState(state: QSSGRenderNode::LocalState::Pickable, on: isPickable);
174 }
175
176 itemNode->m_renderer = m_renderer;
177
178 return node;
179}
180
181void QQuick3DItem2D::markAllDirty()
182{
183 QQuick3DNode::markAllDirty();
184}
185
186void QQuick3DItem2D::preSync()
187{
188 const auto &manager = QQuick3DObjectPrivate::get(item: this)->sceneManager;
189 auto *sourcePrivate = QQuickItemPrivate::get(item: m_contentItem);
190 auto *window = manager->window();
191 if (m_window != window) {
192 update(); // Just schedule an upate immediately.
193 if (m_window) {
194 disconnect(sender: m_window, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(derefWindow(QObject*)));
195 sourcePrivate->derefWindow();
196 }
197 m_window = window;
198 sourcePrivate->refWindow(window);
199 connect(sender: window, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(derefWindow(QObject*)));
200 sourcePrivate->refFromEffectItem(hide: true);
201 }
202}
203
204static void detachWindow(QQuickItem *item, QObject *win)
205{
206 auto *itemPriv = QQuickItemPrivate::get(item);
207
208 if (win == itemPriv->window) {
209 itemPriv->window = nullptr;
210 itemPriv->windowRefCount = 0;
211
212 itemPriv->prevDirtyItem = nullptr;
213 itemPriv->nextDirtyItem = nullptr;
214 }
215
216 for (auto *child: itemPriv->childItems)
217 detachWindow(item: child, win);
218}
219
220void QQuick3DItem2D::derefWindow(QObject *win)
221{
222 detachWindow(item: m_contentItem, win);
223}
224
225QT_END_NAMESPACE
226

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