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