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

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