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