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
qquick3dinstancing.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
5#include <QtQuick3DRuntimeRender/private/qssgrenderinstancetable_p.h>
6#include <QtQuick3DUtils/private/qssgutils_p.h>
7#include <QXmlStreamReader>
8#include <QtQml/QQmlFile>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype Instancing
14 \inherits Object3D
15 \inqmlmodule QtQuick3D
16 \nativetype QQuick3DInstancing
17 \since 6.2
18 \brief Base type for instance tables.
19
20 \l {Instanced Rendering}{Instanced rendering} allows duplicating a model with variations.
21
22 The Instancing type defines a table that specifies how each instance is modified relative to the
23 base model. The table has an entry for each index, containing a transform matrix, a color, and
24 generic data for use by custom materials. To use instancing, set a model's
25 \l{Model::instancing}{instancing} property to reference an Instancing object.
26
27 An application can define an Instancing object in C++ by subclassing QQuick3DInstancing,
28 or it can use one of the pre-defined QML types: InstanceList FileInstancing, or RandomInstancing.
29 In addition, it is possible to use a \l {ParticleSystem3D}{particle system} to define an
30 instancing table by using the \l{ModelParticle3D::instanceTable}{ModelParticle3D.instanceTable}
31 property.
32*/
33
34/*!
35 \qmlproperty int Instancing::instanceCountOverride
36
37 Set this property to limit the number of instances without regenerating or re-uploading the instance table.
38 This allows very inexpensive animation of the number of instances rendered.
39*/
40
41/*!
42 \property QQuick3DInstancing::instanceCountOverride
43
44 Set this property to limit the number of instances without regenerating or re-uploading the instance table.
45 This allows very inexpensive animation of the number of instances rendered.
46*/
47
48/*!
49 \qmlproperty bool Instancing::hasTransparency
50
51 Set this property to true if the instancing table contains alpha values that should be used when
52 rendering the model. This property only makes a difference if the model is opaque: If the model has a
53 transparent \l{Model::materials}{material}, or an \l{Node::opacity}{opacity} less than one, the
54 alpha value from the table will be used regardless.
55
56 \note Enabling alpha blending may cause rendering issues when instances overlap. See the
57 \l{Alpha-blending and instancing}{alpha blending and instancing} documentation for details.
58*/
59
60/*!
61 \property QQuick3DInstancing::hasTransparency
62
63 Set this property to true if the instancing table contains alpha values that should be used when
64 rendering the model. This property only makes a difference if the model is opaque: If the model has a
65 transparent \l{Model::materials}{material}, or an \l{Node::opacity}{opacity} less than one, the
66 alpha value from the table will be used regardless.
67
68 \note Enabling alpha blending may cause rendering issues when instances overlap. See the
69 \l{Alpha-blending and instancing}{alpha blending and instancing} documentation for details.
70*/
71
72/*!
73 \qmlproperty bool Instancing::depthSortingEnabled
74
75 Holds the depth sorting enabled value for the instance table. When enabled, instances are sorted
76 and rendered from the furthest instance from the camera to the nearest i.e. back-to-front.
77 If disabled, which is the default, instances are rendered in the order they are specified in
78 the instance table.
79
80 \note The instances are only sorted against each other. Instances are not sorted against other
81 objects in the scene.
82 \note The sorting increases the frame preparation time especially with large instance counts.
83*/
84
85/*!
86 \property QQuick3DInstancing::depthSortingEnabled
87
88 Holds the depth sorting enabled value for the instance table. When enabled, instances are sorted
89 and rendered from the furthest instance from the camera to the nearest i.e. back-to-front.
90 If disabled, which is the default, instances are rendered in the order they are specified in
91 the instance table.
92
93 \note The instances are only sorted against each other. Instances are not sorted against other
94 objects in the scene.
95 \note The sorting increases the frame preparation time especially with large instance counts.
96*/
97
98/*!
99 \qmlproperty vector3d Instancing::shadowBoundsMinimum
100
101 Sets the minimum bounds used when calculating the shadow map bounds of the models in the
102 instance table.
103
104 Default value: \c{(1, 1, 1)}
105
106 \note This property is only enabled when the respective components of
107 Instancing::shadowBoundsMinimum are smaller than those in
108 Instancing::shadowBoundsMaximum. Otherwise the bounds are calculated automatically.
109 \sa shadowBoundsMaximum
110*/
111
112/*!
113 \qmlproperty vector3d Instancing::shadowBoundsMaximum
114
115 Sets the maximum bounds used when calculating the shadow map bounds of the models in the
116 instance table.
117
118 Default value: \c{(-1, -1, -1)}
119
120 \note This property is only enabled when the respective components of
121 Instancing::shadowBoundsMinimum are smaller than those in
122 Instancing::shadowBoundsMaximum. Otherwise the bounds are calculated automatically.
123 \sa shadowBoundsMinimum
124*/
125
126/*!
127 \class QQuick3DInstancing
128 \inmodule QtQuick3D
129 \inherits QQuick3DObject
130 \since 6.2
131 \brief Base class for defining instance tables.
132
133 The QQuick3DInstancing class can be inherited to specify a custom instance table
134 for a Model in the Qt Quick 3D scene.
135
136 This class is abstract: To use it, create a subclass and implement \l getInstanceBuffer().
137*/
138
139/*!
140 \fn QByteArray QQuick3DInstancing::getInstanceBuffer(int *instanceCount)
141
142 Implement this function to return the contents of the instance table. The number of instances should be
143 returned in \a instanceCount. The subclass is responsible for caching the result if necessary. If the
144 instance table changes, the subclass should call markDirty().
145 */
146
147QQuick3DInstancingPrivate::QQuick3DInstancingPrivate()
148 : QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ModelInstance)
149{
150}
151
152QQuick3DInstancing::QQuick3DInstancing(QQuick3DObject *parent)
153 : QQuick3DObject(*new QQuick3DInstancingPrivate, parent)
154{
155}
156
157QQuick3DInstancing::~QQuick3DInstancing()
158{
159}
160
161/*!
162 \internal
163 Returns the content of the instancing table for testing purposes.
164*/
165QByteArray QQuick3DInstancing::instanceBuffer(int *instanceCount)
166{
167 Q_D(QQuick3DInstancing);
168 QByteArray retval = getInstanceBuffer(instanceCount);
169 if (instanceCount && d->m_instanceCountOverride >= 0)
170 *instanceCount = qMin(d->m_instanceCountOverride, *instanceCount);
171 return retval;
172}
173
174int QQuick3DInstancing::instanceCountOverride() const
175{
176 Q_D(const QQuick3DInstancing);
177 return d->m_instanceCountOverride;
178}
179
180bool QQuick3DInstancing::hasTransparency() const
181{
182 Q_D(const QQuick3DInstancing);
183 return d->m_hasTransparency;
184}
185
186bool QQuick3DInstancing::depthSortingEnabled() const
187{
188 Q_D(const QQuick3DInstancing);
189 return d->m_depthSortingEnabled;
190}
191
192QVector3D QQuick3DInstancing::shadowBoundsMinimum() const
193{
194 Q_D(const QQuick3DInstancing);
195 return d->m_shadowBoundsMinimum;
196}
197
198QVector3D QQuick3DInstancing::shadowBoundsMaximum() const
199{
200 Q_D(const QQuick3DInstancing);
201 return d->m_shadowBoundsMaximum;
202}
203
204const QQuick3DInstancing::InstanceTableEntry *QQuick3DInstancing::getInstanceEntry(int index)
205{
206 const QByteArray data = getInstanceBuffer(nullptr);
207 if (index >= int(data.size() / sizeof(InstanceTableEntry)))
208 return nullptr;
209 return reinterpret_cast<const QQuick3DInstancing::InstanceTableEntry*>(data.constData()) + index;
210}
211
212QVector3D QQuick3DInstancing::InstanceTableEntry::getPosition() const
213{
214 return QVector3D{ row0[3], row1[3], row2[3] };
215}
216
217QVector3D QQuick3DInstancing::InstanceTableEntry::getScale() const
218{
219 const QVector3D col0{row0[0], row1[0], row2[0]};
220 const QVector3D col1{row0[1], row1[1], row2[1]};
221 const QVector3D col2{row0[2], row1[2], row2[2]};
222 const float scaleX = col0.length();
223 const float scaleY = col1.length();
224 const float scaleZ = col2.length();
225 return QVector3D(scaleX, scaleY, scaleZ);
226}
227
228QQuaternion QQuick3DInstancing::InstanceTableEntry::getRotation() const
229{
230 const QVector3D col0 = QVector3D(row0[0], row1[0], row2[0]).normalized();
231 const QVector3D col1 = QVector3D(row0[1], row1[1], row2[1]).normalized();
232 const QVector3D col2 = QVector3D(row0[2], row1[2], row2[2]).normalized();
233
234 const float data3x3[3*3] { // row-major order
235 col0[0], col1[0], col2[0],
236 col0[1], col1[1], col2[1],
237 col0[2], col1[2], col2[2],
238 };
239 QMatrix3x3 rot(data3x3);
240 return QQuaternion::fromRotationMatrix(rot).normalized();
241}
242
243QColor QQuick3DInstancing::InstanceTableEntry::getColor() const
244{
245 return QColor::fromRgbF(color[0], color[1], color[2], color[3]);
246}
247
248/*!
249 \qmlmethod vector3d QtQuick3D::Instancing::instancePosition(int index)
250 \since 6.3
251
252 Returns the position of the instance at \a index
253
254 \sa instanceScale, instanceRotation, instanceColor, instanceCustomData
255*/
256
257QVector3D QQuick3DInstancing::instancePosition(int index)
258{
259 auto *entry = getInstanceEntry(index);
260 if (!entry)
261 return {};
262
263 return QVector3D{ entry->row0[3], entry->row1[3], entry->row2[3] };
264}
265
266/*!
267 \qmlmethod vector3d QtQuick3D::Instancing::instanceScale(int index)
268 \since 6.3
269
270 Returns the scale of the instance at \a index
271
272 \sa instancePosition, instanceScale, instanceRotation, instanceColor, instanceCustomData
273*/
274
275QVector3D QQuick3DInstancing::instanceScale(int index)
276{
277 auto *entry = getInstanceEntry(index);
278 if (!entry)
279 return {};
280 return entry->getScale();
281}
282
283/*!
284 \qmlmethod quaternion QtQuick3D::Instancing::instanceRotation(int index)
285 \since 6.3
286
287 Returns a quaternion representing the rotation of the instance at \a index
288
289 \sa instancePosition, instanceScale, instanceRotation, instanceColor, instanceCustomData
290*/
291
292QQuaternion QQuick3DInstancing::instanceRotation(int index)
293{
294 const auto *entry = getInstanceEntry(index);
295 if (!entry)
296 return {};
297 return entry->getRotation();
298}
299
300/*!
301 \qmlmethod color QtQuick3D::Instancing::instanceColor(int index)
302 \since 6.3
303
304 Returns the color of the instance at \a index
305
306 \sa instancePosition, instanceScale, instanceRotation, instanceColor, instanceCustomData
307*/
308
309QColor QQuick3DInstancing::instanceColor(int index)
310{
311 const auto *entry = getInstanceEntry(index);
312 if (!entry)
313 return {};
314 return entry->getColor();
315}
316
317/*!
318 \qmlmethod vector3d QtQuick3D::Instancing::instanceCustomData(int index)
319 \since 6.3
320
321 Returns the custom data for the instance at \a index
322
323 \sa instancePosition, instanceScale, instanceRotation, instanceColor, instanceCustomData
324*/
325
326QVector4D QQuick3DInstancing::instanceCustomData(int index)
327{
328 const auto *entry = getInstanceEntry(index);
329 if (!entry)
330 return {};
331 return entry->instanceData;
332}
333
334void QQuick3DInstancing::setInstanceCountOverride(int instanceCountOverride)
335{
336 Q_D(QQuick3DInstancing);
337 if (d->m_instanceCountOverride == instanceCountOverride)
338 return;
339
340 d->m_instanceCountOverride = instanceCountOverride;
341 d->m_instanceCountOverrideChanged = true;
342 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
343 emit instanceCountOverrideChanged();
344}
345
346void QQuick3DInstancing::setHasTransparency(bool hasTransparency)
347{
348 Q_D(QQuick3DInstancing);
349 if (d->m_hasTransparency == hasTransparency)
350 return;
351
352 d->m_hasTransparency = hasTransparency;
353 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
354 emit hasTransparencyChanged();
355}
356
357void QQuick3DInstancing::setDepthSortingEnabled(bool enabled)
358{
359 Q_D(QQuick3DInstancing);
360 if (d->m_depthSortingEnabled == enabled)
361 return;
362
363 d->m_depthSortingEnabled = enabled;
364 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
365 emit depthSortingEnabledChanged();
366}
367
368void QQuick3DInstancing::setShadowBoundsMinimum(const QVector3D &newShadowBoundsMinimum)
369{
370 Q_D(QQuick3DInstancing);
371 if (d->m_shadowBoundsMinimum == newShadowBoundsMinimum)
372 return;
373
374 d->m_shadowBoundsMinimum = newShadowBoundsMinimum;
375 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
376 emit shadowBoundsMinimumChanged();
377}
378
379void QQuick3DInstancing::setShadowBoundsMaximum(const QVector3D &newShadowBoundsMaximum)
380{
381 Q_D(QQuick3DInstancing);
382 if (d->m_shadowBoundsMinimum == newShadowBoundsMaximum)
383 return;
384
385 d->m_shadowBoundsMaximum = newShadowBoundsMaximum;
386 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
387 emit shadowBoundsMaximumChanged();
388}
389
390/*!
391 Mark that the instance data has changed and must be uploaded again.
392
393 \sa getInstanceBuffer, instanceCountOverride
394 */
395
396void QQuick3DInstancing::markDirty()
397{
398 Q_D(QQuick3DInstancing);
399 d->dirty(QQuick3DObjectPrivate::DirtyType::Content);
400 d->m_instanceDataChanged = true;
401 emit instanceTableChanged();
402}
403
404QSSGRenderGraphObject *QQuick3DInstancing::updateSpatialNode(QSSGRenderGraphObject *node)
405{
406 Q_D(QQuick3DInstancing);
407 if (!node) {
408 markAllDirty();
409 node = new QSSGRenderInstanceTable();
410 emit instanceNodeDirty();
411 d->m_instanceDataChanged = true;
412 }
413 QQuick3DObject::updateSpatialNode(node);
414 auto effectiveInstanceCount = [d]() {
415 if (d->m_instanceCountOverride >= 0)
416 return qMin(d->m_instanceCount, d->m_instanceCountOverride);
417 return d->m_instanceCount;
418 };
419 auto *instanceTable = static_cast<QSSGRenderInstanceTable *>(node);
420 if (d->m_instanceDataChanged) {
421 QByteArray buffer = getInstanceBuffer(&d->m_instanceCount);
422 instanceTable->setData(buffer, effectiveInstanceCount(), sizeof(InstanceTableEntry));
423 d->m_instanceDataChanged = false;
424 } else if (d->m_instanceCountOverrideChanged) {
425 instanceTable->setInstanceCountOverride(effectiveInstanceCount());
426 }
427 d->m_instanceCountOverrideChanged = false;
428 instanceTable->setHasTransparency(d->m_hasTransparency);
429 instanceTable->setDepthSorting(d->m_depthSortingEnabled);
430 instanceTable->setShadowBoundsMinimum(d->m_shadowBoundsMinimum);
431 instanceTable->setShadowBoundsMaximum(d->m_shadowBoundsMaximum);
432 return node;
433}
434
435static inline QQuick3DInstancing::InstanceTableEntry calculate(const QVector3D &position, const QVector3D &scale,
436 const QVector3D &eulerRotation, const QColor &color,
437 const QVector4D &customData)
438{
439 QMatrix4x4 xform;
440
441 xform(0, 0) = scale[0];
442 xform(1, 1) = scale[1];
443 xform(2, 2) = scale[2];
444
445 QQuaternion quaternion = QQuaternion::fromEulerAngles(eulerRotation);
446 xform = QMatrix4x4(quaternion.toRotationMatrix()) * xform;
447
448 xform(0, 3) += position[0];
449 xform(1, 3) += position[1];
450 xform(2, 3) += position[2];
451
452 auto linearColor = QSSGUtils::color::sRGBToLinear(color);
453
454 return {
455 xform.row(0),
456 xform.row(1),
457 xform.row(2),
458 linearColor,
459 customData
460 };
461}
462
463/*!
464 Converts the
465 \a position
466 \a scale
467 \a eulerRotation
468 \a color
469 and
470 \a customData
471 to the instance table format expected by the standard vertex shaders. Typical pattern:
472
473 \code
474 QByteArray MyInstanceTable::getInstanceBuffer(int *instanceCount)
475 {
476 QByteArray instanceData;
477
478 ...
479
480 auto entry = calculateTableEntry({xPos, yPos, zPos}, {xScale, yScale, zScale}, {xRot, yRot, zRot}, color, {});
481 instanceData.append(reinterpret_cast<const char *>(&entry), sizeof(entry));
482 \endcode
483
484 \sa calculateTableEntryFromQuaternion
485 */
486QQuick3DInstancing::InstanceTableEntry QQuick3DInstancing::calculateTableEntry(const QVector3D &position, const QVector3D &scale,
487 const QVector3D &eulerRotation, const QColor &color, const QVector4D &customData)
488{
489 return calculate(position, scale, eulerRotation, color, customData);
490}
491
492/*!
493 Converts the
494 \a position
495 \a scale
496 \a rotation
497 \a color
498 and
499 \a customData
500 to the instance table format expected by the standard vertex shaders.
501
502 This is the same as calculateTableEntry(), except for using a quaternion to specify the rotation.
503 */
504QQuick3DInstancing::InstanceTableEntry QQuick3DInstancing::calculateTableEntryFromQuaternion(const QVector3D &position, const QVector3D &scale, const QQuaternion &rotation, const QColor &color, const QVector4D &customData)
505{
506 QMatrix4x4 xform;
507
508 xform(0, 0) = scale[0];
509 xform(1, 1) = scale[1];
510 xform(2, 2) = scale[2];
511
512 xform = QMatrix4x4(rotation.toRotationMatrix()) * xform;
513
514 xform(0, 3) += position[0];
515 xform(1, 3) += position[1];
516 xform(2, 3) += position[2];
517
518 auto linearColor = QSSGUtils::color::sRGBToLinear(color);
519
520 return {
521 xform.row(0),
522 xform.row(1),
523 xform.row(2),
524 linearColor,
525 customData
526 };
527}
528
529/*!
530 \qmltype InstanceList
531 \inherits Instancing
532 \inqmlmodule QtQuick3D
533 \brief Allows manually specifying instancing in QML.
534
535 The InstanceList type makes it possible to define an instance table manually in QML.
536
537 The following example creates an instance table with two items:
538 \qml
539 InstanceList {
540 id: manualInstancing
541 instances: [
542 InstanceListEntry {
543 position: Qt.vector3d(0, 0, -60)
544 eulerRotation: Qt.vector3d(-10, 0, 30)
545 color: "red"
546 },
547 InstanceListEntry {
548 position: Qt.vector3d(50, 10, 100)
549 eulerRotation: Qt.vector3d(0, 180, 0)
550 color: "green"
551 }
552 ]
553 }
554 \endqml
555
556 It is also possible to populate the instances property by just adding children to the
557 InstanceList. The following example is equivalent to the previous one:
558 \qml
559 InstanceList {
560 id: manualInstancing
561 InstanceListEntry {
562 position: Qt.vector3d(0, 0, -60)
563 eulerRotation: Qt.vector3d(-10, 0, 30)
564 color: "red"
565 }
566 InstanceListEntry {
567 position: Qt.vector3d(50, 10, 100)
568 eulerRotation: Qt.vector3d(0, 180, 0)
569 color: "green"
570 }
571 }
572 \endqml
573
574 Each InstanceListEntry is an object that can have property bindings and animations. This gives
575 great flexibility, but also causes memory overhead. Therefore, it is not recommended to use
576 InstanceList for procedurally generated tables containing thousands (or millions) of
577 instances. Also, any property change to an entry will cause the entire instance table to be
578 recalculated and uploaded to the GPU.
579
580 \sa RandomInstancing, QQuick3DInstancing
581*/
582
583/*!
584 \qmlproperty List<QtQuick3D::InstanceListEntry> InstanceList::instances
585 \qmldefault
586
587 This property contains the list of instance definitions. Modifying this list, or any of its elements, will cause the instance table to be updated.
588*/
589
590QQuick3DInstanceList::QQuick3DInstanceList(QQuick3DObject *parent) : QQuick3DInstancing(parent) {}
591
592QQuick3DInstanceList::~QQuick3DInstanceList() {}
593
594QByteArray QQuick3DInstanceList::getInstanceBuffer(int *instanceCount)
595{
596 if (m_dirty)
597 generateInstanceData();
598 if (instanceCount)
599 *instanceCount = m_instances.size();
600 return m_instanceData;
601}
602
603QQmlListProperty<QQuick3DInstanceListEntry> QQuick3DInstanceList::instances()
604{
605
606 return QQmlListProperty<QQuick3DInstanceListEntry>(this,
607 nullptr,
608 qmlAppendInstanceListEntry,
609 qmlInstanceListEntriesCount,
610 qmlInstanceListEntryAt,
611 qmlClearInstanceListEntries);
612}
613
614/*!
615 \qmlproperty int QtQuick3D::InstanceList::instanceCount
616 \since 6.3
617
618 This read-only property contains the number of instances in the list.
619*/
620
621int QQuick3DInstanceList::instanceCount() const
622{
623 return m_instances.size();
624}
625
626void QQuick3DInstanceList::onInstanceDestroyed(QObject *object)
627{
628 if (m_instances.removeAll(object))
629 handleInstanceChange();
630}
631
632void QQuick3DInstanceList::qmlAppendInstanceListEntry(QQmlListProperty<QQuick3DInstanceListEntry> *list, QQuick3DInstanceListEntry *instance)
633{
634 if (instance == nullptr)
635 return;
636 auto *self = static_cast<QQuick3DInstanceList *>(list->object);
637 self->m_instances.push_back(instance);
638
639 if (instance->parentItem() == nullptr)
640 instance->setParentItem(self);
641 connect(instance, &QQuick3DInstanceListEntry::changed, self, &QQuick3DInstanceList::handleInstanceChange);
642 connect(instance, &QObject::destroyed, self, &QQuick3DInstanceList::onInstanceDestroyed);
643 self->handleInstanceChange();
644}
645
646QQuick3DInstanceListEntry *QQuick3DInstanceList::qmlInstanceListEntryAt(QQmlListProperty<QQuick3DInstanceListEntry> *list, qsizetype index)
647{
648 auto *self = static_cast<QQuick3DInstanceList *>(list->object);
649 return self->m_instances.at(index);
650}
651
652qsizetype QQuick3DInstanceList::qmlInstanceListEntriesCount(QQmlListProperty<QQuick3DInstanceListEntry> *list)
653{
654 auto *self = static_cast<QQuick3DInstanceList *>(list->object);
655 return self->m_instances.size();
656}
657
658void QQuick3DInstanceList::qmlClearInstanceListEntries(QQmlListProperty<QQuick3DInstanceListEntry> *list)
659{
660 auto *self = static_cast<QQuick3DInstanceList *>(list->object);
661 for (auto *instance : self->m_instances) {
662 disconnect(instance, &QObject::destroyed, self, &QQuick3DInstanceList::onInstanceDestroyed);
663 disconnect(instance, &QQuick3DInstanceListEntry::changed, self, &QQuick3DInstanceList::handleInstanceChange);
664 }
665 self->m_instances.clear();
666 self->handleInstanceChange();
667}
668
669void QQuick3DInstanceList::handleInstanceChange()
670{
671 m_dirty = true;
672 markDirty();
673 emit instanceCountChanged();
674}
675
676void QQuick3DInstanceList::generateInstanceData()
677{
678 m_dirty = false;
679 const int count = m_instances.size();
680
681 qsizetype tableSize = count * sizeof(InstanceTableEntry);
682 m_instanceData.resize(tableSize);
683 auto *array = reinterpret_cast<InstanceTableEntry*>(m_instanceData.data());
684 for (int i = 0; i < count; ++i) {
685 const auto *inst = m_instances.at(i);
686 if (inst->m_useEulerRotation)
687 array[i] = calculateTableEntry(inst->position(), inst->scale(), inst->eulerRotation(), inst->color(), inst->customData());
688 else
689 array[i] = calculateTableEntryFromQuaternion(inst->position(), inst->scale(), inst->rotation(), inst->color(), inst->customData());
690 }
691}
692
693/*!
694 \qmltype InstanceListEntry
695 \inherits Object3D
696 \inqmlmodule QtQuick3D
697 \since 6.2
698 \brief Specifies an instance in an InstanceList.
699
700 The InstanceListEntry QML type is used to specify one instance in an instance list.
701
702 All the properties can have bindings and animation. Changing a property will cause the entire
703 instance table to be recalculated and uploaded to the GPU, so this can be expensive for instance
704 lists with many members.
705*/
706
707QQuick3DInstanceListEntry::QQuick3DInstanceListEntry(QQuick3DObject *parent)
708 : QQuick3DObject(parent)
709{
710}
711
712/*!
713 \qmlproperty vector3d QtQuick3D::InstanceListEntry::position
714
715 This property specifies the position for the instance.
716*/
717void QQuick3DInstanceListEntry::setPosition(QVector3D position)
718{
719 if (m_position == position)
720 return;
721
722 m_position = position;
723 emit positionChanged();
724 emit changed();
725}
726
727/*!
728 \qmlproperty vector3d QtQuick3D::InstanceListEntry::scale
729
730 This property specifies the scale for the instance as a vector containing the scale factor along the x, y and z axes.
731*/
732void QQuick3DInstanceListEntry::setScale(QVector3D scale)
733{
734 if (m_scale == scale)
735 return;
736
737 m_scale = scale;
738 emit scaleChanged();
739 emit changed();
740}
741
742/*!
743 \qmlproperty vector3d QtQuick3D::InstanceListEntry::eulerRotation
744
745 This property specifies the rotation for the instance as an Euler vector, that
746 is a vector containing the rotation in degrees around the x, y and z axes.
747*/
748void QQuick3DInstanceListEntry::setEulerRotation(QVector3D eulerRotation)
749{
750 if (m_useEulerRotation && m_eulerRotation == eulerRotation)
751 return;
752 m_eulerRotation = eulerRotation;
753 m_useEulerRotation = true;
754 emit eulerRotationChanged();
755 emit changed();
756}
757
758/*!
759 \qmlproperty quaternion QtQuick3D::InstanceListEntry::rotation
760
761 This property specifies the rotation for the instance as a quaternion.
762*/
763void QQuick3DInstanceListEntry::setRotation(QQuaternion rotation)
764{
765 if (!m_useEulerRotation && m_rotation == rotation)
766 return;
767
768 m_rotation = rotation;
769 m_useEulerRotation = false;
770 emit rotationChanged();
771 emit changed();
772}
773
774/*!
775 \qmlproperty color QtQuick3D::InstanceListEntry::color
776
777 This property specifies the color for the instance.
778*/
779void QQuick3DInstanceListEntry::setColor(QColor color)
780{
781 if (m_color == color)
782 return;
783
784 m_color = color;
785 emit colorChanged();
786 emit changed();
787}
788
789/*!
790 \qmlproperty vector4d QtQuick3D::InstanceListEntry::customData
791
792 This property specifies the custom data for the instance. This is not used by default,
793 but is made available to the vertex shader of custom materials as \c INSTANCE_DATA.
794*/
795void QQuick3DInstanceListEntry::setCustomData(QVector4D customData)
796{
797 if (m_customData == customData)
798 return;
799
800 m_customData = customData;
801 emit customDataChanged();
802 emit changed();
803}
804
805/*!
806 \qmltype FileInstancing
807 \inherits Instancing
808 \inqmlmodule QtQuick3D
809 \since 6.2
810 \brief Allows reading instance tables from file.
811
812 The FileInstancing type makes it possible to read instance tables from files.
813
814 There are two supported file formats: XML, and a Qt-specific binary format. The
815 binary file format uses the same layout as the table that is uploaded to the GPU,
816 so it can be directly mapped to memory. The \l{Instancer Tool}{instancer} tool converts
817 from XML to the binary format.
818
819 This is an example of the XML file format:
820 \badcode
821 <?xml version="1.0" encoding="UTF-8" ?>
822 <InstanceTable>
823 <Instance position="0 200 0" scale="0.75 0.75 0.75" custom="20 20" color="#ffcf7f"/>
824 <Instance position="0 -100 0" scale="0.5 0.5 0.5" color="red"/>
825 <Instance position="0 -200 0" eulerRotation="0 0 60" color="darkred" custom="10 40 0 0"/>
826 </InstanceTable>
827 \endcode
828
829 In order to be valid, the XML file must have a top-level \c{InstanceTable} element. Each
830 instance is represented by an \c{Instance} element inside the \c{InstanceTable}. Unknown
831 elements are silently ignored.
832
833 An \c{Instance} element can have a number of attributes. \c{color} attributes are specified by the normal Qt
834 SVG color names, or by hexadecimal notation. \c{vector3d} and {vector4d} attributes are specified by
835 a string of space-separated numbers, where missing trailing numbers indicate zeroes. The following
836 attributes are supported:
837 \table
838 \header
839 \li name
840 \li type
841 \row
842 \li \c position
843 \li \c vector3d
844 \row
845 \li \c scale
846 \li \c vector3d
847 \row
848 \li \c eulerRotation
849 \li \c vector3d
850 \row
851 \li \c quaternion
852 \li \c vector4d
853 \row
854 \li \c custom
855 \li \c vector4d
856 \row
857 \li \c color
858 \li \c color
859 \endtable
860 Unknown attributes are silently ignored.
861*/
862
863/*!
864 \qmlproperty url QtQuick3D::FileInstancing::source
865
866 This property holds the location of an XML or binary file containing the instance data.
867
868 If the file name has a ".bin" extension, it is assumed to refer to a binary file.
869 Otherwise it is assumed to refer to an XML file. If an XML file \e{foo.xml} is specified, and
870 the file \e{foo.xml.bin} exists, the binary file \e{foo.xml.bin} will be loaded instead.
871*/
872
873/*!
874 \qmlproperty int QtQuick3D::FileInstancing::instanceCount
875 \since 6.3
876
877 This read-only property contains the number of instances in the instance table.
878*/
879
880static constexpr quint16 currentMajorVersion = 1;
881
891
892static bool writeInstanceTable(QIODevice *out, const QByteArray &instanceData, int instanceCount)
893{
895
896 header.offset = sizeof(header);
897 header.count = instanceCount;
898
899 if (instanceData.size() != qsizetype(header.stride) * instanceCount) {
900 qWarning() << "inconsistent data";
901 return false;
902 }
903
904 // Ignoring endianness: Assume we always create on little-endian, and then special-case reading if we need to.
905
906 out->write(reinterpret_cast<const char *>(&header), sizeof(header));
907 out->write(instanceData.constData(), instanceData.size());
908 return true;
909}
910
911
912bool QQuick3DFileInstancing::loadFromBinaryFile(const QString &filename)
913{
914 auto binaryFile = std::make_unique<QFile>(filename);
915 if (!binaryFile->open(QFile::ReadOnly))
916 return false;
917
918 constexpr auto headerSize = sizeof(QQuick3DInstancingBinaryFileHeader);
919 const quint64 fileSize = binaryFile->size();
920 if (fileSize < headerSize) {
921 qWarning() << "data file too small";
922 return false;
923 }
924 const char *data = reinterpret_cast<const char *>(binaryFile->map(0, fileSize));
925 const auto *header = reinterpret_cast<const QQuick3DInstancingBinaryFileHeader *>(data);
926
927 if (header->majorVersion > currentMajorVersion) {
928 qWarning() << "Version" << header->majorVersion << "is too new";
929 return false;
930 }
931
932 if (fileSize != headerSize + header->count * header->stride) {
933 qWarning() << "wrong data size";
934 return false;
935 }
936
937 delete m_dataFile;
938
939 // In order to use fromRawData safely, the file has to stay open so that the mmap stays valid
940 m_dataFile = binaryFile.release();
941
942 m_instanceData = QByteArray::fromRawData(data + header->offset, header->count * header->stride);
943 m_instanceCount = header->count;
944
945 return true;
946}
947
948bool QQuick3DFileInstancing::loadFromXmlFile(const QString &filename)
949{
950 QFile f(filename);
951 if (!f.open(QFile::ReadOnly))
952 return false;
953
954 bool valid = false;
955 QXmlStreamReader reader(&f);
956 int instances = 0;
957
958 //### Why is there not a QTextStream constructor that takes a QStringView
959 const auto toVector3D = [](const QStringView &str) {
960 float x, y, z;
961 QTextStream(str.toLocal8Bit()) >> x >> y >> z;
962 return QVector3D { x, y, z };
963 };
964 const auto toVector4D = [](const QStringView &str) {
965 float x, y, z, w;
966 QTextStream(str.toLocal8Bit()) >> x >> y >> z >> w;
967 return QVector4D { x, y, z, w };
968 };
969
970 QByteArray instanceData;
971
972 while (reader.readNextStartElement()) {
973
974 if (reader.name() == QLatin1String("InstanceTable")) {
975 valid = true;
976 while (reader.readNextStartElement()) {
977 if (reader.name() == QLatin1String("Instance")) {
978 QColor color = Qt::white;
979 QVector3D position;
980 QVector3D eulerRotation;
981 QQuaternion quaternion;
982 bool useQuaternion = false;
983 QVector4D custom;
984 QVector3D scale { 1, 1, 1 };
985 for (auto &attr : reader.attributes()) {
986 if (attr.name() == QLatin1String("color")) {
987 color = QColor::fromString(attr.value());
988 } else if (attr.name() == QLatin1String("position")) {
989 position = toVector3D(attr.value());
990 } else if (attr.name() == QLatin1String("eulerRotation")) {
991 eulerRotation = toVector3D(attr.value());
992 } else if (attr.name() == QLatin1String("scale")) {
993 scale = toVector3D(attr.value());
994 } else if (attr.name() == QLatin1String("quaternion")) {
995 quaternion = QQuaternion(toVector4D(attr.value()));
996 useQuaternion = true;
997 } else if (attr.name() == QLatin1String("custom")) {
998 custom = toVector4D(attr.value());
999 }
1000 }
1001 auto entry = useQuaternion ? calculateTableEntryFromQuaternion(position, scale, quaternion, color, custom)
1002 : calculateTableEntry(position, scale, eulerRotation, color, custom);
1003 instanceData.append(reinterpret_cast<const char *>(&entry), sizeof(entry));
1004 instances++;
1005 }
1006 reader.skipCurrentElement();
1007 }
1008 } else {
1009 reader.skipCurrentElement();
1010 }
1011 }
1012
1013 if (valid) {
1014 m_instanceCount = instances;
1015 m_instanceData = instanceData;
1016 }
1017
1018 f.close();
1019 return valid;
1020}
1021
1022int QQuick3DFileInstancing::writeToBinaryFile(QIODevice *out)
1023{
1024 bool success = writeInstanceTable(out, m_instanceData, m_instanceCount);
1025 return success ? m_instanceCount : -1;
1026}
1027
1028int QQuick3DFileInstancing::instanceCount() const
1029{
1030 return m_instanceCount;
1031}
1032
1033bool QQuick3DFileInstancing::loadFromFile(const QUrl &source)
1034{
1035 const QQmlContext *context = qmlContext(this);
1036
1037 const QString filePath = QQmlFile::urlToLocalFileOrQrc(context ? context->resolvedUrl(source) : source);
1038
1039 if (filePath.endsWith(QStringLiteral(".bin")))
1040 return loadFromBinaryFile(filePath);
1041
1042 const QString binaryFilePath = filePath + QStringLiteral(".bin");
1043
1044 int oldCount = m_instanceCount;
1045 bool success = loadFromBinaryFile(binaryFilePath) || loadFromXmlFile(filePath);
1046 if (m_instanceCount != oldCount)
1047 emit instanceCountChanged();
1048
1049 return success;
1050}
1051
1052QQuick3DFileInstancing::QQuick3DFileInstancing(QQuick3DObject *parent) : QQuick3DInstancing(parent) { }
1053
1054QQuick3DFileInstancing::~QQuick3DFileInstancing()
1055{
1056 delete m_dataFile;
1057}
1058
1059const QUrl &QQuick3DFileInstancing::source() const
1060{
1061 return m_source;
1062}
1063
1064void QQuick3DFileInstancing::setSource(const QUrl &newSource)
1065{
1066 if (m_source == newSource)
1067 return;
1068 m_source = newSource;
1069 m_dirty = true;
1070 markDirty();
1071
1072 emit sourceChanged();
1073}
1074
1075QByteArray QQuick3DFileInstancing::getInstanceBuffer(int *instanceCount)
1076{
1077 if (m_dirty) {
1078 if (!loadFromFile(m_source)) {
1079 qWarning() << Q_FUNC_INFO << "could not load" << m_source;
1080 m_instanceData = {};
1081 m_instanceCount = 0;
1082 }
1083 m_dirty = false;
1084 }
1085
1086 if (instanceCount)
1087 *instanceCount = m_instanceCount;
1088 return m_instanceData;
1089}
1090
1091static_assert(sizeof(QQuick3DInstancing::InstanceTableEntry) == sizeof(QSSGRenderInstanceTableEntry)
1092 && alignof(QQuick3DInstancing::InstanceTableEntry) == alignof(QSSGRenderInstanceTableEntry),
1093 "QSSGRenderInstanceTableEntry and QQuick3DInstancing::InstanceTableEntry do not match");
1094
1095QT_END_NAMESPACE
static constexpr quint16 currentMajorVersion
\qmltype FileInstancing \inherits Instancing \inqmlmodule QtQuick3D
static QQuick3DInstancing::InstanceTableEntry calculate(const QVector3D &position, const QVector3D &scale, const QVector3D &eulerRotation, const QColor &color, const QVector4D &customData)
static bool writeInstanceTable(QIODevice *out, const QByteArray &instanceData, int instanceCount)