1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | |
5 | #include "qquick3dresourceloader_p.h" |
6 | #include "qquick3dmodel_p.h" |
7 | #include <QtQuick3DRuntimeRender/private/qssgrenderresourceloader_p.h> |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | /*! |
12 | \qmltype ResourceLoader |
13 | \inqmlmodule QtQuick3D |
14 | \inherits Object3D |
15 | |
16 | \brief Allows pre-loading of 3D resources. |
17 | |
18 | ResourceLoader is used to pre-load resources for Qt Quick 3D. Normally |
19 | resources are only loaded when they are needed to render a frame, and are |
20 | unloaded when they are not used to render the scene. This aggressive |
21 | approach to resource lifetimes means that only the bare minimum of GPU |
22 | resources are used to render a frame, but for some dynamic scenes this |
23 | can lead to resources being loaded and released frequently. The |
24 | ResourceLoader component enables a finer grain control on the lifetimes |
25 | of resources in the scene. Resources listed in the ResourceLoader |
26 | component are loaded into GPU memory and will remain there until they |
27 | are removed from the ResourceLoader lists or the ResourceLoader is |
28 | destroyed. |
29 | |
30 | ResourceLoader can also be used to make sure that large resources are |
31 | available before rendering a frame. Since resources are loaded only |
32 | when needed for a frame, this can lead to frames being dropped waiting |
33 | for a large resource to be loaded. By pre-loading large resources before |
34 | showing a scene, there is no risk of dropping any frames due to resources |
35 | being loaded during an animation. |
36 | |
37 | For usage examples, see \l {Qt Quick 3D - Principled Material Example} |
38 | |
39 | */ |
40 | |
41 | /*! |
42 | \qmlproperty List<url> ResourceLoader::meshSources |
43 | |
44 | This property defines a list of locations of mesh files containing geometry. |
45 | When a mesh file is added to this list, it will be loaded to the GPU and |
46 | cached. If these same mesh files are source is used by a /c Model they will |
47 | not need to be loaded again. |
48 | |
49 | */ |
50 | |
51 | /*! |
52 | \qmlproperty List<QtQuick3D::Texture> ResourceLoader::textures |
53 | |
54 | This property defines a list of Texture resources that will be loaded to the |
55 | GPU and cached. |
56 | |
57 | */ |
58 | |
59 | /*! |
60 | \qmlproperty List<QtQuick3D::Geometry> ResourceLoader::geometries |
61 | |
62 | This property defines a list of Geometry resources that will be loaded to the |
63 | GPU and cached. |
64 | |
65 | */ |
66 | |
67 | QQuick3DResourceLoader::QQuick3DResourceLoader(QQuick3DObject *parent) |
68 | : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ResourceLoader)), parent) |
69 | { |
70 | } |
71 | |
72 | const QList<QUrl> &QQuick3DResourceLoader::meshSources() const |
73 | { |
74 | return m_meshSources; |
75 | } |
76 | |
77 | void QQuick3DResourceLoader::setMeshSources(const QList<QUrl> &newMeshSources) |
78 | { |
79 | if (m_meshSources == newMeshSources) |
80 | return; |
81 | m_meshSources = newMeshSources; |
82 | emit meshSourcesChanged(); |
83 | markDirty(type: QQuick3DResourceLoader::MeshesDirty); |
84 | } |
85 | |
86 | |
87 | QQmlListProperty<QQuick3DGeometry> QQuick3DResourceLoader::geometries() |
88 | { |
89 | return QQmlListProperty<QQuick3DGeometry>(this, |
90 | nullptr, |
91 | QQuick3DResourceLoader::qmlAppendGeometry, |
92 | QQuick3DResourceLoader::qmlGeometriesCount, |
93 | QQuick3DResourceLoader::qmlGeometryAt, |
94 | QQuick3DResourceLoader::qmlClearGeometries); |
95 | } |
96 | |
97 | QQmlListProperty<QQuick3DTexture> QQuick3DResourceLoader::textures() |
98 | { |
99 | return QQmlListProperty<QQuick3DTexture>(this, |
100 | nullptr, |
101 | QQuick3DResourceLoader::qmlAppendTexture, |
102 | QQuick3DResourceLoader::qmlTexturesCount, |
103 | QQuick3DResourceLoader::qmlTextureAt, |
104 | QQuick3DResourceLoader::qmlClearTextures); |
105 | } |
106 | |
107 | void QQuick3DResourceLoader::onGeometryDestroyed(QObject *object) |
108 | { |
109 | bool found = false; |
110 | for (int i = 0; i < m_geometries.size(); ++i) { |
111 | if (m_geometries[i] == object) { |
112 | m_geometries.removeAt(i: i--); |
113 | found = true; |
114 | } |
115 | } |
116 | if (found) |
117 | markDirty(type: QQuick3DResourceLoader::GeometriesDirty); |
118 | } |
119 | |
120 | void QQuick3DResourceLoader::onTextureDestroyed(QObject *object) |
121 | { |
122 | bool found = false; |
123 | for (int i = 0; i < m_textures.size(); ++i) { |
124 | if (m_textures[i] == object) { |
125 | m_textures.removeAt(i: i--); |
126 | found = true; |
127 | } |
128 | } |
129 | if (found) |
130 | markDirty(type: QQuick3DResourceLoader::TexturesDirty); |
131 | } |
132 | |
133 | QSSGRenderGraphObject *QQuick3DResourceLoader::updateSpatialNode(QSSGRenderGraphObject *node) |
134 | { |
135 | if (!node) { |
136 | markAllDirty(); |
137 | node = new QSSGRenderResourceLoader(); |
138 | } |
139 | QQuick3DObject::updateSpatialNode(node); |
140 | int dirtyAttribute = 0; |
141 | |
142 | auto resourceLoaderNode = static_cast<QSSGRenderResourceLoader *>(node); |
143 | if (m_dirtyAttributes & MeshesDirty) { |
144 | resourceLoaderNode->meshes.clear(); |
145 | for (const auto &mesh : std::as_const(t&: m_meshSources)) |
146 | resourceLoaderNode->meshes.push_back(t: QSSGRenderPath(QQuick3DModel::translateMeshSource(source: mesh, contextObject: this))); |
147 | } |
148 | |
149 | if (m_dirtyAttributes & TexturesDirty) { |
150 | resourceLoaderNode->textures.clear(); |
151 | for (const auto &texture : std::as_const(t&: m_textures)) { |
152 | auto graphObject = QQuick3DObjectPrivate::get(item: texture)->spatialNode; |
153 | if (graphObject) |
154 | resourceLoaderNode->textures.push_back(t: graphObject); |
155 | else |
156 | dirtyAttribute |= TexturesDirty; |
157 | } |
158 | } |
159 | |
160 | if (m_dirtyAttributes & GeometriesDirty) { |
161 | resourceLoaderNode->geometries.clear(); |
162 | for (const auto &geometry : std::as_const(t&: m_geometries)) { |
163 | auto graphObject = QQuick3DObjectPrivate::get(item: geometry)->spatialNode; |
164 | if (graphObject) |
165 | resourceLoaderNode->geometries.push_back(t: graphObject); |
166 | else |
167 | dirtyAttribute |= GeometriesDirty; |
168 | } |
169 | } |
170 | |
171 | m_dirtyAttributes = dirtyAttribute; |
172 | return resourceLoaderNode; |
173 | |
174 | } |
175 | |
176 | void QQuick3DResourceLoader::markAllDirty() |
177 | { |
178 | m_dirtyAttributes = 0xffffffff; |
179 | QQuick3DObject::markAllDirty(); |
180 | } |
181 | |
182 | void QQuick3DResourceLoader::itemChange(ItemChange change, const ItemChangeData &value) |
183 | { |
184 | if (change == QQuick3DObject::ItemSceneChange) |
185 | updateSceneManager(sceneManager: value.sceneManager); |
186 | } |
187 | |
188 | void QQuick3DResourceLoader::qmlAppendGeometry(QQmlListProperty<QQuick3DGeometry> *list, QQuick3DGeometry *geometry) |
189 | { |
190 | if (geometry == nullptr) |
191 | return; |
192 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
193 | self->m_geometries.push_back(t: geometry); |
194 | |
195 | self->markDirty(type: QQuick3DResourceLoader::GeometriesDirty); |
196 | |
197 | if (geometry->parentItem() == nullptr) { |
198 | // If the material has no parent, check if it has a hierarchical parent that's a QQuick3DObject |
199 | // and re-parent it to that, e.g., inline materials |
200 | QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(object: geometry->parent()); |
201 | if (parentItem) { |
202 | geometry->setParentItem(parentItem); |
203 | } else { // If no valid parent was found, make sure the material refs our scene manager |
204 | const auto &sceneManager = QQuick3DObjectPrivate::get(item: self)->sceneManager; |
205 | if (sceneManager) { |
206 | QQuick3DObjectPrivate::get(item: geometry)->refSceneManager(*sceneManager); |
207 | } |
208 | } |
209 | } |
210 | |
211 | // Make sure geometries are removed when destroyed |
212 | connect(sender: geometry, signal: &QQuick3DGeometry::destroyed, context: self, slot: &QQuick3DResourceLoader::onGeometryDestroyed); |
213 | } |
214 | |
215 | QQuick3DGeometry *QQuick3DResourceLoader::qmlGeometryAt(QQmlListProperty<QQuick3DGeometry> *list, qsizetype index) |
216 | { |
217 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
218 | |
219 | if (index >= self->m_geometries.size()) { |
220 | qWarning(msg: "The index exceeds the range of valid geometries." ); |
221 | return nullptr; |
222 | } |
223 | |
224 | return self->m_geometries.at(i: index); |
225 | } |
226 | |
227 | qsizetype QQuick3DResourceLoader::qmlGeometriesCount(QQmlListProperty<QQuick3DGeometry> *list) |
228 | { |
229 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
230 | return self->m_geometries.size(); |
231 | } |
232 | |
233 | void QQuick3DResourceLoader::qmlClearGeometries(QQmlListProperty<QQuick3DGeometry> *list) |
234 | { |
235 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
236 | for (const auto &geometry : std::as_const(t&: self->m_geometries)) { |
237 | if (geometry->parentItem() == nullptr) |
238 | QQuick3DObjectPrivate::get(item: geometry)->derefSceneManager(); |
239 | geometry->disconnect(receiver: self, SLOT(onMorphTargetDestroyed(QObject*))); |
240 | } |
241 | |
242 | self->m_geometries.clear(); |
243 | self->markDirty(type: QQuick3DResourceLoader::GeometriesDirty); |
244 | } |
245 | |
246 | void QQuick3DResourceLoader::qmlAppendTexture(QQmlListProperty<QQuick3DTexture> *list, QQuick3DTexture *texture) |
247 | { |
248 | if (texture == nullptr) |
249 | return; |
250 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
251 | self->m_textures.push_back(t: texture); |
252 | |
253 | self->markDirty(type: QQuick3DResourceLoader::TexturesDirty); |
254 | |
255 | if (texture->parentItem() == nullptr) { |
256 | // If the material has no parent, check if it has a hierarchical parent that's a QQuick3DObject |
257 | // and re-parent it to that, e.g., inline materials |
258 | QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(object: texture->parent()); |
259 | if (parentItem) { |
260 | texture->setParentItem(parentItem); |
261 | } else { // If no valid parent was found, make sure the material refs our scene manager |
262 | const auto &sceneManager = QQuick3DObjectPrivate::get(item: self)->sceneManager; |
263 | if (sceneManager) { |
264 | QQuick3DObjectPrivate::get(item: texture)->refSceneManager(*sceneManager); |
265 | } |
266 | } |
267 | } |
268 | // Make sure TextureData are removed when destroyed |
269 | connect(sender: texture, signal: &QQuick3DTextureData::destroyed, context: self, slot: &QQuick3DResourceLoader::onTextureDestroyed); |
270 | } |
271 | |
272 | QQuick3DTexture *QQuick3DResourceLoader::qmlTextureAt(QQmlListProperty<QQuick3DTexture> *list, qsizetype index) |
273 | { |
274 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
275 | if (index >= self->m_textures.size()) { |
276 | qWarning(msg: "The index exceeds the range of valid texture data." ); |
277 | return nullptr; |
278 | } |
279 | |
280 | return self->m_textures.at(i: index); |
281 | } |
282 | |
283 | qsizetype QQuick3DResourceLoader::qmlTexturesCount(QQmlListProperty<QQuick3DTexture> *list) |
284 | { |
285 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
286 | return self->m_textures.size(); |
287 | } |
288 | |
289 | void QQuick3DResourceLoader::qmlClearTextures(QQmlListProperty<QQuick3DTexture> *list) |
290 | { |
291 | QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object); |
292 | for (const auto &data : std::as_const(t&: self->m_textures)) { |
293 | if (data->parentItem() == nullptr) |
294 | QQuick3DObjectPrivate::get(item: data)->derefSceneManager(); |
295 | data->disconnect(receiver: self, SLOT(onMorphTargetDestroyed(QObject*))); |
296 | } |
297 | self->m_textures.clear(); |
298 | self->markDirty(type: QQuick3DResourceLoader::TexturesDirty); |
299 | } |
300 | |
301 | void QQuick3DResourceLoader::markDirty(ResourceLoaderDirtyType type) |
302 | { |
303 | if (!(m_dirtyAttributes & quint32(type))) { |
304 | m_dirtyAttributes |= quint32(type); |
305 | update(); |
306 | } |
307 | } |
308 | |
309 | void QQuick3DResourceLoader::updateSceneManager(QQuick3DSceneManager *sceneManager) |
310 | { |
311 | if (sceneManager) { |
312 | for (auto &geometry : m_geometries) |
313 | if (!geometry->parentItem() && !QQuick3DObjectPrivate::get(item: geometry)->sceneManager) |
314 | QQuick3DObjectPrivate::refSceneManager(obj: geometry, mgr&: *sceneManager); |
315 | for (auto &texture : m_textures) |
316 | if (!texture->parentItem() && !QQuick3DObjectPrivate::get(item: texture)->sceneManager) |
317 | QQuick3DObjectPrivate::refSceneManager(obj: texture, mgr&: *sceneManager); |
318 | } else { |
319 | for (auto &geometry : m_geometries) |
320 | QQuick3DObjectPrivate::derefSceneManager(obj: geometry); |
321 | for (auto &texture : m_textures) |
322 | QQuick3DObjectPrivate::derefSceneManager(obj: texture); |
323 | } |
324 | } |
325 | |
326 | QT_END_NAMESPACE |
327 | |