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
9QT_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
67QQuick3DResourceLoader::QQuick3DResourceLoader(QQuick3DObject *parent)
68 : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ResourceLoader)), parent)
69{
70}
71
72const QList<QUrl> &QQuick3DResourceLoader::meshSources() const
73{
74 return m_meshSources;
75}
76
77void 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
87QQmlListProperty<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
97QQmlListProperty<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
107void 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
120void 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
133QSSGRenderGraphObject *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
176void QQuick3DResourceLoader::markAllDirty()
177{
178 m_dirtyAttributes = 0xffffffff;
179 QQuick3DObject::markAllDirty();
180}
181
182void QQuick3DResourceLoader::itemChange(ItemChange change, const ItemChangeData &value)
183{
184 if (change == QQuick3DObject::ItemSceneChange)
185 updateSceneManager(sceneManager: value.sceneManager);
186}
187
188void 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
215QQuick3DGeometry *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
227qsizetype QQuick3DResourceLoader::qmlGeometriesCount(QQmlListProperty<QQuick3DGeometry> *list)
228{
229 QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object);
230 return self->m_geometries.size();
231}
232
233void 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
246void 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
272QQuick3DTexture *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
283qsizetype QQuick3DResourceLoader::qmlTexturesCount(QQmlListProperty<QQuick3DTexture> *list)
284{
285 QQuick3DResourceLoader *self = static_cast<QQuick3DResourceLoader *>(list->object);
286 return self->m_textures.size();
287}
288
289void 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
301void QQuick3DResourceLoader::markDirty(ResourceLoaderDirtyType type)
302{
303 if (!(m_dirtyAttributes & quint32(type))) {
304 m_dirtyAttributes |= quint32(type);
305 update();
306 }
307}
308
309void 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
326QT_END_NAMESPACE
327

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