1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dtexture_p.h"
5#include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
8#include <QtQml/QQmlFile>
9#include <QtQuick/QQuickItem>
10#include <QtQuick/private/qquickitem_p.h>
11#include <QtCore/qmath.h>
12
13#include "qquick3dobject_p.h"
14#include "qquick3dscenemanager_p.h"
15#include "qquick3dutils_p.h"
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 \qmltype Texture
21 \inherits Object3D
22 \inqmlmodule QtQuick3D
23 \brief Defines a texture for use in 3D scenes.
24
25 A texture is technically any array of pixels (1D, 2D or 3D) and its related
26 settings, such as minification and magnification filters, scaling and UV
27 transformations.
28
29 The Texture type in Qt Quick 3D represents a two-dimensional image. Its use
30 is typically to map onto / wrap around three-dimensional geometry to emulate
31 additional detail which cannot be efficiently modelled in 3D. It can also be
32 used to emulate other lighting effects, such as reflections.
33
34 While Texture itself always represents a 2D texture, other kinds of
35 textures are available as well via subclasses of Texture. For example, to
36 create a cube map texture with 6 faces, use the \l CubeMapTexture type.
37
38 When the geometry is being rendered, each location on its surface will be
39 transformed to a corresponding location in the texture by transforming and
40 interpolating the UV coordinates (texture coordinate) that have been set for
41 the mesh's vertexes. The fragment shader program that is being used to render
42 the active material will then typically sample the material's texture(s) at
43 the given coordinates and use the sampled data in its light calculations.
44
45 \note A Material may use multiple textures to give the desired interaction with
46 light in the 3D scene. It can represent the color of each texel on the geometry
47 surface, but also other attributes of the surface. For instance, a "normal map"
48 can represent the deviation from the geometry normals for each texel on the
49 surface, emulating light interaction with finer details on the surface, such
50 as cracks or bumps. See \l{Qt Quick 3D - Principled Material Example}{the principled
51 material example} for a demonstration of a material with multiple texture maps.
52
53 Texture objects can source image data from:
54 \list
55 \li an image or texture file by using the \l source property,
56 \li a Qt Quick \l Item by using the sourceItem property,
57 \li or by setting the \l textureData property to a \l TextureData item
58 subclass for defining the custom texture contents.
59 \endlist
60
61 The following example maps the image "madewithqt.png" onto the default sphere
62 mesh, and scales the UV coordinates to tile the image on the sphere surface.
63 \qml
64 Model {
65 source: "#Sphere"
66 materials: [ PrincipledMaterial {
67 baseColorMap: Texture {
68 source: "madewithqt.png"
69 scaleU: 4.0
70 scaleV: 4.0
71 }
72 }
73 ]
74 }
75 \endqml
76
77 The result looks as follows:
78 \table
79 \header
80 \li Original image
81 \li Mapped onto a sphere
82 \row
83 \li \image madewithqt.png
84 \li \image spheremap.png
85 \endtable
86
87 \sa {Qt Quick 3D - Procedural Texture Example}
88*/
89
90QQuick3DTexture::QQuick3DTexture(QQuick3DObject *parent)
91 : QQuick3DTexture(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Image2D)), parent)
92{
93}
94
95QQuick3DTexture::QQuick3DTexture(QQuick3DObjectPrivate &dd, QQuick3DObject *parent)
96 : QQuick3DObject(dd, parent)
97{
98 const QMetaObject *mo = metaObject();
99 const int updateSlotIdx = mo->indexOfSlot(slot: "update()");
100 if (updateSlotIdx >= 0)
101 m_updateSlot = mo->method(index: updateSlotIdx);
102 if (!m_updateSlot.isValid())
103 qWarning(msg: "QQuick3DTexture: Failed to find update() slot");
104}
105
106QQuick3DTexture::~QQuick3DTexture()
107{
108 if (m_layer) {
109 if (m_sceneManagerForLayer)
110 m_sceneManagerForLayer->qsgDynamicTextures.removeAll(t: m_layer);
111 m_layer->deleteLater(); // uhh...
112 }
113
114 if (m_sourceItem) {
115 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
116 sourcePrivate->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
117 }
118}
119
120/*!
121 \qmlproperty url QtQuick3D::Texture::source
122
123 This property holds the location of an image or texture file containing the data used by the
124 texture.
125
126 The property is a URL, with the same rules as other source properties, such
127 as \l{Image::source}{Image.source}. With Texture, only the \c qrc and \c
128 file schemes are supported. When no scheme is present and the value is a
129 relative path, it is assumed to be relative to the component's (i.e. the
130 \c{.qml} file's) location.
131
132 The source file can have any conventional image file format
133 \l{QImageReader::supportedImageFormats()}{supported by Qt}. In addition, Texture supports the
134 same \l [QtQuick]{Compressed Texture Files}{compressed texture file types} as QtQuick::Image.
135
136 \note Texture data read from image files such as .png or .jpg involves
137 storing the rows of pixels within the texture in an order defined the Qt
138 Quick 3D rendering engine. When the source file is a container for -
139 possibly compressed - texture data, such transformations cannot happen on
140 the pixel data level. Examples of this are .ktx or .pkm files. Instead, the
141 Texture implicitly enables vertical flipping in the fragment shader code in
142 order to get identical on-screen results. This is controlled by the \l
143 autoOrientation property and can be disabled, if desired.
144
145 \note Some texture compression tools may apply automatic vertical mirroring
146 (flipping) on the image data. In modern tools this is often an opt-in
147 setting. It is important to be aware of the settings used in the asset
148 conditioning pipeline, because an unexpectedly flipped texture, and thus
149 incorrect texturing of objects, can have its root cause in the asset
150 itself, outside the application's and rendering engine's control. When the
151 asset requires it, applications can always set the \l flipV property
152 themselves.
153
154 \sa sourceItem, textureData, autoOrientation, flipV
155*/
156QUrl QQuick3DTexture::source() const
157{
158 return m_source;
159}
160
161/*!
162 \qmlproperty Item QtQuick3D::Texture::sourceItem
163
164 This property defines a Item to be used as the source of the texture. Using
165 this property allows any 2D Qt Quick content to be used as a texture source
166 by rendering that item as an offscreen layer.
167
168 If the item is a \l{QQuickItem::textureProvider()}{texture provider}, no
169 additional texture is used.
170
171 If this property is set, then the value of \l source will be ignored. A
172 Texture should use one method to provide image data, and set only one of
173 source, \l sourceItem, or \l textureData.
174
175 \note Currently input events are forwarded to the Item used as a texture
176 source only if the user is limited to interacting with one sourceItem
177 instance at a time. In other words: you can share the same Item between
178 multiple Textures, but then you cannot have multi-touch interaction with
179 the same item on multiple textures at the same time. So it's best to use a
180 separate 2D subscene instance for each Texture instance, if you expect to
181 manipulate interactive items inside.
182
183 \note Using this property in a Texture that is referenced from multiple
184 windows is strongly discouraged. This includes usage via
185 \l{View3D::importScene}. As the source texture created by this property is
186 only accessible by one render thread, attempting to share it between
187 multiple QQuickWindow instances is going to fail, unless the \c basic
188 render loop of Qt Quick is used instead of the default \c threaded one. See
189 \l{Qt Quick Scene Graph} on more information about the Qt Quick render
190 loops.
191
192 \note A Texture that contains the results of a Qt Quick offscreen render
193 pass will in effect have an Y axis orientation that is different from what
194 a Texture that receives its content via the source property uses. When used
195 in combination with DefaultMaterial or PrincipledMaterial, this is all
196 transparent to the application as the necessary UV transformations are
197 applied automatically as long as the autoOrientation property is set to
198 true, and so no further action is needed, regardless of how the texture was
199 sourced. However, when developing \l{QtQuick3D::CustomMaterial}{custom
200 materials} this needs to be kept in mind by the shader code author when
201 sampling the texture and working with UV coordinates.
202
203 \sa source, textureData, autoOrientation
204*/
205QQuickItem *QQuick3DTexture::sourceItem() const
206{
207 return m_sourceItem;
208}
209
210/*!
211 \qmlproperty float QtQuick3D::Texture::scaleU
212
213 This property defines how to scale the U texture coordinate when mapping to
214 a mesh's UV coordinates.
215
216 Scaling the U value when using horizontal tiling will define how many times the
217 texture is repeated from left to right.
218
219 The default is 1.0.
220
221 \note This property is effective when the Texture is used in combination
222 with a DefaultMaterial or PrincipledMaterial.
223 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
224 code, and so transformations such as the one configured by this property
225 are ignored and are up to the application-provided shader code to
226 implement.
227
228 \sa tilingModeHorizontal
229 */
230float QQuick3DTexture::scaleU() const
231{
232 return m_scaleU;
233}
234
235/*!
236 \qmlproperty float QtQuick3D::Texture::scaleV
237
238 This property defines how to scale the V texture coordinate when mapping to
239 a mesh's UV coordinates.
240
241 Scaling the V value when using vertical tiling will define how many times a
242 texture is repeated from bottom to top.
243
244 The default is 1.0.
245
246 \note This property is effective when the Texture is used in combination
247 with a DefaultMaterial or PrincipledMaterial.
248 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
249 code, and so transformations such as the one configured by this property
250 are ignored and are up to the application-provided shader code to
251 implement.
252
253 \sa tilingModeVertical
254*/
255float QQuick3DTexture::scaleV() const
256{
257 return m_scaleV;
258}
259
260/*!
261 \qmlproperty enumeration QtQuick3D::Texture::mappingMode
262
263 This property defines which method of mapping to use when sampling this
264 texture.
265
266 \value Texture.UV The default value. Suitable for base color, diffuse,
267 opacity, and most other texture maps. Performs standard UV mapping. The
268 same portion of the image will always appear on the same vertex, unless the
269 UV coordinates are transformed and animated.
270
271 \value Texture.Environment Used for
272 \l{PrincipledMaterial::specularReflectionMap}{specular reflection}, this
273 causes the image to be projected onto the material as though it was being
274 reflected. Using this mode for other type of texture maps provides a mirror
275 effect.
276
277 \value Texture.LightProbe The default for HDRI sphere maps used by light
278 probes. This mode does not need to be manually set for Texture objects
279 associated with the \l{SceneEnvironment::lightProbe}{lightProbe} property,
280 because it is implied automatically.
281*/
282QQuick3DTexture::MappingMode QQuick3DTexture::mappingMode() const
283{
284 return m_mappingMode;
285}
286
287/*!
288 \qmlproperty enumeration QtQuick3D::Texture::tilingModeHorizontal
289
290 Controls how the texture is mapped when the U scaling value is greater than 1.
291
292 By default, this property is set to \c{Texture.Repeat}.
293
294 \value Texture.ClampToEdge Texture is not tiled, but the value on the edge is used instead.
295 \value Texture.MirroredRepeat Texture is repeated and mirrored over the X axis.
296 \value Texture.Repeat Texture is repeated over the X axis.
297
298 \sa scaleU
299*/
300QQuick3DTexture::TilingMode QQuick3DTexture::horizontalTiling() const
301{
302 return m_tilingModeHorizontal;
303}
304
305/*!
306 \qmlproperty enumeration QtQuick3D::Texture::tilingModeVertical
307
308 This property controls how the texture is mapped when the V scaling value
309 is greater than 1.
310
311 By default, this property is set to \c{Texture.Repeat}.
312
313 \value Texture.ClampToEdge Texture is not tiled, but the value on the edge is used instead.
314 \value Texture.MirroredRepeat Texture is repeated and mirrored over the Y axis.
315 \value Texture.Repeat Texture is repeated over the Y axis.
316
317 \sa scaleV
318*/
319QQuick3DTexture::TilingMode QQuick3DTexture::verticalTiling() const
320{
321 return m_tilingModeVertical;
322}
323
324/*!
325 \qmlproperty float QtQuick3D::Texture::rotationUV
326
327 This property rotates the texture around the pivot point. This is defined
328 using euler angles and for a positive value rotation is clockwise.
329
330 The default is 0.0.
331
332 \note This property is effective when the Texture is used in combination
333 with a DefaultMaterial or PrincipledMaterial.
334 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
335 code, and so transformations such as the one configured by this property
336 are ignored and are up to the application-provided shader code to
337 implement.
338
339 \sa pivotU, pivotV
340*/
341float QQuick3DTexture::rotationUV() const
342{
343 return m_rotationUV;
344}
345
346/*!
347 \qmlproperty float QtQuick3D::Texture::positionU
348
349 This property offsets the U coordinate mapping from left to right.
350
351 The default is 0.0.
352
353 \note This property is effective when the Texture is used in combination
354 with a DefaultMaterial or PrincipledMaterial.
355 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
356 code, and so transformations such as the one configured by this property
357 are ignored and are up to the application-provided shader code to
358 implement.
359
360 \sa positionV
361*/
362float QQuick3DTexture::positionU() const
363{
364 return m_positionU;
365}
366
367/*!
368 \qmlproperty float QtQuick3D::Texture::positionV
369
370 This property offsets the V coordinate mapping from bottom to top.
371
372 The default is 0.0.
373
374 \note Qt Quick 3D uses OpenGL-style vertex data, regardless of the graphics
375 API used at run time. The UV position \c{(0, 0)} is therefore referring to
376 the bottom-left corner of the image data.
377
378 \note This property is effective when the Texture is used in combination
379 with a DefaultMaterial or PrincipledMaterial.
380 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
381 code, and so transformations such as the one configured by this property
382 are ignored and are up to the application-provided shader code to
383 implement.
384
385 \sa positionU
386*/
387float QQuick3DTexture::positionV() const
388{
389 return m_positionV;
390}
391
392/*!
393 \qmlproperty float QtQuick3D::Texture::pivotU
394
395 This property sets the pivot U position which is used when applying a
396 \l{QtQuick3D::Texture::rotationUV}{rotationUV}.
397
398 The default is 0.0.
399
400 \note This property is effective when the Texture is used in combination
401 with a DefaultMaterial or PrincipledMaterial.
402 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
403 code, and so transformations such as the one configured by this property
404 are ignored and are up to the application-provided shader code to
405 implement.
406
407 \sa rotationUV
408*/
409float QQuick3DTexture::pivotU() const
410{
411 return m_pivotU;
412}
413
414/*!
415 \qmlproperty float QtQuick3D::Texture::pivotV
416
417 This property sets the pivot V position which is used when applying a
418 \l{QtQuick3D::Texture::rotationUV}{rotationUV}.
419
420 The default is 0.0.
421
422 \note This property is effective when the Texture is used in combination
423 with a DefaultMaterial or PrincipledMaterial.
424 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
425 code, and so transformations such as the one configured by this property
426 are ignored and are up to the application-provided shader code to
427 implement.
428
429 \sa pivotU, rotationUV
430*/
431float QQuick3DTexture::pivotV() const
432{
433 return m_pivotV;
434}
435
436/*!
437 \qmlproperty bool QtQuick3D::Texture::flipU
438
439 This property sets the use of the horizontally flipped texture coordinates.
440
441 The default is false.
442
443 \note This property is effective when the Texture is used in combination
444 with a DefaultMaterial or PrincipledMaterial.
445 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
446 code, and so transformations such as the one configured by this property
447 are ignored and are up to the application-provided shader code to
448 implement.
449
450 \sa flipV
451*/
452bool QQuick3DTexture::flipU() const
453{
454 return m_flipU;
455}
456
457/*!
458 \qmlproperty bool QtQuick3D::Texture::flipV
459
460 This property sets the use of the vertically flipped texture coordinates.
461
462 The default is false.
463
464 \note This property is effective when the Texture is used in combination
465 with a DefaultMaterial or PrincipledMaterial.
466 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
467 code, and so transformations such as the one configured by this property
468 are ignored and are up to the application-provided shader code to
469 implement.
470
471 \sa flipU
472*/
473bool QQuick3DTexture::flipV() const
474{
475 return m_flipV;
476}
477
478/*!
479 \qmlproperty int QtQuick3D::Texture::indexUV
480
481 This property sets the UV coordinate index used by this texture. Since
482 QtQuick3D supports 2 UV sets(0 or 1) for now, the value will be saturated
483 to the range.
484
485 The default is 0.
486*/
487int QQuick3DTexture::indexUV() const
488{
489 return m_indexUV;
490}
491
492/*!
493 \qmlproperty enumeration QtQuick3D::Texture::magFilter
494
495 This property determines how the texture is sampled when a texel covers
496 more than one pixel.
497
498 The default value is \c{Texture.Linear}.
499
500 \value Texture.Nearest uses the value of the closest texel.
501 \value Texture.Linear takes the four closest texels and bilinearly interpolates them.
502
503 \note Using \c Texture.None here will default to \c Texture.Linear instead.
504
505 \sa minFilter, mipFilter
506*/
507QQuick3DTexture::Filter QQuick3DTexture::magFilter() const
508{
509 return m_magFilter;
510}
511
512/*!
513 \qmlproperty enumeration QtQuick3D::Texture::minFilter
514
515 This property determines how the texture is sampled when a texel covers
516 more than one pixel.
517
518 The default value is \c{Texture.Linear}.
519
520 \value Texture.Nearest uses the value of the closest texel.
521 \value Texture.Linear takes the four closest texels and bilinearly interpolates them.
522
523 \note Using \c Texture.None here will default to \c Texture.Linear instead.
524
525 \sa magFilter, mipFilter
526*/
527QQuick3DTexture::Filter QQuick3DTexture::minFilter() const
528{
529 return m_minFilter;
530}
531
532/*!
533 \qmlproperty enumeration QtQuick3D::Texture::mipFilter
534
535 This property determines how the texture mipmaps are sampled when a texel covers
536 less than one pixel.
537
538 The default value is \c{Texture.None}.
539
540 \value Texture.None disables the usage of mipmap sampling.
541 \value Texture.Nearest uses mipmapping and samples the value of the closest texel.
542 \value Texture.Linear uses mipmapping and interpolates between multiple texel values.
543
544 \note This property will have no effect on Textures that do not have mipmaps.
545
546 \sa minFilter, magFilter
547*/
548QQuick3DTexture::Filter QQuick3DTexture::mipFilter() const
549{
550 return m_mipFilter;
551}
552
553/*!
554 \qmlproperty TextureData QtQuick3D::Texture::textureData
555
556 This property holds a reference to a \l TextureData component which
557 defines the contents and properties of raw texture data.
558
559 If this property is used, then the value of \l source will be ignored. A
560 Texture should use one method to provide image data, and set only one of
561 source, \l sourceItem, or \l textureData.
562
563 \sa source, sourceItem, {Qt Quick 3D - Procedural Texture Example}
564*/
565
566QQuick3DTextureData *QQuick3DTexture::textureData() const
567{
568 return m_textureData;
569}
570
571/*!
572 \qmlproperty bool QtQuick3D::Texture::generateMipmaps
573
574 This property determines if mipmaps are generated for textures that
575 do not provide mipmap levels themselves. Using mipmaps along with mip
576 filtering gives better visual quality when viewing textures at a distance
577 compared rendering without them, but it may come at a performance
578 cost (both when initializing the image and during rendering).
579
580 By default, this property is set to false.
581
582 \note It is necessary to set a \l{QtQuick3D::Texture::mipFilter}{mipFilter} mode
583 for the generated mipmaps to be be used.
584
585 \note This property is not applicable when the texture content is based on
586 a Qt Quick item referenced by the \l sourceItem property. Mipmap generation
587 for dynamic textures is not feasible due to the performance implications.
588 Therefore, the value of this property is ignored for such textures.
589
590 \sa mipFilter
591*/
592bool QQuick3DTexture::generateMipmaps() const
593{
594 return m_generateMipmaps;
595}
596
597/*!
598 \qmlproperty bool QtQuick3D::Texture::autoOrientation
599
600 This property determines if a texture transformation, such as flipping the
601 V texture coordinate, is applied automatically for textures where this is
602 typically relevant.
603
604 By default, this property is set to true.
605
606 Certain type of texture data, such as compressed textures loaded via the \l
607 source property from a .ktx or .pkm file, or textures generated by
608 rendering a Qt Quick scene via the \l sourceItem property, often have a
609 different Y axis orientation when compared to textures loaded from image
610 files, such as, .png or .jpg. Therefore, such a Texture would appear
611 "upside down" compared to a Texture with its source set to a regular image
612 file. To remedy this, any qualifying Texture gets an implicit UV
613 transformation as if the flipV property was set to true. If this is not
614 desired, set this property to false.
615
616 \note This property is effective when the Texture is used in combination
617 with a DefaultMaterial or PrincipledMaterial.
618 \l{QtQuick3D::CustomMaterial}{Custom materials} provide their own shader
619 code, and so transformations such as the one configured by this property
620 are ignored and are up to the application-provided shader code to
621 implement.
622
623 \since 6.2
624
625 \sa flipV
626*/
627
628bool QQuick3DTexture::autoOrientation() const
629{
630 return m_autoOrientation;
631}
632
633void QQuick3DTexture::setSource(const QUrl &source)
634{
635 if (m_source == source)
636 return;
637
638 m_source = source;
639 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceDirty);
640 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty);
641 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDataDirty);
642 emit sourceChanged();
643 update();
644}
645
646void QQuick3DTexture::trySetSourceParent()
647{
648 if (m_sourceItem->parentItem() && m_sourceItemRefed)
649 return;
650
651 auto *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
652
653 if (!m_sourceItem->parentItem()) {
654 if (const auto &manager = QQuick3DObjectPrivate::get(item: this)->sceneManager) {
655 if (auto *window = manager->window()) {
656 if (m_sourceItemRefed) {
657 // Item was already refed but probably with hide set to false...
658 // so we need to deref before we ref again below.
659 const bool hide = m_sourceItemReparented;
660 sourcePrivate->derefFromEffectItem(unhide: hide);
661 m_sourceItemRefed = false;
662 }
663
664 m_sourceItem->setParentItem(window->contentItem());
665 m_sourceItemReparented = true;
666 update();
667 }
668 }
669 }
670
671 if (!m_sourceItemRefed) {
672 const bool hide = m_sourceItemReparented;
673 sourcePrivate->refFromEffectItem(hide);
674 }
675}
676
677void QQuick3DTexture::setSourceItem(QQuickItem *sourceItem)
678{
679 if (m_sourceItem == sourceItem)
680 return;
681
682 disconnect(m_textureProviderConnection);
683 disconnect(m_textureUpdateConnection);
684
685 if (m_sourceItem) {
686 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
687
688 const bool hide = m_sourceItemReparented;
689 sourcePrivate->derefFromEffectItem(unhide: hide);
690 m_sourceItemRefed = false;
691
692 sourcePrivate->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
693 disconnect(sender: m_sourceItem, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceItemDestroyed(QObject*)));
694 if (m_sourceItemReparented) {
695 m_sourceItem->setParentItem(nullptr);
696 m_sourceItemReparented = false;
697 }
698 }
699
700 m_sourceItem = sourceItem;
701
702 if (sourceItem) {
703 trySetSourceParent();
704 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
705 sourcePrivate->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry);
706 connect(sender: m_sourceItem, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceItemDestroyed(QObject*)));
707 sourcePrivate->ensureSubsceneDeliveryAgent();
708 }
709
710 if (m_layer) {
711 const auto &manager = QQuick3DObjectPrivate::get(item: this)->sceneManager;
712 manager->qsgDynamicTextures.removeAll(t: m_layer);
713 m_sceneManagerForLayer = nullptr;
714 // cannot touch m_layer here
715 }
716 m_initializedSourceItem = nullptr;
717 m_initializedSourceItemSize = QSize();
718
719 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceDirty);
720 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty);
721 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDataDirty);
722 emit sourceItemChanged();
723 update();
724}
725
726void QQuick3DTexture::setScaleU(float scaleU)
727{
728 if (qFuzzyCompare(p1: m_scaleU, p2: scaleU))
729 return;
730
731 m_scaleU = scaleU;
732 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
733 emit scaleUChanged();
734 update();
735}
736
737void QQuick3DTexture::setScaleV(float scaleV)
738{
739 if (qFuzzyCompare(p1: m_scaleV, p2: scaleV))
740 return;
741
742 m_scaleV = scaleV;
743 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
744 emit scaleVChanged();
745 update();
746}
747
748void QQuick3DTexture::setMappingMode(QQuick3DTexture::MappingMode mappingMode)
749{
750 if (m_mappingMode == mappingMode)
751 return;
752
753 m_mappingMode = mappingMode;
754 emit mappingModeChanged();
755 update();
756}
757
758void QQuick3DTexture::setHorizontalTiling(QQuick3DTexture::TilingMode tilingModeHorizontal)
759{
760 if (m_tilingModeHorizontal == tilingModeHorizontal)
761 return;
762
763 m_tilingModeHorizontal = tilingModeHorizontal;
764 emit horizontalTilingChanged();
765 update();
766}
767
768void QQuick3DTexture::setVerticalTiling(QQuick3DTexture::TilingMode tilingModeVertical)
769{
770 if (m_tilingModeVertical == tilingModeVertical)
771 return;
772
773 m_tilingModeVertical = tilingModeVertical;
774 emit verticalTilingChanged();
775 update();
776}
777
778void QQuick3DTexture::setRotationUV(float rotationUV)
779{
780 if (qFuzzyCompare(p1: m_rotationUV, p2: rotationUV))
781 return;
782
783 m_rotationUV = rotationUV;
784 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
785 emit rotationUVChanged();
786 update();
787}
788
789void QQuick3DTexture::setPositionU(float positionU)
790{
791 if (qFuzzyCompare(p1: m_positionU, p2: positionU))
792 return;
793
794 m_positionU = positionU;
795 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
796 emit positionUChanged();
797 update();
798}
799
800void QQuick3DTexture::setPositionV(float positionV)
801{
802 if (qFuzzyCompare(p1: m_positionV, p2: positionV))
803 return;
804
805 m_positionV = positionV;
806 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
807 emit positionVChanged();
808 update();
809}
810
811void QQuick3DTexture::setPivotU(float pivotU)
812{
813 if (qFuzzyCompare(p1: m_pivotU, p2: pivotU))
814 return;
815
816 m_pivotU = pivotU;
817 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
818 emit pivotUChanged();
819 update();
820}
821
822void QQuick3DTexture::setPivotV(float pivotV)
823{
824 if (qFuzzyCompare(p1: m_pivotV, p2: pivotV))
825 return;
826
827 m_pivotV = pivotV;
828 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
829 emit pivotVChanged();
830 update();
831}
832
833void QQuick3DTexture::setFlipU(bool flipU)
834{
835 if (m_flipU == flipU)
836 return;
837
838 m_flipU = flipU;
839 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty);
840 emit flipUChanged();
841 update();
842}
843
844void QQuick3DTexture::setFlipV(bool flipV)
845{
846 if (m_flipV == flipV)
847 return;
848
849 m_flipV = flipV;
850 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty);
851 emit flipVChanged();
852 update();
853}
854
855void QQuick3DTexture::setIndexUV(int indexUV)
856{
857 if (m_indexUV == indexUV)
858 return;
859
860 if (indexUV < 0)
861 m_indexUV = 0;
862 else if (indexUV > 1)
863 m_indexUV = 1;
864 else
865 m_indexUV = indexUV;
866
867 m_dirtyFlags.setFlag(flag: DirtyFlag::IndexUVDirty);
868 emit indexUVChanged();
869 update();
870}
871
872void QQuick3DTexture::setTextureData(QQuick3DTextureData *textureData)
873{
874 if (m_textureData == textureData)
875 return;
876
877 // Make sure to disconnect if the geometry gets deleted out from under us
878 QQuick3DObjectPrivate::attachWatcher(context: this, setter: &QQuick3DTexture::setTextureData, newO: textureData, oldO: m_textureData);
879
880 if (m_textureData)
881 QObject::disconnect(m_textureDataConnection);
882 m_textureData = textureData;
883
884 if (m_textureData) {
885 m_textureDataConnection
886 = QObject::connect(sender: m_textureData, signal: &QQuick3DTextureData::textureDataNodeDirty, slot: [this]() {
887 markDirty(type: DirtyFlag::TextureDataDirty);
888 });
889 }
890
891 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceDirty);
892 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty);
893 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDataDirty);
894 emit textureDataChanged();
895 update();
896}
897
898void QQuick3DTexture::setGenerateMipmaps(bool generateMipmaps)
899{
900 if (m_generateMipmaps == generateMipmaps)
901 return;
902
903 m_generateMipmaps = generateMipmaps;
904 m_dirtyFlags.setFlag(flag: DirtyFlag::SamplerDirty);
905 emit generateMipmapsChanged();
906 update();
907}
908
909void QQuick3DTexture::setAutoOrientation(bool autoOrientation)
910{
911 if (m_autoOrientation == autoOrientation)
912 return;
913
914 m_autoOrientation = autoOrientation;
915 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty);
916 emit autoOrientationChanged();
917 update();
918}
919
920void QQuick3DTexture::setMagFilter(QQuick3DTexture::Filter magFilter)
921{
922 if (m_magFilter == magFilter)
923 return;
924
925 m_magFilter = magFilter;
926 m_dirtyFlags.setFlag(flag: DirtyFlag::SamplerDirty);
927 emit magFilterChanged();
928 update();
929}
930
931void QQuick3DTexture::setMinFilter(QQuick3DTexture::Filter minFilter)
932{
933 if (m_minFilter == minFilter)
934 return;
935
936 m_minFilter = minFilter;
937 m_dirtyFlags.setFlag(flag: DirtyFlag::SamplerDirty);
938 emit minFilterChanged();
939 update();
940}
941
942void QQuick3DTexture::setMipFilter(QQuick3DTexture::Filter mipFilter)
943{
944 if (m_mipFilter == mipFilter)
945 return;
946
947 m_mipFilter = mipFilter;
948 m_dirtyFlags.setFlag(flag: DirtyFlag::SamplerDirty);
949 emit mipFilterChanged();
950 update();
951}
952
953// this function may involve file system access and hence can be expensive
954bool QQuick3DTexture::effectiveFlipV(const QSSGRenderImage &imageNode) const
955{
956 // No magic when autoOrientation is false.
957 if (!m_autoOrientation)
958 return m_flipV;
959
960 // Keep the same order as in QSSGBufferManager: sourceItem > textureData > source
961
962 // Using sourceItem implies inverting (the effective, internal) flipV,
963 // transparently to the user. Otherwise two #Rectangle models textured with
964 // two Textures where one has its content loaded from an image file via
965 // QImage while the other is generated by Qt Quick rendering into the
966 // texture would appear upside-down relative to each other, and that
967 // discrepancy is not ideal. (that said, this won't help CustomMaterial, as
968 // documented for flipV and co.)
969
970 if (m_sourceItem)
971 return !m_flipV;
972
973 // With textureData we assume the application knows what it is doing,
974 // because there the application is controlling the content itself.
975
976 if (m_textureData)
977 return m_flipV;
978
979 // Compressed textures (or any texture that is coming from the associated
980 // container formats, such as KTX, i.e. not via QImage but through
981 // QTextureFileReader) get the implicit flip, like sourceItem. This is done
982 // mainly for parity with Qt Quick's Image, see QTBUG-93972.
983
984 if (!m_source.isEmpty()) {
985 const QString filePath = imageNode.m_imagePath.path();
986 if (!filePath.isEmpty()) {
987 QSSGInputUtil::FileType fileType = QSSGInputUtil::UnknownFile;
988 if (QSSGInputUtil::getStreamForTextureFile(inPath: filePath, inQuiet: true, outPath: nullptr, outFileType: &fileType)) {
989 if (fileType == QSSGInputUtil::TextureFile)
990 return !m_flipV;
991 }
992 }
993 }
994
995 return m_flipV;
996}
997
998static QSSGRenderPath resolveImagePath(const QUrl &url, const QQmlContext *context)
999{
1000 if (context && url.isRelative()) {
1001 QString path = url.path();
1002 QChar separator = QChar::fromLatin1(c: ';');
1003 if (path.contains(c: separator)) {
1004 QString resolvedPath;
1005 const QStringList paths = path.split(sep: separator);
1006 bool first = true;
1007 for (auto &s : paths) {
1008 auto mapped = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(s));
1009 if (!first)
1010 resolvedPath.append(c: separator);
1011 resolvedPath.append(s: mapped);
1012 first = false;
1013 }
1014 return QSSGRenderPath(resolvedPath);
1015 }
1016 }
1017 return QSSGRenderPath(QQmlFile::urlToLocalFileOrQrc(context ? context->resolvedUrl(url) : url));
1018}
1019
1020QSSGRenderGraphObject *QQuick3DTexture::updateSpatialNode(QSSGRenderGraphObject *node)
1021{
1022 if (!node) {
1023 markAllDirty();
1024 node = new QSSGRenderImage(QQuick3DObjectPrivate::get(item: this)->type);
1025 }
1026 QQuick3DObject::updateSpatialNode(node);
1027 auto imageNode = static_cast<QSSGRenderImage *>(node);
1028
1029 if (m_dirtyFlags.testFlag(flag: DirtyFlag::TransformDirty)) {
1030 m_dirtyFlags.setFlag(flag: DirtyFlag::TransformDirty, on: false);
1031
1032 // flipV and indexUV have their own dirty flags, handled separately below
1033 imageNode->m_flipU = m_flipU;
1034 imageNode->m_scale = QVector2D(m_scaleU, m_scaleV);
1035 imageNode->m_pivot = QVector2D(m_pivotU, m_pivotV);
1036 imageNode->m_rotation = m_rotationUV;
1037 imageNode->m_position = QVector2D(m_positionU, m_positionV);
1038
1039 imageNode->m_flags.setFlag(flag: QSSGRenderImage::Flag::TransformDirty);
1040 }
1041
1042 bool nodeChanged = false;
1043 if (m_dirtyFlags.testFlag(flag: DirtyFlag::SourceDirty)) {
1044 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceDirty, on: false);
1045 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty, on: true);
1046 if (!m_source.isEmpty()) {
1047 const QQmlContext *context = qmlContext(this);
1048 imageNode->m_imagePath = resolveImagePath(url: m_source, context);
1049 } else {
1050 imageNode->m_imagePath = QSSGRenderPath();
1051 }
1052 nodeChanged = true;
1053 }
1054 if (m_dirtyFlags.testFlag(flag: DirtyFlag::IndexUVDirty)) {
1055 m_dirtyFlags.setFlag(flag: DirtyFlag::IndexUVDirty, on: false);
1056 imageNode->m_indexUV = m_indexUV;
1057 }
1058 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_mappingMode,
1059 updated: QSSGRenderImage::MappingModes(m_mappingMode));
1060 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_horizontalTilingMode,
1061 updated: QSSGRenderTextureCoordOp(m_tilingModeHorizontal));
1062 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_verticalTilingMode,
1063 updated: QSSGRenderTextureCoordOp(m_tilingModeVertical));
1064
1065 if (m_dirtyFlags.testFlag(flag: DirtyFlag::SamplerDirty)) {
1066 m_dirtyFlags.setFlag(flag: DirtyFlag::SamplerDirty, on: false);
1067 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_minFilterType,
1068 updated: QSSGRenderTextureFilterOp(m_minFilter));
1069 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_magFilterType,
1070 updated: QSSGRenderTextureFilterOp(m_magFilter));
1071 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_mipFilterType,
1072 updated: QSSGRenderTextureFilterOp(m_mipFilter));
1073 nodeChanged |= qUpdateIfNeeded(orig&: imageNode->m_generateMipmaps,
1074 updated: m_generateMipmaps);
1075 }
1076
1077 if (m_dirtyFlags.testFlag(flag: DirtyFlag::TextureDataDirty)) {
1078 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDataDirty, on: false);
1079 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty, on: true);
1080 if (m_textureData)
1081 imageNode->m_rawTextureData = static_cast<QSSGRenderTextureData *>(QQuick3DObjectPrivate::get(item: m_textureData)->spatialNode);
1082 else
1083 imageNode->m_rawTextureData = nullptr;
1084 nodeChanged = true;
1085 }
1086
1087 if (m_dirtyFlags.testFlag(flag: DirtyFlag::SourceItemDirty)) {
1088 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty, on: false);
1089 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty, on: true);
1090 if (m_sourceItem) {
1091 QQuickWindow *window = m_sourceItem->window();
1092 // If it was an inline declared item (very common, e.g. Texture {
1093 // sourceItem: Rectangle { ... } } then it is likely it won't be
1094 // associated with a window (Qt Quick scene) unless we help it to
1095 // one via refWindow. However, this here is only the last resort,
1096 // ideally there is a refWindow upon ItemSceneChange already.
1097 if (!window) {
1098 window = QQuick3DObjectPrivate::get(item: this)->sceneManager->window();
1099 if (window)
1100 QQuickItemPrivate::get(item: m_sourceItem)->refWindow(window);
1101 else
1102 qWarning(msg: "Unable to get window, this will probably not work");
1103 }
1104
1105 // This assumes that the QSGTextureProvider returned never changes,
1106 // which is hopefully the case for both Image and Item layers.
1107 if (QSGTextureProvider *provider = m_sourceItem->textureProvider()) {
1108 imageNode->m_qsgTexture = provider->texture();
1109
1110 disconnect(m_textureProviderConnection);
1111 m_textureProviderConnection = connect(sender: provider, signal: &QSGTextureProvider::textureChanged, context: this, slot: [this, provider] () {
1112 // called on the render thread, if there is one; the gui
1113 // thread may or may not be blocked (e.g. if the source is
1114 // a View3D, that emits textureChanged() from preprocess,
1115 // so after sync, whereas an Image emits in
1116 // updatePaintNode() where gui is blocked)
1117 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(item: this)->spatialNode);
1118 if (!imageNode)
1119 return;
1120
1121 imageNode->m_qsgTexture = provider->texture();
1122 // the QSGTexture may be different now, go through loadRenderImage() again
1123 imageNode->m_flags.setFlag(flag: QSSGRenderImage::Flag::Dirty);
1124 // Call update() on the main thread - otherwise we could
1125 // end up in a situation where the 3D scene does not update
1126 // due to nothing else changing, even though the source
1127 // texture is now different.
1128 m_updateSlot.invoke(obj: this, c: Qt::AutoConnection);
1129 }, type: Qt::DirectConnection);
1130
1131 disconnect(m_textureUpdateConnection);
1132 auto *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
1133 if (sourcePrivate->window) {
1134 QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
1135
1136 // Why after, not beforeSynchronizing? Consider the case of an Item layer:
1137 // if the View3D gets to sync (updatePaintNode) first, doing an
1138 // updateTexture() is futile, the QSGLayer is not yet initialized (not
1139 // associated with an Item, has no size, etc.). That happens only once the
1140 // underlying QQuickShaderEffectSource hits its updatePaintNode. And that
1141 // may well happen happen only after the View3D has finished with its sync
1142 // step. By connecting to afterSynchronizing, we still get a chance to
1143 // trigger a layer texture update and so have a QSGTexture with real
1144 // content ready by the time the View3D prepares/renders the 3D scene upon
1145 // the scenegraph's preprocess step (Offscreen) or before/after the
1146 // scenegraph rendering (if Underlay/Overlay).
1147 //
1148 // This eliminates, or in the worst case reduces, the ugly effects of not
1149 // having a texture ready when rendering the 3D scene.
1150
1151 m_textureUpdateConnection = connect(sender: sourcePrivate->window, signal: &QQuickWindow::afterSynchronizing, context: this, slot: [this, sourceItem]() {
1152 // Called on the render thread with gui blocked (if there is a render thread, that is).
1153 if (m_sourceItem != sourceItem) {
1154 disconnect(m_textureProviderConnection);
1155 disconnect(m_textureUpdateConnection);
1156 return;
1157 }
1158 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(item: this)->spatialNode);
1159 if (!imageNode)
1160 return;
1161
1162 if (QSGDynamicTexture *t = qobject_cast<QSGDynamicTexture *>(object: imageNode->m_qsgTexture)) {
1163 if (t->updateTexture())
1164 update(); // safe because the gui thread is blocked
1165 }
1166 }, type: Qt::DirectConnection);
1167 } else {
1168 qWarning(msg: "No window for item, texture updates are doomed");
1169 }
1170
1171 if (m_layer) {
1172 delete m_layer;
1173 m_layer = nullptr;
1174 }
1175 } else {
1176 // Not a texture provider, so not an Image or an Item with
1177 // layer.enabled: true, create our own QSGLayer.
1178 if (m_initializedSourceItem != m_sourceItem || m_initializedSourceItemSize != m_sourceItem->size()) {
1179 // If there was a previous sourceItem and m_layer is valid
1180 // then set its content to null until we get to
1181 // afterSynchronizing, otherwise things can blow up.
1182 if (m_layer)
1183 m_layer->setItem(nullptr);
1184
1185 m_initializedSourceItem = m_sourceItem;
1186 m_initializedSourceItemSize = m_sourceItem->size();
1187
1188 // The earliest next point where we can do anything is
1189 // after the scenegraph's QQuickItem sync round has completed.
1190 connect(sender: window, signal: &QQuickWindow::afterSynchronizing, context: this, slot: [this, window]() {
1191 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(item: this)->spatialNode);
1192 if (!imageNode)
1193 return;
1194
1195 // Called on the render thread with gui blocked (if there is a render thread, that is).
1196 disconnect(sender: window, signal: &QQuickWindow::afterSynchronizing, receiver: this, zero: nullptr);
1197 if (m_layer) {
1198 const auto &manager = QQuick3DObjectPrivate::get(item: this)->sceneManager;
1199 manager->qsgDynamicTextures.removeAll(t: m_layer);
1200 delete m_layer;
1201 m_layer = nullptr;
1202 }
1203
1204 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(item: m_sourceItem);
1205 QSGRenderContext *rc = sourcePrivate->sceneGraphRenderContext();
1206 Q_ASSERT(QThread::currentThread() == rc->thread()); // must be on the render thread
1207 QSGLayer *layer = rc->sceneGraphContext()->createLayer(renderContext: rc);
1208 connect(sender: sourcePrivate->window, SIGNAL(sceneGraphInvalidated()), receiver: layer, SLOT(invalidated()), Qt::DirectConnection);
1209
1210 QQuick3DSceneManager *manager = QQuick3DObjectPrivate::get(item: this)->sceneManager;
1211 manager->qsgDynamicTextures << layer;
1212 m_sceneManagerForLayer = manager;
1213
1214 connect(sender: layer, signal: &QObject::destroyed, context: manager, slot: [manager, layer]()
1215 {
1216 // this is on the render thread so all borked threading-wise (all data here is gui thread stuff...) but will survive
1217 manager->qsgDynamicTextures.removeAll(t: layer);
1218 }, type: Qt::DirectConnection);
1219
1220 QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
1221 connect(sender: layer, signal: &QObject::destroyed, context: this, slot: [this, sourceItem]()
1222 {
1223 // just as dubious as the previous connection
1224 if (m_initializedSourceItem == sourceItem) {
1225 m_sceneManagerForLayer = nullptr;
1226 m_initializedSourceItem = nullptr;
1227 }
1228 }, type: Qt::DirectConnection);
1229
1230 // With every frame try to update the texture. Use
1231 // afterSynchronizing like in the other branch. (why
1232 // after: a property changing something in the 2D
1233 // subtree leading to updates in the content will only
1234 // be "visible" after the (2D item) sync, not before)
1235 //
1236 // If updateTexture() returns false, content hasn't
1237 // changed. This complements qsgDynamicTextures and
1238 // QQuick3DViewport::updateDynamicTextures().
1239 m_textureUpdateConnection = connect(sender: sourcePrivate->window, signal: &QQuickWindow::afterSynchronizing,
1240 context: this, slot: [this, sourceItem]()
1241 {
1242 // Called on the render thread with gui blocked (if there is a render thread, that is).
1243 if (!m_layer)
1244 return;
1245 if (m_sourceItem != sourceItem) {
1246 disconnect(m_textureUpdateConnection);
1247 return;
1248 }
1249 if (m_layer->updateTexture())
1250 update();
1251 }, type: Qt::DirectConnection);
1252
1253 m_layer = layer;
1254 m_layer->setItem(QQuickItemPrivate::get(item: m_sourceItem)->itemNode());
1255
1256 QRectF sourceRect = QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height());
1257 if (qFuzzyIsNull(d: sourceRect.width()))
1258 sourceRect.setWidth(256);
1259 if (qFuzzyIsNull(d: sourceRect.height()))
1260 sourceRect.setHeight(256);
1261 m_layer->setRect(sourceRect);
1262
1263 QSize textureSize(qCeil(v: qAbs(t: sourceRect.width())), qCeil(v: qAbs(t: sourceRect.height())));
1264 const QSize minTextureSize = sourcePrivate->sceneGraphContext()->minimumFBOSize();
1265 while (textureSize.width() < minTextureSize.width())
1266 textureSize.rwidth() *= 2;
1267 while (textureSize.height() < minTextureSize.height())
1268 textureSize.rheight() *= 2;
1269 m_layer->setSize(textureSize);
1270
1271 // now that the layer has an item and a size, it can render into the texture
1272 m_layer->updateTexture();
1273
1274 imageNode->m_qsgTexture = m_layer;
1275 imageNode->m_flags.setFlag(flag: QSSGRenderImage::Flag::Dirty);
1276 }, type: Qt::DirectConnection);
1277 }
1278 }
1279 } else {
1280 if (m_layer) {
1281 m_layer->setItem(nullptr);
1282 delete m_layer;
1283 m_layer = nullptr;
1284 }
1285 imageNode->m_qsgTexture = nullptr;
1286 }
1287 nodeChanged = true;
1288 }
1289
1290 if (m_dirtyFlags.testFlag(flag: DirtyFlag::FlipVDirty)) {
1291 m_dirtyFlags.setFlag(flag: DirtyFlag::FlipVDirty, on: false);
1292 imageNode->m_flipV = effectiveFlipV(imageNode: *imageNode);
1293 imageNode->m_flags.setFlag(flag: QSSGRenderImage::Flag::TransformDirty);
1294 }
1295
1296 if (nodeChanged)
1297 imageNode->m_flags.setFlag(flag: QSSGRenderImage::Flag::Dirty);
1298
1299 return imageNode;
1300}
1301
1302void QQuick3DTexture::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
1303{
1304 QQuick3DObject::itemChange(change, value);
1305 if (change == QQuick3DObject::ItemChange::ItemSceneChange) {
1306 // Source item
1307 if (m_sourceItem) {
1308 disconnect(m_sceneManagerWindowChangeConnection);
1309
1310 if (m_sceneManagerForLayer) {
1311 m_sceneManagerForLayer->qsgDynamicTextures.removeOne(t: m_layer);
1312 m_sceneManagerForLayer = nullptr;
1313 }
1314 trySetSourceParent();
1315 const auto &sceneManager = value.sceneManager;
1316 Q_ASSERT(QQuick3DObjectPrivate::get(this)->sceneManager == sceneManager);
1317 if (m_layer) {
1318 if (sceneManager)
1319 sceneManager->qsgDynamicTextures << m_layer;
1320 m_sceneManagerForLayer = sceneManager;
1321 }
1322
1323 // If m_sourceItem was an inline declared item (very common, e.g.
1324 // Texture { sourceItem: Rectangle { ... } } then it is highly
1325 // likely it won't be associated with a window (Qt Quick scene)
1326 // yet. Associate with one as soon as possible, do not leave it to
1327 // updateSpatialNode, because that, while safe, would defer
1328 // rendering into the texture to a future frame (adding a 2 frame
1329 // lag for the first rendering of the mesh textured with the 2D
1330 // item content), since a refWindow needs to be followed by a
1331 // scenegraph sync round to get QSGNodes created (updatePaintNode),
1332 // whereas updateSpatialNode is in the middle of a sync round, so
1333 // would need to wait for another one, etc.
1334 if (sceneManager && m_sourceItem && !m_sourceItem->window()) {
1335 if (sceneManager->window()) {
1336 QQuickItemPrivate::get(item: m_sourceItem)->refWindow(sceneManager->window());
1337 } else {
1338 m_sceneManagerWindowChangeConnection = connect(sender: sceneManager, signal: &QQuick3DSceneManager::windowChanged, context: this,
1339 slot: [this, sceneManager]
1340 {
1341 if (m_sourceItem && !m_sourceItem->window() && sceneManager->window())
1342 QQuickItemPrivate::get(item: m_sourceItem)->refWindow(sceneManager->window());
1343 });
1344 }
1345 }
1346 }
1347 // TextureData
1348 if (m_textureData) {
1349 const auto &sceneManager = value.sceneManager;
1350 if (sceneManager)
1351 QQuick3DObjectPrivate::refSceneManager(obj: m_textureData, mgr&: *sceneManager);
1352 else
1353 QQuick3DObjectPrivate::derefSceneManager(obj: m_textureData);
1354 }
1355 }
1356}
1357
1358void QQuick3DTexture::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &geometry)
1359{
1360 Q_ASSERT(item == m_sourceItem);
1361 Q_UNUSED(item);
1362 Q_UNUSED(geometry);
1363 if (change.sizeChange()) {
1364 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty);
1365 update();
1366 }
1367}
1368
1369void QQuick3DTexture::sourceItemDestroyed(QObject *item)
1370{
1371 Q_ASSERT(item == m_sourceItem);
1372 Q_UNUSED(item);
1373
1374 m_sourceItem = nullptr;
1375
1376 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceDirty);
1377 m_dirtyFlags.setFlag(flag: DirtyFlag::SourceItemDirty);
1378 m_dirtyFlags.setFlag(flag: DirtyFlag::TextureDataDirty);
1379 emit sourceItemChanged();
1380 update();
1381}
1382
1383void QQuick3DTexture::markDirty(QQuick3DTexture::DirtyFlag type)
1384{
1385 if (!m_dirtyFlags.testFlag(flag: type)) {
1386 m_dirtyFlags.setFlag(flag: type, on: true);
1387 update();
1388 }
1389}
1390
1391QSSGRenderImage *QQuick3DTexture::getRenderImage()
1392{
1393 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: this);
1394 return static_cast<QSSGRenderImage *>(p->spatialNode);
1395}
1396
1397void QQuick3DTexture::markAllDirty()
1398{
1399 m_dirtyFlags = DirtyFlags(0xFFFF);
1400 QQuick3DObject::markAllDirty();
1401}
1402
1403QT_END_NAMESPACE
1404

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