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 disconnect(m_sourceItem, &QQuickItem::windowChanged, this, &QQuick3DTexture::sourceItemWindowChanged);
730 if (m_sourceItemReparented) {
731 m_sourceItem->setParentItem(nullptr);
732 m_sourceItemReparented = false;
733 }
734 }
735
736 m_sourceItem = sourceItem;
737
738 if (sourceItem) {
739 trySetSourceParent();
740 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
741 sourcePrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
742 connect(m_sourceItem, SIGNAL(destroyed(QObject*)), this, SLOT(sourceItemDestroyed(QObject*)));
743 connect(m_sourceItem, &QQuickItem::windowChanged, this, &QQuick3DTexture::sourceItemWindowChanged);
744 sourcePrivate->ensureSubsceneDeliveryAgent();
745 }
746
747 if (m_layer) {
748 const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager;
749 manager->qsgDynamicTextures.removeAll(m_layer);
750 m_sceneManagerForLayer = nullptr;
751 // cannot touch m_layer here
752 }
753 m_initializedSourceItem = nullptr;
754 m_initializedSourceItemSize = QSize();
755
756 m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
757 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
758 m_dirtyFlags.setFlag(DirtyFlag::TextureDataDirty);
759 emit sourceItemChanged();
760 update();
761}
762
763void QQuick3DTexture::setScaleU(float scaleU)
764{
765 if (qFuzzyCompare(m_scaleU, scaleU))
766 return;
767
768 m_scaleU = scaleU;
769 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
770 emit scaleUChanged();
771 update();
772}
773
774void QQuick3DTexture::setScaleV(float scaleV)
775{
776 if (qFuzzyCompare(m_scaleV, scaleV))
777 return;
778
779 m_scaleV = scaleV;
780 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
781 emit scaleVChanged();
782 update();
783}
784
785void QQuick3DTexture::setMappingMode(QQuick3DTexture::MappingMode mappingMode)
786{
787 if (m_mappingMode == mappingMode)
788 return;
789
790 m_mappingMode = mappingMode;
791 emit mappingModeChanged();
792 update();
793}
794
795void QQuick3DTexture::setHorizontalTiling(QQuick3DTexture::TilingMode tilingModeHorizontal)
796{
797 if (m_tilingModeHorizontal == tilingModeHorizontal)
798 return;
799
800 m_tilingModeHorizontal = tilingModeHorizontal;
801 emit horizontalTilingChanged();
802 update();
803}
804
805void QQuick3DTexture::setVerticalTiling(QQuick3DTexture::TilingMode tilingModeVertical)
806{
807 if (m_tilingModeVertical == tilingModeVertical)
808 return;
809
810 m_tilingModeVertical = tilingModeVertical;
811 emit verticalTilingChanged();
812 update();
813}
814
815void QQuick3DTexture::setDepthTiling(QQuick3DTexture::TilingMode tilingModeDepth)
816{
817 if (m_tilingModeDepth == tilingModeDepth)
818 return;
819 m_tilingModeDepth = tilingModeDepth;
820 emit depthTilingChanged();
821 update();
822}
823
824void QQuick3DTexture::setRotationUV(float rotationUV)
825{
826 if (qFuzzyCompare(m_rotationUV, rotationUV))
827 return;
828
829 m_rotationUV = rotationUV;
830 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
831 emit rotationUVChanged();
832 update();
833}
834
835void QQuick3DTexture::setPositionU(float positionU)
836{
837 if (qFuzzyCompare(m_positionU, positionU))
838 return;
839
840 m_positionU = positionU;
841 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
842 emit positionUChanged();
843 update();
844}
845
846void QQuick3DTexture::setPositionV(float positionV)
847{
848 if (qFuzzyCompare(m_positionV, positionV))
849 return;
850
851 m_positionV = positionV;
852 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
853 emit positionVChanged();
854 update();
855}
856
857void QQuick3DTexture::setPivotU(float pivotU)
858{
859 if (qFuzzyCompare(m_pivotU, pivotU))
860 return;
861
862 m_pivotU = pivotU;
863 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
864 emit pivotUChanged();
865 update();
866}
867
868void QQuick3DTexture::setPivotV(float pivotV)
869{
870 if (qFuzzyCompare(m_pivotV, pivotV))
871 return;
872
873 m_pivotV = pivotV;
874 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
875 emit pivotVChanged();
876 update();
877}
878
879void QQuick3DTexture::setFlipU(bool flipU)
880{
881 if (m_flipU == flipU)
882 return;
883
884 m_flipU = flipU;
885 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
886 emit flipUChanged();
887 update();
888}
889
890void QQuick3DTexture::setFlipV(bool flipV)
891{
892 if (m_flipV == flipV)
893 return;
894
895 m_flipV = flipV;
896 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty);
897 emit flipVChanged();
898 update();
899}
900
901void QQuick3DTexture::setIndexUV(int indexUV)
902{
903 if (m_indexUV == indexUV)
904 return;
905
906 if (indexUV < 0)
907 m_indexUV = 0;
908 else if (indexUV > 1)
909 m_indexUV = 1;
910 else
911 m_indexUV = indexUV;
912
913 m_dirtyFlags.setFlag(DirtyFlag::IndexUVDirty);
914 emit indexUVChanged();
915 update();
916}
917
918void QQuick3DTexture::setTextureData(QQuick3DTextureData *textureData)
919{
920 if (m_textureData == textureData)
921 return;
922
923 // Make sure to disconnect if the geometry gets deleted out from under us
924 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DTexture::setTextureData, textureData, m_textureData);
925
926 if (m_textureData)
927 QObject::disconnect(m_textureDataConnection);
928 m_textureData = textureData;
929
930 if (m_textureData) {
931 m_textureDataConnection
932 = QObject::connect(m_textureData, &QQuick3DTextureData::textureDataNodeDirty, this, [this]() {
933 markDirty(DirtyFlag::TextureDataDirty);
934 });
935 }
936
937 m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
938 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
939 m_dirtyFlags.setFlag(DirtyFlag::TextureDataDirty);
940 emit textureDataChanged();
941 update();
942}
943
944void QQuick3DTexture::setGenerateMipmaps(bool generateMipmaps)
945{
946 if (m_generateMipmaps == generateMipmaps)
947 return;
948
949 m_generateMipmaps = generateMipmaps;
950 m_dirtyFlags.setFlag(DirtyFlag::SamplerDirty);
951 emit generateMipmapsChanged();
952 update();
953}
954
955void QQuick3DTexture::setAutoOrientation(bool autoOrientation)
956{
957 if (m_autoOrientation == autoOrientation)
958 return;
959
960 m_autoOrientation = autoOrientation;
961 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty);
962 emit autoOrientationChanged();
963 update();
964}
965
966void QQuick3DTexture::setMagFilter(QQuick3DTexture::Filter magFilter)
967{
968 if (m_magFilter == magFilter)
969 return;
970
971 m_magFilter = magFilter;
972 m_dirtyFlags.setFlag(DirtyFlag::SamplerDirty);
973 emit magFilterChanged();
974 update();
975}
976
977void QQuick3DTexture::setMinFilter(QQuick3DTexture::Filter minFilter)
978{
979 if (m_minFilter == minFilter)
980 return;
981
982 m_minFilter = minFilter;
983 m_dirtyFlags.setFlag(DirtyFlag::SamplerDirty);
984 emit minFilterChanged();
985 update();
986}
987
988void QQuick3DTexture::setMipFilter(QQuick3DTexture::Filter mipFilter)
989{
990 if (m_mipFilter == mipFilter)
991 return;
992
993 m_mipFilter = mipFilter;
994 m_dirtyFlags.setFlag(DirtyFlag::SamplerDirty);
995 emit mipFilterChanged();
996 update();
997}
998
999// this function may involve file system access and hence can be expensive
1000bool QQuick3DTexture::effectiveFlipV(const QSSGRenderImage &imageNode) const
1001{
1002 // No magic when autoOrientation is false.
1003 if (!m_autoOrientation)
1004 return m_flipV;
1005
1006 // Keep the same order as in QSSGBufferManager: sourceItem > textureData > source
1007
1008 // Using sourceItem implies inverting (the effective, internal) flipV,
1009 // transparently to the user. Otherwise two #Rectangle models textured with
1010 // two Textures where one has its content loaded from an image file via
1011 // QImage while the other is generated by Qt Quick rendering into the
1012 // texture would appear upside-down relative to each other, and that
1013 // discrepancy is not ideal. (that said, this won't help CustomMaterial, as
1014 // documented for flipV and co.)
1015
1016 if (m_sourceItem)
1017 return !m_flipV;
1018
1019 // With textureData and renderExtension we assume the application knows what it is doing,
1020 // because there the application is controlling the content itself.
1021
1022 if (m_textureData || m_renderExtension)
1023 return m_flipV;
1024
1025 // Compressed textures (or any texture that is coming from the associated
1026 // container formats, such as KTX, i.e. not via QImage but through
1027 // QTextureFileReader) get the implicit flip, like sourceItem. This is done
1028 // mainly for parity with Qt Quick's Image, see QTBUG-93972.
1029
1030 if (!m_source.isEmpty()) {
1031 const QString filePath = imageNode.m_imagePath.path();
1032 if (!filePath.isEmpty()) {
1033 QSSGInputUtil::FileType fileType = QSSGInputUtil::UnknownFile;
1034 if (QSSGInputUtil::getStreamForTextureFile(filePath, true, nullptr, &fileType)) {
1035 if (fileType == QSSGInputUtil::TextureFile)
1036 return !m_flipV;
1037 }
1038 }
1039 }
1040
1041 return m_flipV;
1042}
1043
1044static QSSGRenderPath resolveImagePath(const QUrl &url, const QQmlContext *context)
1045{
1046 if (context && url.isRelative()) {
1047 QString path = url.path();
1048 QChar separator = QChar::fromLatin1(';');
1049 if (path.contains(separator)) {
1050 QString resolvedPath;
1051 const QStringList paths = path.split(separator);
1052 bool first = true;
1053 for (auto &s : paths) {
1054 auto mapped = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(s));
1055 if (!first)
1056 resolvedPath.append(separator);
1057 resolvedPath.append(mapped);
1058 first = false;
1059 }
1060 return QSSGRenderPath(resolvedPath);
1061 }
1062 }
1063 return QSSGRenderPath(QQmlFile::urlToLocalFileOrQrc(context ? context->resolvedUrl(url) : url));
1064}
1065
1066QSSGRenderGraphObject *QQuick3DTexture::updateSpatialNode(QSSGRenderGraphObject *node)
1067{
1068 if (!node) {
1069 markAllDirty();
1070 node = new QSSGRenderImage(QQuick3DObjectPrivate::get(this)->type);
1071 }
1072 QQuick3DObject::updateSpatialNode(node);
1073 auto imageNode = static_cast<QSSGRenderImage *>(node);
1074
1075 if (m_dirtyFlags.testFlag(DirtyFlag::TransformDirty)) {
1076 m_dirtyFlags.setFlag(DirtyFlag::TransformDirty, false);
1077
1078 // flipV and indexUV have their own dirty flags, handled separately below
1079 imageNode->m_flipU = m_flipU;
1080 imageNode->m_scale = QVector2D(m_scaleU, m_scaleV);
1081 imageNode->m_pivot = QVector2D(m_pivotU, m_pivotV);
1082 imageNode->m_rotation = m_rotationUV;
1083 imageNode->m_position = QVector2D(m_positionU, m_positionV);
1084
1085 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::TransformDirty);
1086 }
1087
1088 bool nodeChanged = false;
1089 if (m_dirtyFlags.testFlag(DirtyFlag::SourceDirty)) {
1090 m_dirtyFlags.setFlag(DirtyFlag::SourceDirty, false);
1091 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty, true);
1092 if (!m_source.isEmpty()) {
1093 const QQmlContext *context = qmlContext(this);
1094 imageNode->m_imagePath = resolveImagePath(m_source, context);
1095 } else {
1096 imageNode->m_imagePath = QSSGRenderPath();
1097 }
1098 nodeChanged = true;
1099 }
1100 if (m_dirtyFlags.testFlag(DirtyFlag::IndexUVDirty)) {
1101 m_dirtyFlags.setFlag(DirtyFlag::IndexUVDirty, false);
1102 imageNode->m_indexUV = m_indexUV;
1103 }
1104 nodeChanged |= qUpdateIfNeeded(imageNode->m_mappingMode,
1105 QSSGRenderImage::MappingModes(m_mappingMode));
1106 nodeChanged |= qUpdateIfNeeded(imageNode->m_horizontalTilingMode,
1107 QSSGRenderTextureCoordOp(m_tilingModeHorizontal));
1108 nodeChanged |= qUpdateIfNeeded(imageNode->m_verticalTilingMode,
1109 QSSGRenderTextureCoordOp(m_tilingModeVertical));
1110 nodeChanged |= qUpdateIfNeeded(imageNode->m_depthTilingMode,
1111 QSSGRenderTextureCoordOp(m_tilingModeDepth));
1112
1113 if (m_dirtyFlags.testFlag(DirtyFlag::SamplerDirty)) {
1114 m_dirtyFlags.setFlag(DirtyFlag::SamplerDirty, false);
1115 nodeChanged |= qUpdateIfNeeded(imageNode->m_minFilterType,
1116 QSSGRenderTextureFilterOp(m_minFilter));
1117 nodeChanged |= qUpdateIfNeeded(imageNode->m_magFilterType,
1118 QSSGRenderTextureFilterOp(m_magFilter));
1119 nodeChanged |= qUpdateIfNeeded(imageNode->m_mipFilterType,
1120 QSSGRenderTextureFilterOp(m_mipFilter));
1121 nodeChanged |= qUpdateIfNeeded(imageNode->m_generateMipmaps,
1122 m_generateMipmaps);
1123 }
1124
1125 if (m_dirtyFlags.testFlag(DirtyFlag::TextureDataDirty)) {
1126 m_dirtyFlags.setFlag(DirtyFlag::TextureDataDirty, false);
1127 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty, true);
1128 if (m_textureData)
1129 imageNode->m_rawTextureData = static_cast<QSSGRenderTextureData *>(QQuick3DObjectPrivate::get(m_textureData)->spatialNode);
1130 else
1131 imageNode->m_rawTextureData = nullptr;
1132 nodeChanged = true;
1133 }
1134
1135 if (m_dirtyFlags.testFlag(DirtyFlag::ExtensionDirty)) {
1136 bool extDirty = false;
1137 if (m_renderExtension) {
1138 auto *sn = QQuick3DObjectPrivate::get(m_renderExtension)->spatialNode;
1139 // NOTE: We don't clear if we haven't gotten the spatial node yet, as
1140 // we'll be called once _again_ when the extensions have been processed.
1141 extDirty = (sn == nullptr);
1142 if (sn && QSSG_GUARD(QSSGRenderGraphObject::isExtension(sn->type)))
1143 imageNode->m_extensionsSource = static_cast<QSSGRenderExtension *>(sn);
1144 }
1145
1146 m_dirtyFlags.setFlag(DirtyFlag::ExtensionDirty, extDirty);
1147 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty, true);
1148
1149 nodeChanged = true;
1150 }
1151
1152 if (m_dirtyFlags.testFlag(DirtyFlag::SourceItemDirty)) {
1153 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty, false);
1154 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty, true);
1155 if (m_sourceItem) {
1156 QQuickWindow *window = m_sourceItem->window();
1157 // If it was an inline declared item (very common, e.g. Texture {
1158 // sourceItem: Rectangle { ... } } then it is likely it won't be
1159 // associated with a window (Qt Quick scene) unless we help it to
1160 // one via refWindow. However, this here is only the last resort,
1161 // ideally there is a refWindow upon ItemSceneChange already.
1162 if (!window) {
1163 window = QQuick3DObjectPrivate::get(this)->sceneManager->window();
1164 if (window)
1165 QQuickItemPrivate::get(m_sourceItem)->refWindow(window);
1166 else
1167 qWarning("Unable to get window, this will probably not work");
1168 }
1169
1170 // Workaround: Due to limitation in how the texture provider works in View3D we can't use it in the
1171 // special case when a non-visible View3D is used as a sourceItem, at least not in its current incarnation.
1172 // There's also a drawback here, since we now have an extra indirection by rendering to the layer texture first..
1173 const bool isHiddenView3D = !m_sourceItem->isVisible() && (qobject_cast<QQuick3DViewport *>(m_sourceItem) != nullptr);
1174
1175 // This assumes that the QSGTextureProvider returned never changes,
1176 // which is hopefully the case for both Image and Item layers.
1177 if (QSGTextureProvider *provider = m_sourceItem->textureProvider(); provider != nullptr && !isHiddenView3D) {
1178 imageNode->m_qsgTexture = provider->texture();
1179
1180 disconnect(m_textureProviderConnection);
1181 m_textureProviderConnection = connect(provider, &QSGTextureProvider::textureChanged, this, [this, provider] () {
1182 // called on the render thread, if there is one; the gui
1183 // thread may or may not be blocked (e.g. if the source is
1184 // a View3D, that emits textureChanged() from preprocess,
1185 // so after sync, whereas an Image emits in
1186 // updatePaintNode() where gui is blocked)
1187 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(this)->spatialNode);
1188 if (!imageNode)
1189 return;
1190
1191 imageNode->m_qsgTexture = provider->texture();
1192 // the QSGTexture may be different now, go through loadRenderImage() again
1193 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
1194 // Call update() on the main thread - otherwise we could
1195 // end up in a situation where the 3D scene does not update
1196 // due to nothing else changing, even though the source
1197 // texture is now different.
1198 QMetaObject::invokeMethod(this, &QQuick3DTexture::update, Qt::AutoConnection);
1199 }, Qt::DirectConnection);
1200
1201 disconnect(m_textureUpdateConnection);
1202 auto *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
1203 if (sourcePrivate->window) {
1204 QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
1205
1206 // Why after, not beforeSynchronizing? Consider the case of an Item layer:
1207 // if the View3D gets to sync (updatePaintNode) first, doing an
1208 // updateTexture() is futile, the QSGLayer is not yet initialized (not
1209 // associated with an Item, has no size, etc.). That happens only once the
1210 // underlying QQuickShaderEffectSource hits its updatePaintNode. And that
1211 // may well happen happen only after the View3D has finished with its sync
1212 // step. By connecting to afterSynchronizing, we still get a chance to
1213 // trigger a layer texture update and so have a QSGTexture with real
1214 // content ready by the time the View3D prepares/renders the 3D scene upon
1215 // the scenegraph's preprocess step (Offscreen) or before/after the
1216 // scenegraph rendering (if Underlay/Overlay).
1217 //
1218 // This eliminates, or in the worst case reduces, the ugly effects of not
1219 // having a texture ready when rendering the 3D scene.
1220
1221 m_textureUpdateConnection = connect(sourcePrivate->window, &QQuickWindow::afterSynchronizing, this, [this, sourceItem]() {
1222 // Called on the render thread with gui blocked (if there is a render thread, that is).
1223 if (m_sourceItem != sourceItem) {
1224 disconnect(m_textureProviderConnection);
1225 disconnect(m_textureUpdateConnection);
1226 return;
1227 }
1228 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(this)->spatialNode);
1229 if (!imageNode)
1230 return;
1231
1232 if (QSGDynamicTexture *t = qobject_cast<QSGDynamicTexture *>(imageNode->m_qsgTexture)) {
1233 if (t->updateTexture())
1234 update(); // safe because the gui thread is blocked
1235 }
1236 }, Qt::DirectConnection);
1237 } else {
1238 qWarning("No window for item, texture updates are doomed");
1239 }
1240
1241 if (m_layer) {
1242 delete m_layer;
1243 m_layer = nullptr;
1244 }
1245 } else {
1246 // Not a texture provider, so not an Image or an Item with
1247 // layer.enabled: true, create our own QSGLayer.
1248 if (m_initializedSourceItem != m_sourceItem || m_initializedSourceItemSize != m_sourceItem->size()) {
1249 // If there was a previous sourceItem and m_layer is valid
1250 // then set its content to null until we get to
1251 // afterSynchronizing, otherwise things can blow up.
1252 if (m_layer)
1253 m_layer->setItem(nullptr);
1254
1255 m_initializedSourceItem = m_sourceItem;
1256 m_initializedSourceItemSize = m_sourceItem->size();
1257
1258 // The earliest next point where we can do anything is
1259 // after the scenegraph's QQuickItem sync round has completed.
1260 connect(window, &QQuickWindow::afterSynchronizing, this, [this, window]() {
1261 auto imageNode = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(this)->spatialNode);
1262 if (!imageNode)
1263 return;
1264
1265 // Called on the render thread with gui blocked (if there is a render thread, that is).
1266 disconnect(window, &QQuickWindow::afterSynchronizing, this, nullptr);
1267 if (m_layer) {
1268 const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager;
1269 manager->qsgDynamicTextures.removeAll(m_layer);
1270 delete m_layer;
1271 m_layer = nullptr;
1272 }
1273
1274 QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
1275 QSGRenderContext *rc = sourcePrivate->sceneGraphRenderContext();
1276 Q_ASSERT(QThread::currentThread() == rc->thread()); // must be on the render thread
1277 QSGLayer *layer = rc->sceneGraphContext()->createLayer(rc);
1278 connect(sourcePrivate->window, SIGNAL(sceneGraphInvalidated()), layer, SLOT(invalidated()), Qt::DirectConnection);
1279
1280 QQuick3DSceneManager *manager = QQuick3DObjectPrivate::get(this)->sceneManager;
1281 manager->qsgDynamicTextures << layer;
1282 m_sceneManagerForLayer = manager;
1283
1284 connect(layer, &QObject::destroyed, manager, [manager, layer]()
1285 {
1286 // this is on the render thread so all borked threading-wise (all data here is gui thread stuff...) but will survive
1287 manager->qsgDynamicTextures.removeAll(layer);
1288 }, Qt::DirectConnection);
1289
1290 QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
1291 connect(layer, &QObject::destroyed, this, [this, sourceItem]()
1292 {
1293 // just as dubious as the previous connection
1294 if (m_initializedSourceItem == sourceItem) {
1295 m_sceneManagerForLayer = nullptr;
1296 m_initializedSourceItem = nullptr;
1297 }
1298 }, Qt::DirectConnection);
1299
1300 // With every frame try to update the texture. Use
1301 // afterSynchronizing like in the other branch. (why
1302 // after: a property changing something in the 2D
1303 // subtree leading to updates in the content will only
1304 // be "visible" after the (2D item) sync, not before)
1305 //
1306 // If updateTexture() returns false, content hasn't
1307 // changed. This complements qsgDynamicTextures and
1308 // QQuick3DViewport::updateDynamicTextures().
1309 m_textureUpdateConnection = connect(sourcePrivate->window, &QQuickWindow::afterSynchronizing,
1310 this, [this, sourceItem]()
1311 {
1312 // Called on the render thread with gui blocked (if there is a render thread, that is).
1313 if (!m_layer)
1314 return;
1315 if (m_sourceItem != sourceItem) {
1316 disconnect(m_textureUpdateConnection);
1317 return;
1318 }
1319 if (m_layer->updateTexture())
1320 update();
1321 }, Qt::DirectConnection);
1322
1323 m_layer = layer;
1324 m_layer->setItem(QQuickItemPrivate::get(m_sourceItem)->itemNode());
1325
1326 QRectF sourceRect = QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height());
1327 if (qFuzzyIsNull(sourceRect.width()))
1328 sourceRect.setWidth(256);
1329 if (qFuzzyIsNull(sourceRect.height()))
1330 sourceRect.setHeight(256);
1331 m_layer->setRect(sourceRect);
1332
1333 QSize textureSize(qCeil(qAbs(sourceRect.width())), qCeil(qAbs(sourceRect.height())));
1334 const QSize minTextureSize = sourcePrivate->sceneGraphContext()->minimumFBOSize();
1335 while (textureSize.width() < minTextureSize.width())
1336 textureSize.rwidth() *= 2;
1337 while (textureSize.height() < minTextureSize.height())
1338 textureSize.rheight() *= 2;
1339 m_layer->setSize(textureSize);
1340
1341 // now that the layer has an item and a size, it can render into the texture
1342 m_layer->updateTexture();
1343
1344 imageNode->m_qsgTexture = m_layer;
1345 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
1346 }, Qt::DirectConnection);
1347 }
1348 }
1349 } else {
1350 if (m_layer) {
1351 m_layer->setItem(nullptr);
1352 delete m_layer;
1353 m_layer = nullptr;
1354 }
1355 imageNode->m_qsgTexture = nullptr;
1356 }
1357 nodeChanged = true;
1358 }
1359
1360 if (m_dirtyFlags.testFlag(DirtyFlag::FlipVDirty)) {
1361 m_dirtyFlags.setFlag(DirtyFlag::FlipVDirty, false);
1362 imageNode->m_flipV = effectiveFlipV(*imageNode);
1363 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::TransformDirty);
1364 }
1365
1366 if (nodeChanged)
1367 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
1368
1369 return imageNode;
1370}
1371
1372void QQuick3DTexture::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
1373{
1374 QQuick3DObject::itemChange(change, value);
1375 if (change == QQuick3DObject::ItemChange::ItemSceneChange) {
1376 // Source item
1377 if (m_sourceItem) {
1378 disconnect(m_sceneManagerWindowChangeConnection);
1379
1380 if (m_sceneManagerForLayer) {
1381 m_sceneManagerForLayer->qsgDynamicTextures.removeOne(m_layer);
1382 m_sceneManagerForLayer = nullptr;
1383 }
1384 trySetSourceParent();
1385 const auto &sceneManager = value.sceneManager;
1386 Q_ASSERT(QQuick3DObjectPrivate::get(this)->sceneManager == sceneManager);
1387 if (m_layer) {
1388 if (sceneManager)
1389 sceneManager->qsgDynamicTextures << m_layer;
1390 m_sceneManagerForLayer = sceneManager;
1391 }
1392
1393 // If m_sourceItem was an inline declared item (very common, e.g.
1394 // Texture { sourceItem: Rectangle { ... } } then it is highly
1395 // likely it won't be associated with a window (Qt Quick scene)
1396 // yet. Associate with one as soon as possible, do not leave it to
1397 // updateSpatialNode, because that, while safe, would defer
1398 // rendering into the texture to a future frame (adding a 2 frame
1399 // lag for the first rendering of the mesh textured with the 2D
1400 // item content), since a refWindow needs to be followed by a
1401 // scenegraph sync round to get QSGNodes created (updatePaintNode),
1402 // whereas updateSpatialNode is in the middle of a sync round, so
1403 // would need to wait for another one, etc.
1404 if (sceneManager && m_sourceItem && !m_sourceItem->window()) {
1405 if (sceneManager->window()) {
1406 QQuickItemPrivate::get(m_sourceItem)->refWindow(sceneManager->window());
1407 } else {
1408 m_sceneManagerWindowChangeConnection = connect(sceneManager, &QQuick3DSceneManager::windowChanged, this,
1409 [this, sceneManager]
1410 {
1411 if (m_sourceItem && !m_sourceItem->window() && sceneManager->window())
1412 QQuickItemPrivate::get(m_sourceItem)->refWindow(sceneManager->window());
1413 });
1414 }
1415 }
1416 }
1417 // TextureData
1418 if (m_textureData) {
1419 const auto &sceneManager = value.sceneManager;
1420 if (sceneManager)
1421 QQuick3DObjectPrivate::refSceneManager(m_textureData, *sceneManager);
1422 else
1423 QQuick3DObjectPrivate::derefSceneManager(m_textureData);
1424 }
1425
1426 // Texture Providers
1427 if (m_renderExtension) {
1428 const auto &sceneManager = value.sceneManager;
1429 if (sceneManager)
1430 QQuick3DObjectPrivate::refSceneManager(m_renderExtension, *sceneManager);
1431 else
1432 QQuick3DObjectPrivate::derefSceneManager(m_renderExtension);
1433 }
1434 }
1435}
1436
1437void QQuick3DTexture::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &geometry)
1438{
1439 Q_ASSERT(item == m_sourceItem);
1440 Q_UNUSED(item);
1441 Q_UNUSED(geometry);
1442 if (change.sizeChange()) {
1443 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
1444 update();
1445 }
1446}
1447
1448void QQuick3DTexture::sourceItemDestroyed(QObject *item)
1449{
1450 Q_ASSERT(item == m_sourceItem);
1451 Q_UNUSED(item);
1452
1453 m_sourceItem = nullptr;
1454
1455 m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
1456 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
1457 m_dirtyFlags.setFlag(DirtyFlag::TextureDataDirty);
1458 emit sourceItemChanged();
1459 update();
1460}
1461
1462void QQuick3DTexture::sourceItemWindowChanged(QQuickWindow *window)
1463{
1464 Q_UNUSED(window);
1465 if (m_layer != nullptr) {
1466 // During teardown the sourceItem may lose its window first after
1467 // the 3D scene has been torn down, so we need to make sure we still
1468 // have a valid sceneManager to remove the layer from.
1469 if (const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager) {
1470 manager->qsgDynamicTextures.removeAll(m_layer);
1471 delete m_layer;
1472 m_layer = nullptr;
1473 update();
1474 }
1475 } else {
1476 markAllDirty();
1477 update();
1478 }
1479}
1480
1481void QQuick3DTexture::markDirty(QQuick3DTexture::DirtyFlag type)
1482{
1483 if (!m_dirtyFlags.testFlag(type)) {
1484 m_dirtyFlags.setFlag(type, true);
1485 update();
1486 }
1487}
1488
1489QSSGRenderImage *QQuick3DTexture::getRenderImage()
1490{
1491 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(this);
1492 return static_cast<QSSGRenderImage *>(p->spatialNode);
1493}
1494
1495void QQuick3DTexture::markAllDirty()
1496{
1497 m_dirtyFlags = DirtyFlags(0xFFFF);
1498 QQuick3DObject::markAllDirty();
1499}
1500
1501void QQuick3DTexture::setTextureProvider(QQuick3DRenderExtension *textureProvider)
1502{
1503 if (m_renderExtension == textureProvider)
1504 return;
1505
1506 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DTexture::setTextureProvider, textureProvider, m_renderExtension);
1507
1508 m_renderExtension = textureProvider;
1509
1510 m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
1511 m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
1512 m_dirtyFlags.setFlag(DirtyFlag::TextureDataDirty);
1513 m_dirtyFlags.setFlag(DirtyFlag::ExtensionDirty);
1514
1515 emit textureProviderChanged();
1516 update();
1517}
1518
1519QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QSSGRenderPath resolveImagePath(const QUrl &url, const QQmlContext *context)