Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrtutilities.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
10
11#include <QtCore/qurl.h>
12#include <QtCore/qbuffer.h>
13
14#include <QtGui/qimage.h>
15#include <QtGui/qimagereader.h>
16#include <QtGui/qimagewriter.h>
17#include <QtGui/qquaternion.h>
18
19#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
20
22
23// Serves an instance table calculated at import time
25{
27public:
29
30 void setTable(const QByteArray &data, int count)
31 {
32 m_data = data;
33 m_count = count;
34 markDirty();
35 }
36
37protected:
44
45private:
47 int m_count = 0;
48};
49
50// Actually set the property on node->obj, using QMetaProperty::write()
51void QSSGRuntimeUtils::applyPropertyValue(const QSSGSceneDesc::Node *node, QObject *o, QSSGSceneDesc::Property *property)
52{
53 auto *obj = qobject_cast<QQuick3DObject *>(o ? o : node->obj);
54 if (!obj)
55 return;
56
57 auto *metaObj = obj->metaObject();
58 int propertyIndex = metaObj->indexOfProperty(property->name);
59 if (propertyIndex < 0) {
60 qWarning() << "QSSGSceneDesc: could not find property" << property->name << "in" << obj;
61 return;
62 }
63 auto metaProp = metaObj->property(propertyIndex);
64 QVariant value;
65
66 auto metaId = property->value.metaType().id();
67 auto *scene = node->scene;
68
69 if (metaId == qMetaTypeId<QSSGSceneDesc::Node *>()) {
70 const auto *valueNode = qvariant_cast<QSSGSceneDesc::Node *>(property->value);
71 QObject *obj = valueNode ? valueNode->obj : nullptr;
72 value = QVariant::fromValue(obj);
73 } else if (metaId == qMetaTypeId<QSSGSceneDesc::Mesh *>()) { // Special handling for mesh nodes.
74 // Mesh nodes does not have an equivalent in the QtQuick3D scene, but is registered
75 // as a source property in the intermediate scene we therefore need to convert it to
76 // be a usable source url now.
77 const auto meshNode = qvariant_cast<const QSSGSceneDesc::Mesh *>(property->value);
78 const auto url = meshNode ? QUrl(QSSGBufferManager::runtimeMeshSourceName(node->scene->id, meshNode->idx)) : QUrl{};
79 value = QVariant::fromValue(url);
80 } else if (metaId == qMetaTypeId<QUrl>()) {
81 const auto url = qvariant_cast<QUrl>(property->value);
82 // TODO: Use QUrl::resolved() instead??
83 QString workingDir = scene->sourceDir;
84 const QUrl qurl = url.isValid() ? QUrl::fromUserInput(url.path(), workingDir) : QUrl{};
85 value = QVariant::fromValue(qurl);
86 } else if (metaId == qMetaTypeId<QSSGSceneDesc::Flag>() && property->call) {
87 // If we have a QSSGSceneDesc::Flag variant, then it came from setProperty(), and the setter function is defined.
88 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(property->value);
89 property->call->set(*obj, property->name, flag.value);
90 qDebug() << "Flag special case, probably shouldn't happen" << node->name << property->name << property->value << flag.value;
91 return;
92 } else {
93 value = property->value;
94 }
95
96 if (value.metaType().id() == qMetaTypeId<QString>()) {
97 auto str = value.toString();
98 auto propType = metaProp.metaType();
99 if (propType.id() == qMetaTypeId<QVector3D>()) {
100 QStringList l = str.split(u',');
101 if (l.length() != 3) {
102 qWarning() << "Wrong format for QVector3D:" << str;
103 } else {
104 QVector3D vec3(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat());
105 value = QVariant::fromValue(vec3);
106 }
107 } else if (propType.id() == qMetaTypeId<QVector2D>()) {
108 QStringList l = str.split(u',');
109 if (l.length() != 2) {
110 qWarning() << "Wrong format for QVector2D:" << str;
111 } else {
112 QVector2D vec(l.at(0).toFloat(), l.at(1).toFloat());
113 value = QVariant::fromValue(vec);
114 }
115 } else if (propType.id() == qMetaTypeId<QVector4D>()) {
116 QStringList l = str.split(u',');
117 if (l.length() != 2) {
118 qWarning() << "Wrong format for QVector4D:" << str;
119 } else {
120 QVector4D vec(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat(), l.at(3).toFloat());
121 value = QVariant::fromValue(vec);
122 }
123 } else if (propType.id() == qMetaTypeId<QQuaternion>()) {
124 QStringList l = str.split(u',');
125 if (l.length() != 4) {
126 qWarning() << "Wrong format for QQuaternion:" << str;
127 } else {
128 QQuaternion quat(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat(), l.at(3).toFloat());
129 value = QVariant::fromValue(quat);
130 }
131 } else {
132 // All other strings are supposed to be in QML-compatible format, so they can be written out directly
133 }
134 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::NodeList*>()) {
135 auto qmlListVar = metaProp.read(obj);
136 // We have to write explicit code for each list property type, since metatype can't
137 // tell us if we have a QQmlListProperty (if we had known, we could have made a naughty
138 // hack and just static_cast to QQmlListProperty<QObject>
139 if (qmlListVar.metaType().id() == qMetaTypeId<QQmlListProperty<QQuick3DMaterial>>()) {
140 auto qmlList = qvariant_cast<QQmlListProperty<QQuick3DMaterial>>(qmlListVar);
141 auto nodeList = qvariant_cast<QSSGSceneDesc::NodeList*>(value);
142 auto head = reinterpret_cast<QSSGSceneDesc::Node **>(nodeList->head);
143
144 for (int i = 0, end = nodeList->count; i != end; ++i)
145 qmlList.append(&qmlList, qobject_cast<QQuick3DMaterial *>((*(head + i))->obj));
146
147 } else {
148 qWarning() << "Can't handle list property type" << qmlListVar.metaType();
149 }
150 return; //In any case, we can't send NodeList to QMetaProperty::write()
151 }
152
153 // qobject_cast doesn't work on nullptr, so we must convert the pointer manually
154 if ((metaProp.metaType().flags() & QMetaType::PointerToQObject) && value.isNull())
155 value.convert(metaProp.metaType()); //This will return false, but convert to the type anyway
156
157 // Q_ENUMS with '|' is explicitly handled, otherwise uses QVariant::convert. This means
158 // we get implicit qobject_cast and string to:
159 // QMetaType::Bool, QMetaType::QByteArray, QMetaType::QChar, QMetaType::QColor, QMetaType::QDate, QMetaType::QDateTime,
160 // QMetaType::Double, QMetaType::QFont, QMetaType::Int, QMetaType::QKeySequence, QMetaType::LongLong,
161 // QMetaType::QStringList, QMetaType::QTime, QMetaType::UInt, QMetaType::ULongLong, QMetaType::QUuid
162
163 bool success = metaProp.write(obj, value);
164
165 if (!success) {
166 qWarning() << "Failure when setting property" << property->name << "to" << property->value << "maps to" << value
167 << "property metatype:" << metaProp.typeName();
168 }
169}
170
171
172static void setProperties(QQuick3DObject &obj, const QSSGSceneDesc::Node &node, const QString &workingDir = {})
173{
174 using namespace QSSGSceneDesc;
175 const auto &properties = node.properties;
176 auto it = properties.begin();
177 const auto end = properties.end();
178 for (; it != end; ++it) {
179 const auto &v = *it;
180 if (!v->call) {
182 continue;
183 }
184 const auto &var = v->value;
185 if (var.metaType().id() == qMetaTypeId<Node *>()) {
186 const auto *node = qvariant_cast<Node *>(var);
187 v->call->set(obj, v->name, node ? node->obj : nullptr);
188 } else if (var.metaType() == QMetaType::fromType<Mesh *>()) { // Special handling for mesh nodes.
189 // Mesh nodes does not have an equivalent in the QtQuick3D scene, but is registered
190 // as a source property in the intermediate scene we therefore need to convert it to
191 // be a usable source url now.
192 const auto meshNode = qvariant_cast<const Mesh *>(var);
193 const auto url = meshNode ? QUrl(QSSGBufferManager::runtimeMeshSourceName(node.scene->id, meshNode->idx)) : QUrl{};
194 v->call->set(obj, v->name, &url);
195 } else if (var.metaType() == QMetaType::fromType<QUrl>()) {
196 const auto url = qvariant_cast<QUrl>(var);
197 // TODO: Use QUrl::resolved() instead
198 const QUrl qurl = url.isValid() ? QUrl::fromUserInput(url.toString(), workingDir) : QUrl{};
199 v->call->set(obj, v->name, &qurl);
200 } else if (var.metaType().id() == qMetaTypeId<QSSGSceneDesc::Flag>()) {
201 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(var);
202 v->call->set(obj, v->name, flag.value);
203 } else {
204 v->call->set(obj, v->name, var);
205 }
206 }
207}
208
209template<typename GraphObjectType, typename NodeType>
210GraphObjectType *createRuntimeObject(NodeType &node, QQuick3DObject &parent)
211{
212 GraphObjectType *obj = qobject_cast<GraphObjectType *>(node.obj);
213 if (!obj) {
214 node.obj = qobject_cast<QQuick3DObject *>(obj = new GraphObjectType);
215 QString name = QString::fromUtf8(node.name);
216 if (name.isEmpty())
217 name = QString::fromUtf8(QSSGQmlUtilities::getQmlElementName(node));
218 obj->setObjectName(name);
219 obj->setParent(&parent);
220 obj->setParentItem(&parent);
221 }
222 Q_ASSERT(obj == node.obj);
223
224 return obj;
225}
226
227template<>
228QQuick3DTextureData *createRuntimeObject<QQuick3DTextureData>(QSSGSceneDesc::TextureData &node, QQuick3DObject &parent)
229{
230 QQuick3DTextureData *obj = qobject_cast<QQuick3DTextureData *>(node.obj);
231 if (!obj) {
232 node.obj = qobject_cast<QQuick3DObject *>(obj = new QQuick3DTextureData);
233 obj->setObjectName(node.name);
234 obj->setParent(&parent);
235 obj->setParentItem(&parent);
236
237 const auto &texData = node.data;
238 const bool isCompressed = ((node.flgs & quint8(QSSGSceneDesc::TextureData::Flags::Compressed)) != 0);
239
240 if (!texData.isEmpty()) {
241 QImage image;
242 if (isCompressed) {
243 QByteArray data = texData;
244 QBuffer readBuffer(&data);
245 QImageReader imageReader(&readBuffer, node.fmt);
246 image = imageReader.read();
247 if (image.isNull())
248 qWarning() << imageReader.errorString();
249 } else {
250 const auto &size = node.sz;
251 image = QImage(reinterpret_cast<const uchar *>(texData.data()), size.width(), size.height(), QImage::Format::Format_RGBA8888);
252 }
253
254 if (!image.isNull()) {
255 const QPixelFormat pixFormat = image.pixelFormat();
256 QImage::Format targetFormat = QImage::Format_RGBA8888_Premultiplied;
257 QQuick3DTextureData::Format textureFormat = QQuick3DTextureData::Format::RGBA8;
258 if (image.colorCount()) { // a palleted image
259 targetFormat = QImage::Format_RGBA8888;
260 } else if (pixFormat.channelCount() == 1) {
261 targetFormat = QImage::Format_Grayscale8;
262 textureFormat = QQuick3DTextureData::Format::R8;
263 } else if (pixFormat.alphaUsage() == QPixelFormat::IgnoresAlpha) {
264 targetFormat = QImage::Format_RGBX8888;
265 } else if (pixFormat.premultiplied() == QPixelFormat::NotPremultiplied) {
266 targetFormat = QImage::Format_RGBA8888;
267 }
268
269 image.convertTo(targetFormat); // convert to a format mappable to QRhiTexture::Format
270 image.flip(); // Flip vertically to the conventional Y-up orientation
271
272 const auto bytes = image.sizeInBytes();
273 obj->setSize(image.size());
274 obj->setFormat(textureFormat);
275 obj->setTextureData(QByteArray(reinterpret_cast<const char *>(image.constBits()), bytes));
276 }
277 }
278 }
279
280 return obj;
281}
282
283
284// Resources may refer to other resources and/or nodes, so we first generate all the resources without setting properties,
285// then all the nodes with properties, and finally we set properties for the resources
286// TODO: split this into different functions
287
288void QSSGRuntimeUtils::createGraphObject(QSSGSceneDesc::Node &node,
289 QQuick3DObject &parent,
290 QString path,
291 QSSGRuntimeObjectNameMap *objectMap,
292 QSSGRuntimeObjectTypeMap *typeMap,
293 bool traverseChildrenAndSetProperties)
294{
295 using namespace QSSGSceneDesc;
296
297 QQuick3DObject *obj = nullptr;
298 switch (node.nodeType) {
299 case Node::Type::Skeleton:
300 // Skeleton is a resource that also needs to be in the node tree. We don't do that anymore.
301 qWarning("Skeleton runtime import not supported");
302
303 // NOTE: The skeleton is special as it's a resource and a node, the
304 // hierarchical parent is therefore important here.
305 if (!node.obj) {// 1st Phase : 'create Resources'
306 obj = createRuntimeObject<QQuick3DSkeleton>(static_cast<Skeleton &>(node), parent);
307 } else { // 2nd Phase : setParent for the Node hierarchy.
308 obj = qobject_cast<QQuick3DSkeleton *>(node.obj);
309 obj->setParent(&parent);
310 obj->setParentItem(&parent);
311 }
312 break;
313 case Node::Type::Joint:
314 obj = createRuntimeObject<QQuick3DJoint>(static_cast<Joint &>(node), parent);
315 break;
316 case Node::Type::Skin:
317 obj = createRuntimeObject<QQuick3DSkin>(static_cast<Skin &>(node), parent);
318 break;
319 case Node::Type::MorphTarget:
320 obj = createRuntimeObject<QQuick3DMorphTarget>(static_cast<MorphTarget &>(node), parent);
321 break;
322 case Node::Type::Instancing:
323 {
324 auto &instancing = static_cast<Instancing &>(node);
325 auto *table = createRuntimeObject<QSSGRuntimeInstancing>(instancing, parent);
326 if (table)
327 table->setTable(instancing.instanceData, int(instancing.instanceCount));
328 obj = table;
329 break;
330 }
331 case Node::Type::Light:
332 {
333 auto &light = static_cast<Light &>(node);
334 if (light.runtimeType == Node::RuntimeType::DirectionalLight)
335 obj = createRuntimeObject<QQuick3DDirectionalLight>(light, parent);
336 else if (light.runtimeType == Node::RuntimeType::PointLight)
337 obj = createRuntimeObject<QQuick3DPointLight>(light, parent);
338 else if (light.runtimeType == Node::RuntimeType::SpotLight)
339 obj = createRuntimeObject<QQuick3DSpotLight>(light, parent);
340 else
341 Q_UNREACHABLE();
342 }
343 break;
344 case Node::Type::Transform:
345 obj = createRuntimeObject<QQuick3DNode>(node, parent);
346 break;
347 case Node::Type::Camera:
348 {
349 auto &camera = static_cast<Camera &>(node);
350 if (camera.runtimeType == Node::RuntimeType::OrthographicCamera)
351 obj = createRuntimeObject<QQuick3DOrthographicCamera>(camera, parent);
352 else if (camera.runtimeType == Node::RuntimeType::PerspectiveCamera)
353 obj = createRuntimeObject<QQuick3DPerspectiveCamera>(camera, parent);
354 else if (camera.runtimeType == Node::RuntimeType::CustomCamera)
355 obj = createRuntimeObject<QQuick3DCustomCamera>(camera, parent);
356 else
357 Q_UNREACHABLE();
358 }
359 break;
360 case Node::Type::Model:
361 obj = createRuntimeObject<QQuick3DModel>(static_cast<Model &>(node), parent);
362 break;
363 case Node::Type::Texture:
364 if (node.runtimeType == Node::RuntimeType::TextureData)
365 obj = createRuntimeObject<QQuick3DTextureData>(static_cast<TextureData &>(node), parent);
366 else if (node.runtimeType == Node::RuntimeType::Image2D)
367 obj = createRuntimeObject<QQuick3DTexture>(static_cast<Texture &>(node), parent);
368 else if (node.runtimeType == Node::RuntimeType::ImageCube)
369 obj = createRuntimeObject<QQuick3DCubeMapTexture>(static_cast<Texture &>(node), parent);
370 else
371 Q_UNREACHABLE();
372 break;
373 case Node::Type::Material:
374 {
375 if (node.runtimeType == Node::RuntimeType::PrincipledMaterial)
376 obj = createRuntimeObject<QQuick3DPrincipledMaterial>(static_cast<Material &>(node), parent);
377 else if (node.runtimeType == Node::RuntimeType::CustomMaterial)
378 obj = createRuntimeObject<QQuick3DCustomMaterial>(static_cast<Material &>(node), parent);
379 else if (node.runtimeType == Node::RuntimeType::SpecularGlossyMaterial)
380 obj = createRuntimeObject<QQuick3DSpecularGlossyMaterial>(static_cast<Material &>(node), parent);
381 else
382 Q_UNREACHABLE();
383 }
384 break;
385 case Node::Type::Mesh:
386 // There's no runtime object for this type, but we need to register the mesh with the
387 // buffer manager (this will happen once the mesh property is processed on the model).
388 break;
389 }
390
391 if (obj) {
392 const QString keyName = obj->objectName().isEmpty()
393 ? QString::fromUtf8(QSSGQmlUtilities::getQmlElementName(node))
394 : obj->objectName();
395 path += QStringLiteral("/") + keyName;
396 if (objectMap)
397 objectMap->insert({ keyName, path }, obj);
398 if (typeMap)
399 typeMap->insert(QSSGRenderGraphObjectUtils::getBaseType(node.runtimeType), obj);
400 }
401
402 if (obj && traverseChildrenAndSetProperties) {
403 setProperties(*obj, node);
404 for (auto &chld : node.children)
405 createGraphObject(*chld, *obj, path, objectMap, typeMap);
406 }
407}
408
409/*
410
411Node {
412 name: "root"
413 children: [
414 Node {
415 name: "child1"
416 children: [
417 Node {
418 name: "grandchild1"
419 }
420 ]
421 }
422 ]
423}
424
425We'll create a map of all nodes using the path as key, so we can easily get any node by providing the path.
426Since the node names aren't unique, we'll insert each node both by name and by path. If there are multiple
427nodes with the same name, we'll just overwrite the previous one.
428
429
430*/
431
432QQuick3DNode *QSSGRuntimeUtils::createScene(QQuick3DNode &parent, const QSSGSceneDesc::Scene &scene, QSSGRuntimeObjectNameMap *objectMap, QSSGRuntimeObjectTypeMap *typeMap)
433{
434 if (!scene.root) {
435 qWarning("Incomplete scene description (missing plugin?)");
436 return nullptr;
437 }
438
439 Q_ASSERT(QQuick3DObjectPrivate::get(&parent)->sceneManager);
440
441 QSSGBufferManager::registerMeshData(scene.id, scene.meshStorage);
442
443 auto root = scene.root;
444
445 for (const auto &resource : scene.resources)
446 createGraphObject(*resource, parent, {}, objectMap, typeMap, false);
447
448 createGraphObject(*root, parent, {}, objectMap, typeMap);
449
450 // Some resources such as Skin have properties related with the node
451 // hierarchy. Therefore, resources are handled after nodes.
452 for (const auto &resource : scene.resources) {
453 if (resource->obj != nullptr) // A mesh node has no runtime object.
454 setProperties(static_cast<QQuick3DObject &>(*resource->obj), *resource, scene.sourceDir);
455 }
456
457 // Usually it makes sense to only enable 1 timeline at a time
458 // so for now we just enable the first one.
459 bool isFirstAnimation = true;
460 for (const auto &anim: scene.animations) {
461 QSSGQmlUtilities::createTimelineAnimation(*anim, root->obj, isFirstAnimation);
462 if (isFirstAnimation)
463 isFirstAnimation = false;
464 }
465
466 return qobject_cast<QQuick3DNode *>(scene.root->obj);
467}
468
469QT_END_NAMESPACE
470
471#include "qssgrtutilities.moc"
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:34
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:178
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:341
Q_QUICK3DASSETUTILS_EXPORT void applyPropertyValue(const QSSGSceneDesc::Node *node, QObject *obj, QSSGSceneDesc::Property *property)
Combined button and popup list for selecting options.
GraphObjectType * createRuntimeObject(NodeType &node, QQuick3DObject &parent)
static void setProperties(QQuick3DObject &obj, const QSSGSceneDesc::Node &node, const QString &workingDir={})