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