1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Quick 3D.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "qquick3dtexture_p.h"
31 #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
32 #include <QtQml/QQmlFile>
33 #include <QtQuick/QQuickItem>
34 #include <QtQuick/private/qquickitem_p.h>
35 #include <QtCore/qmath.h>
36 
37 #include "qquick3dobject_p.h"
38 #include "qquick3dscenemanager_p.h"
39 #include "qquick3dutils_p.h"
40 
41 QT_BEGIN_NAMESPACE
42 
43 /*!
44     \qmltype Texture
45     \inherits Object3D
46     \inqmlmodule QtQuick3D
47     \brief Defines a texture for use in 3D scenes.
48 
49     Texture defines an image and how it is mapped to meshes in a 3d scene.
50 
51     Texture components can use image data either from a file using the
52     \l source property, or a Qt Quick item using the sourceItem property.
53 */
54 
QQuick3DTexture(QQuick3DObject * parent)55 QQuick3DTexture::QQuick3DTexture(QQuick3DObject *parent)
56     : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Image)), parent) {}
57 
~QQuick3DTexture()58 QQuick3DTexture::~QQuick3DTexture()
59 {
60     if (m_layer && m_sceneManagerForLayer) {
61         m_sceneManagerForLayer->qsgDynamicTextures.removeAll(m_layer);
62         m_layer->deleteLater();
63     }
64 
65     if (m_sourceItem) {
66         QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
67         sourcePrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
68     }
69 }
70 
71 /*!
72     \qmlproperty url QtQuick3D::Texture::source
73 
74     This property holds the location of an image file containing the data used
75     by the texture.
76 
77     \sa sourceItem
78 */
source() const79 QUrl QQuick3DTexture::source() const
80 {
81     return m_source;
82 }
83 
84 /*!
85     \qmlproperty Item QtQuick3D::Texture::sourceItem
86 
87     This property defines a Item to be used as the source of the texture. Using
88     this property allows any 2D Qt Quick content to be used as a texture source
89     by renderind that item as an offscreen layer.
90 
91     If this property is used, then the value of \l source will be ignored.
92 
93     \note Currently there is no way to forward input events to the Item used as
94     a texture source.
95 
96     \sa source
97 */
sourceItem() const98 QQuickItem *QQuick3DTexture::sourceItem() const
99 {
100     return m_sourceItem;
101 }
102 
103 /*!
104     \qmlproperty float QtQuick3D::Texture::scaleU
105 
106     This property defines how to scale the U texture coordinate when mapping to
107     a mesh's UV coordinates.
108 
109     Scaling the U value when using horizontal tiling will define how many times the
110     texture is repeated from left to right.
111 
112     \sa tilingModeHorizontal
113  */
scaleU() const114 float QQuick3DTexture::scaleU() const
115 {
116     return m_scaleU;
117 }
118 
119 /*!
120     \qmlproperty float QtQuick3D::Texture::scaleV
121 
122     This property defines how to scale the V texture coordinate when mapping to
123     a mesh's UV coordinates.
124 
125     Scaling the V value when using vertical tiling will define how many times a
126     texture is repeated from bottom to top.
127 
128     \sa tilingModeVertical
129 */
scaleV() const130 float QQuick3DTexture::scaleV() const
131 {
132     return m_scaleV;
133 }
134 
135 /*!
136     \qmlproperty enumeration QtQuick3D::Texture::mappingMode
137 
138     This property defines which method of mapping to use when sampling this
139     texture.
140 
141     \value Texture.UV The default for diffuse and opacity maps,
142         this causes the image to be stuck to the mesh. The same portion of the
143         image will always appear on the same vertex (unless the UV properties are
144         animated).
145     \value Texture.Environment The default for specular reflection,
146         this causes the image to be ‘projected’ onto the material as though it is
147         being reflected. Using Environmental Mapping for diffuse maps provides a
148         mirror effect.
149     \value Texture.LightProbe The default for HDRI sphere maps used by light probes.
150 */
mappingMode() const151 QQuick3DTexture::MappingMode QQuick3DTexture::mappingMode() const
152 {
153     return m_mappingMode;
154 }
155 
156 /*!
157     \qmlproperty enumeration QtQuick3D::Texture::tilingModeHorizontal
158 
159     Controls how the texture is mapped when the U scaling value is greater than 1.
160 
161     \value Texture.ClampToEdge Texture is not tiled, but the value on the edge is used instead.
162     \value Texture.MirroredRepeat Texture is repeated and mirrored over the X axis.
163     \value Texture.Repeat Texture is repeated over the X axis.
164 
165     \sa scaleU
166 */
horizontalTiling() const167 QQuick3DTexture::TilingMode QQuick3DTexture::horizontalTiling() const
168 {
169     return m_tilingModeHorizontal;
170 }
171 
172 /*!
173     \qmlproperty enumeration QtQuick3D::Texture::tilingModeVertical
174 
175     This property controls how the texture is mapped when the V scaling value
176     is greater than 1.
177 
178     \value Texture.ClampToEdge Texture is not tiled, but the value on the edge is used instead.
179     \value Texture.MirroredRepeat Texture is repeated and mirrored over the Y axis.
180     \value Texture.Repeat Texture is repeated over the Y axis.
181 
182     \sa scaleV
183 */
verticalTiling() const184 QQuick3DTexture::TilingMode QQuick3DTexture::verticalTiling() const
185 {
186     return m_tilingModeVertical;
187 }
188 
189 /*!
190     \qmlproperty float QtQuick3D::Texture::rotationUV
191 
192     This property rotates the texture around the pivot point. This is defined
193     using euler angles and for a positve value rotation is clockwise.
194 
195     \sa pivotU, pivotV
196 */
rotationUV() const197 float QQuick3DTexture::rotationUV() const
198 {
199     return m_rotationUV;
200 }
201 
202 /*!
203     \qmlproperty float QtQuick3D::Texture::positionU
204 
205     This property offsets the U coordinate mapping from left to right.
206 */
positionU() const207 float QQuick3DTexture::positionU() const
208 {
209     return m_positionU;
210 }
211 
212 /*!
213     \qmlproperty float QtQuick3D::Texture::positionV
214 
215     This property offsets the V coordinate mapping from bottom to top.
216 */
positionV() const217 float QQuick3DTexture::positionV() const
218 {
219     return m_positionV;
220 }
221 
222 /*!
223     \qmlproperty float QtQuick3D::Texture::pivotU
224 
225     This property sets the pivot U position.
226 
227     \sa rotationUV
228 */
pivotU() const229 float QQuick3DTexture::pivotU() const
230 {
231     return m_pivotU;
232 }
233 
234 /*!
235     \qmlproperty float QtQuick3D::Texture::pivotV
236 
237     This property sets the pivot V position.
238 
239     \sa rotationUV
240 */
pivotV() const241 float QQuick3DTexture::pivotV() const
242 {
243     return m_pivotV;
244 }
245 
246 /*!
247     \qmlproperty bool QtQuick3D::Texture::flipV
248 
249     This property sets the use of the vertically flipped coordinates.
250 */
flipV() const251 bool QQuick3DTexture::flipV() const
252 {
253     return m_flipV;
254 }
255 
256 /*!
257     \qmlproperty enumeration QtQuick3D::Texture::format
258 
259     This property controls the color format of the texture assigned in \l source property.
260 
261     By default, it is automatically determined.
262     However, it can be manually set if the automatic format is not what is wanted.
263 
264     \value Texture.Automatic The color format will be automatically determined (default).
265     \value Texture.R8 The color format is considered as 8-bit integer in R channel.
266     \value Texture.R16 The color format is considered as 16-bit integer in R channel.
267     \value Texture.R16F The color format is considered as 16-bit float in R channel.
268     \value Texture.R32I The color format is considered as 32-bit integer in R channel.
269     \value Texture.R32UI The color format is considered as 32-bit unsigned integer in R channel.
270     \value Texture.R32F The color format is considered as 32-bit float R channel.
271     \value Texture.RG8 The color format is considered as 8-bit integer in R and G channels.
272     \value Texture.RGBA8 The color format is considered as 8-bit integer in R, G, B and alpha channels.
273     \value Texture.RGB8 The color format is considered as 8-bit integer in R, G and B channels.
274     \value Texture.SRGB8 The color format is considered as 8-bit integer in R, G and B channels in standard RGB color space.
275     \value Texture.SRGB8A8 The color format is considered as 8-bit integer in R, G, B and alpha channels in standard RGB color space.
276     \value Texture.RGB565 The color format is considered as 5-bit integer in R and B channels and 6-bit integer in G channel.
277     \value Texture.RGBA5551 The color format is considered as 5-bit integer in R, G, B channels and boolean alpha channel.
278     \value Texture.Alpha8 The color format is considered as 8-bit alpha map.
279     \value Texture.Luminance8 The color format is considered as 8-bit luminance map.
280     \value Texture.Luminance16 The color format is considered as 16-bit luminance map.
281     \value Texture.LuminanceAlpha8 The color format is considered as 8-bit luminance and alpha map.
282     \value Texture.RGBA16F The color format is considered as 16-bit float in R,G,B and alpha channels.
283     \value Texture.RG16F The color format is considered as 16-bit float in R and G channels.
284     \value Texture.RG32F The color format is considered as 32-bit float in R and G channels.
285     \value Texture.RGB32F The color format is considered as 32-bit float in R, G and B channels.
286     \value Texture.RGBA32F The color format is considered as 32-bit float in R, G, B and alpha channels.
287     \value Texture.R11G11B10 The color format is considered as 11-bit integer in R and G channels and 10-bit integer in B channel.
288     \value Texture.RGB9E5 The color format is considered as 9-bit mantissa in R, G and B channels and 5-bit shared exponent.
289     \value Texture.RGBA_DXT1 The color format is considered as DXT1 compressed format with R, G, B and alpha channels.
290     \value Texture.RGB_DXT1 The color format is considered as DXT1 compressed format with R, G and B channels.
291     \value Texture.RGBA_DXT3 The color format is considered as DXT3 compressed format with R, G, B and alpha channels.
292     \value Texture.RGBA_DXT5 The color format is considered as DXT5 compressed format with R, G, B and alpha channels.
293     \value Texture.Depth16 The color format is considered as 16-bit depth map.
294     \value Texture.Depth24 The color format is considered as 24-bit depth map.
295     \value Texture.Depth32 The color format is considered as 32-bit depth map.
296     \value Texture.Depth24Stencil8 The color format is considered as 24-bit depth and 8-bit stencil map.
297 */
format() const298 QQuick3DTexture::Format QQuick3DTexture::format() const
299 {
300     return m_format;
301 }
302 
setSource(const QUrl & source)303 void QQuick3DTexture::setSource(const QUrl &source)
304 {
305     if (m_source == source)
306         return;
307 
308     m_source = source;
309     m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
310     m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
311     emit sourceChanged();
312     update();
313 }
314 
trySetSourceParent()315 void QQuick3DTexture::trySetSourceParent()
316 {
317     if (m_sourceItem->parentItem() && m_sourceItemRefed)
318         return;
319 
320     auto *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
321 
322     if (!m_sourceItem->parentItem()) {
323         if (const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager) {
324             if (auto *window = manager->window()) {
325                 if (m_sourceItemRefed) {
326                     // Item was already refed but probably with hide set to false...
327                     // so we need to deref before we ref again below.
328                     const bool hide = m_sourceItemReparented;
329                     sourcePrivate->derefFromEffectItem(hide);
330                     m_sourceItemRefed = false;
331                 }
332 
333                 m_sourceItem->setParentItem(window->contentItem());
334                 m_sourceItemReparented = true;
335                 update();
336             }
337         }
338     }
339 
340     if (!m_sourceItemRefed) {
341         const bool hide = m_sourceItemReparented;
342         sourcePrivate->refFromEffectItem(hide);
343     }
344 }
345 
setSourceItem(QQuickItem * sourceItem)346 void QQuick3DTexture::setSourceItem(QQuickItem *sourceItem)
347 {
348     if (m_sourceItem == sourceItem)
349         return;
350 
351     disconnect(m_textureProviderConnection);
352     disconnect(m_textureUpdateConnection);
353 
354     if (m_sourceItem) {
355         QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
356 
357         const bool hide = m_sourceItemReparented;
358         sourcePrivate->derefFromEffectItem(hide);
359         m_sourceItemRefed = false;
360 
361         sourcePrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
362         disconnect(m_sourceItem, SIGNAL(destroyed(QObject*)), this, SLOT(sourceItemDestroyed(QObject*)));
363         if (m_sourceItemReparented) {
364             m_sourceItem->setParentItem(nullptr);
365             m_sourceItemReparented = false;
366         }
367     }
368 
369     m_sourceItem = sourceItem;
370 
371     if (sourceItem) {
372         trySetSourceParent();
373         QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
374         sourcePrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
375         connect(m_sourceItem, SIGNAL(destroyed(QObject*)), this, SLOT(sourceItemDestroyed(QObject*)));
376     }
377 
378     if (m_layer) {
379         const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager;
380         manager->qsgDynamicTextures.removeAll(m_layer);
381         m_sceneManagerForLayer = nullptr;
382         // cannot touch m_layer here
383     }
384     m_initializedSourceItem = nullptr;
385     m_initializedSourceItemSize = QSize();
386 
387     m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
388     m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
389     emit sourceItemChanged();
390     update();
391 }
392 
setScaleU(float scaleU)393 void QQuick3DTexture::setScaleU(float scaleU)
394 {
395     if (qFuzzyCompare(m_scaleU, scaleU))
396         return;
397 
398     m_scaleU = scaleU;
399     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
400     emit scaleUChanged();
401     update();
402 }
403 
setScaleV(float scaleV)404 void QQuick3DTexture::setScaleV(float scaleV)
405 {
406     if (qFuzzyCompare(m_scaleV, scaleV))
407         return;
408 
409     m_scaleV = scaleV;
410     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
411     emit scaleVChanged();
412     update();
413 }
414 
setMappingMode(QQuick3DTexture::MappingMode mappingMode)415 void QQuick3DTexture::setMappingMode(QQuick3DTexture::MappingMode mappingMode)
416 {
417     if (m_mappingMode == mappingMode)
418         return;
419 
420     m_mappingMode = mappingMode;
421     emit mappingModeChanged();
422     update();
423 }
424 
setHorizontalTiling(QQuick3DTexture::TilingMode tilingModeHorizontal)425 void QQuick3DTexture::setHorizontalTiling(QQuick3DTexture::TilingMode tilingModeHorizontal)
426 {
427     if (m_tilingModeHorizontal == tilingModeHorizontal)
428         return;
429 
430     m_tilingModeHorizontal = tilingModeHorizontal;
431     emit horizontalTilingChanged();
432     update();
433 }
434 
setVerticalTiling(QQuick3DTexture::TilingMode tilingModeVertical)435 void QQuick3DTexture::setVerticalTiling(QQuick3DTexture::TilingMode tilingModeVertical)
436 {
437     if (m_tilingModeVertical == tilingModeVertical)
438         return;
439 
440     m_tilingModeVertical = tilingModeVertical;
441     emit verticalTilingChanged();
442     update();
443 }
444 
setRotationUV(float rotationUV)445 void QQuick3DTexture::setRotationUV(float rotationUV)
446 {
447     if (qFuzzyCompare(m_rotationUV, rotationUV))
448         return;
449 
450     m_rotationUV = rotationUV;
451     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
452     emit rotationUVChanged();
453     update();
454 }
455 
setPositionU(float positionU)456 void QQuick3DTexture::setPositionU(float positionU)
457 {
458     if (qFuzzyCompare(m_positionU, positionU))
459         return;
460 
461     m_positionU = positionU;
462     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
463     emit positionUChanged();
464     update();
465 }
466 
setPositionV(float positionV)467 void QQuick3DTexture::setPositionV(float positionV)
468 {
469     if (qFuzzyCompare(m_positionV, positionV))
470         return;
471 
472     m_positionV = positionV;
473     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
474     emit positionVChanged();
475     update();
476 }
477 
setPivotU(float pivotU)478 void QQuick3DTexture::setPivotU(float pivotU)
479 {
480     if (qFuzzyCompare(m_pivotU, pivotU))
481         return;
482 
483     m_pivotU = pivotU;
484     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
485     emit pivotUChanged();
486     update();
487 }
488 
setPivotV(float pivotV)489 void QQuick3DTexture::setPivotV(float pivotV)
490 {
491     if (qFuzzyCompare(m_pivotV, pivotV))
492         return;
493 
494     m_pivotV = pivotV;
495     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
496     emit pivotVChanged();
497     update();
498 }
499 
setFlipV(bool flipV)500 void QQuick3DTexture::setFlipV(bool flipV)
501 {
502     if (m_flipV == flipV)
503         return;
504 
505     m_flipV = flipV;
506     m_dirtyFlags.setFlag(DirtyFlag::TransformDirty);
507     emit flipVChanged();
508     update();
509 }
510 
setFormat(QQuick3DTexture::Format format)511 void QQuick3DTexture::setFormat(QQuick3DTexture::Format format)
512 {
513     if (m_format == format)
514         return;
515 
516     m_format = format;
517     emit formatChanged();
518     update();
519 }
520 
updateSpatialNode(QSSGRenderGraphObject * node)521 QSSGRenderGraphObject *QQuick3DTexture::updateSpatialNode(QSSGRenderGraphObject *node)
522 {
523     if (!node) {
524         markAllDirty();
525         node = new QSSGRenderImage();
526     }
527 
528     auto imageNode = static_cast<QSSGRenderImage *>(node);
529 
530     if (m_dirtyFlags.testFlag(DirtyFlag::TransformDirty)) {
531         m_dirtyFlags.setFlag(DirtyFlag::TransformDirty, false);
532         imageNode->m_flipV = m_sourceItem ? !m_flipV : m_flipV;
533         imageNode->m_scale = QVector2D(m_scaleU, m_scaleV);
534         imageNode->m_pivot = QVector2D(m_pivotU, m_pivotV);
535         imageNode->m_rotation = m_rotationUV;
536         imageNode->m_position = QVector2D(m_positionU, m_positionV);
537 
538         imageNode->m_flags.setFlag(QSSGRenderImage::Flag::TransformDirty);
539     }
540 
541     bool nodeChanged = false;
542     if (m_dirtyFlags.testFlag(DirtyFlag::SourceDirty)) {
543         m_dirtyFlags.setFlag(DirtyFlag::SourceDirty, false);
544         imageNode->m_imagePath = QQmlFile::urlToLocalFileOrQrc(m_source);
545         nodeChanged = true;
546     }
547 
548     nodeChanged |= qUpdateIfNeeded(imageNode->m_mappingMode,
549                                   QSSGRenderImage::MappingModes(m_mappingMode));
550     nodeChanged |= qUpdateIfNeeded(imageNode->m_horizontalTilingMode,
551                                   QSSGRenderTextureCoordOp(m_tilingModeHorizontal));
552     nodeChanged |= qUpdateIfNeeded(imageNode->m_verticalTilingMode,
553                                   QSSGRenderTextureCoordOp(m_tilingModeVertical));
554     QSSGRenderTextureFormat format{QSSGRenderTextureFormat::Format(m_format)};
555     nodeChanged |= qUpdateIfNeeded(imageNode->m_format, format);
556 
557     if (m_dirtyFlags.testFlag(DirtyFlag::SourceItemDirty)) {
558         m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty, false);
559         if (m_sourceItem) {
560             QQuickWindow *window = m_sourceItem->window();
561             // If it was an inline declared item (very common, e.g. Texture {
562             // sourceItem: Rectangle { ... } } then it is likely it won't be
563             // associated with a window (Qt Quick scene) unless we help it to
564             // one via refWindow. However, this here is only the last resort,
565             // ideally there is a refWindow upon ItemSceneChange already.
566              if (!window) {
567                 window = QQuick3DObjectPrivate::get(this)->sceneManager->window();
568                 if (window)
569                     QQuickItemPrivate::get(m_sourceItem)->refWindow(window);
570                 else
571                     qWarning("Unable to get window, this will probably not work");
572             }
573 
574             // This assumes that the QSGTextureProvider returned never changes,
575             // which is hopefully the case for both Image and Item layers.
576             if (QSGTextureProvider *provider = m_sourceItem->textureProvider()) {
577                 imageNode->m_qsgTexture = provider->texture();
578 
579                 disconnect(m_textureProviderConnection);
580                 m_textureProviderConnection = connect(provider, &QSGTextureProvider::textureChanged, this, [provider, imageNode] () {
581                     // called on the render thread, if there is one; while not
582                     // obvious, the gui thread is blocked too because one can
583                     // get here only from either the textureProvider() call
584                     // above, or from QQuickImage::updatePaintNode()
585                     imageNode->m_qsgTexture = provider->texture();
586                     // the QSGTexture may be different now, go through loadRenderImage() again
587                     imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
588                 }, Qt::DirectConnection);
589 
590                 disconnect(m_textureUpdateConnection);
591                 auto *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
592                 if (sourcePrivate->window) {
593                     QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
594 
595                     // Why after, not beforeSynchronizing? Consider the case of an Item layer:
596                     // if the View3D gets to sync (updatePaintNode) first, doing an
597                     // updateTexture() is futile, the QSGLayer is not yet initialized (not
598                     // associated with an Item, has no size, etc.). That happens only once the
599                     // underlying QQuickShaderEffectSource hits its updatePaintNode. And that
600                     // may well happen happen only after the View3D has finished with its sync
601                     // step. By connecting to afterSynchronizing, we still get a chance to
602                     // trigger a layer texture update and so have a QSGTexture with real
603                     // content ready by the time the View3D prepares/renders the 3D scene upon
604                     // the scenegraph's preprocess step (Offscreen) or before/after the
605                     // scenegraph rendering (if Underlay/Overlay).
606                     //
607                     // This eliminates, or in the worst case reduces, the ugly effects of not
608                     // having a texture ready when rendering the 3D scene.
609 
610                     m_textureUpdateConnection = connect(sourcePrivate->window, &QQuickWindow::afterSynchronizing, this, [this, imageNode, sourceItem]() {
611                         // Called on the render thread with gui blocked (if there is a render thread, that is).
612                         if (m_sourceItem != sourceItem) {
613                             disconnect(m_textureProviderConnection);
614                             disconnect(m_textureUpdateConnection);
615                             return;
616                         }
617                         if (QSGDynamicTexture *t = qobject_cast<QSGDynamicTexture *>(imageNode->m_qsgTexture)) {
618                             if (t->updateTexture())
619                                 update(); // safe because the gui thread is blocked
620                         }
621                     }, Qt::DirectConnection);
622                 } else {
623                     qWarning("No window for item, texture updates are doomed");
624                 }
625 
626                 if (m_layer) {
627                     delete m_layer;
628                     m_layer = nullptr;
629                 }
630             } else {
631                 // Not a texture provider, so not an Image or an Item with
632                 // layer.enabled: true, create our own QSGLayer.
633                 if (m_initializedSourceItem != m_sourceItem || m_initializedSourceItemSize != m_sourceItem->size()) {
634                     // If there was a previous sourceItem and m_layer is valid
635                     // then set its content to null until we get to
636                     // afterSynchronizing, otherwise things can blow up.
637                     if (m_layer)
638                         m_layer->setItem(nullptr);
639 
640                     m_initializedSourceItem = m_sourceItem;
641                     m_initializedSourceItemSize = m_sourceItem->size();
642 
643                     // The earliest next point where we can do anything is
644                     // after the scenegraph's QQuickItem sync round has completed.
645                     connect(window, &QQuickWindow::afterSynchronizing, this, [this, imageNode, window]() {
646                         // Called on the render thread with gui blocked (if there is a render thread, that is).
647                         disconnect(window, &QQuickWindow::afterSynchronizing, this, nullptr);
648                         if (m_layer) {
649                             const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager;
650                             manager->qsgDynamicTextures.removeAll(m_layer);
651                             delete m_layer;
652                             m_layer = nullptr;
653                         }
654 
655                         QQuickItemPrivate *sourcePrivate = QQuickItemPrivate::get(m_sourceItem);
656                         QSGRenderContext *rc = sourcePrivate->sceneGraphRenderContext();
657                         Q_ASSERT(QThread::currentThread() == rc->thread()); // must be on the render thread
658                         QSGLayer *layer = rc->sceneGraphContext()->createLayer(rc);
659                         connect(sourcePrivate->window, SIGNAL(sceneGraphInvalidated()), layer, SLOT(invalidated()), Qt::DirectConnection);
660 
661                         auto manager = QQuick3DObjectPrivate::get(this)->sceneManager;
662                         manager->qsgDynamicTextures << layer;
663                         m_sceneManagerForLayer = manager;
664 
665                         connect(layer, &QObject::destroyed, manager.data(), [manager, layer]()
666                         {
667                             // this is on the render thread so all borked threading-wise (all data here is gui thread stuff...) but will survive
668                             manager->qsgDynamicTextures.removeAll(layer);
669                         }, Qt::DirectConnection);
670 
671                         QQuickItem *sourceItem = m_sourceItem; // for capturing, recognizing in the lambda that m_sourceItem has changed is essential
672                         connect(layer, &QObject::destroyed, this, [this, sourceItem]()
673                         {
674                             // just as dubious as the previous connection
675                             if (m_initializedSourceItem == sourceItem) {
676                                 m_sceneManagerForLayer = nullptr;
677                                 m_initializedSourceItem = nullptr;
678                             }
679                         }, Qt::DirectConnection);
680 
681                         // With every frame (even when QQuickWindow isn't dirty so doesn't render),
682                         // try to update the texture. If updateTexture() returns false, content hasn't changed.
683                         // This complements qsgDynamicTextures and QQuick3DViewport::updateDynamicTextures().
684                         m_textureUpdateConnection = connect(sourcePrivate->window, &QQuickWindow::beforeSynchronizing,
685                                                             this, [this, sourceItem]()
686                         {
687                             // Called on the render thread with gui blocked (if there is a render thread, that is).
688                             if (!m_layer)
689                                 return;
690                             if (m_sourceItem != sourceItem) {
691                                 disconnect(m_textureUpdateConnection);
692                                 return;
693                             }
694                             if (m_layer->updateTexture())
695                                 update();
696                         }, Qt::DirectConnection);
697 
698                         m_layer = layer;
699                         m_layer->setItem(QQuickItemPrivate::get(m_sourceItem)->itemNode());
700 
701                         QRectF sourceRect = QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height());
702                         if (qFuzzyIsNull(sourceRect.width()))
703                             sourceRect.setWidth(256);
704                         if (qFuzzyIsNull(sourceRect.height()))
705                             sourceRect.setHeight(256);
706                         m_layer->setRect(sourceRect);
707 
708                         QSize textureSize(qCeil(qAbs(sourceRect.width())), qCeil(qAbs(sourceRect.height())));
709                         const QSize minTextureSize = sourcePrivate->sceneGraphContext()->minimumFBOSize();
710                         while (textureSize.width() < minTextureSize.width())
711                             textureSize.rwidth() *= 2;
712                         while (textureSize.height() < minTextureSize.height())
713                             textureSize.rheight() *= 2;
714                         m_layer->setSize(textureSize);
715 
716                         // now that the layer has an item and a size, it can render into the texture
717                         m_layer->updateTexture();
718 
719                         imageNode->m_qsgTexture = m_layer;
720                         imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
721                     }, Qt::DirectConnection);
722                 }
723             }
724             if (imageNode->m_flipV != !m_flipV) {
725                 imageNode->m_flipV = !m_flipV;
726                 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::TransformDirty);
727             }
728         } else {
729             if (m_layer) {
730                 m_layer->setItem(nullptr);
731                 delete m_layer;
732                 m_layer = nullptr;
733             }
734             imageNode->m_qsgTexture = nullptr;
735             if (imageNode->m_flipV != m_flipV) {
736                 imageNode->m_flipV = m_flipV;
737                 imageNode->m_flags.setFlag(QSSGRenderImage::Flag::TransformDirty);
738             }
739         }
740         nodeChanged = true;
741     }
742 
743     if (nodeChanged)
744         imageNode->m_flags.setFlag(QSSGRenderImage::Flag::Dirty);
745 
746     return imageNode;
747 }
748 
itemChange(QQuick3DObject::ItemChange change,const QQuick3DObject::ItemChangeData & value)749 void QQuick3DTexture::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
750 {
751     QQuick3DObject::itemChange(change, value);
752     if (change == QQuick3DObject::ItemChange::ItemSceneChange) {
753         if (m_sourceItem) {
754             disconnect(m_sceneManagerWindowChangeConnection);
755 
756             if (m_sceneManagerForLayer) {
757                 m_sceneManagerForLayer->qsgDynamicTextures.removeOne(m_layer);
758                 m_sceneManagerForLayer = nullptr;
759             }
760             trySetSourceParent();
761             const auto &sceneManager = value.sceneManager;
762             Q_ASSERT(QQuick3DObjectPrivate::get(this)->sceneManager == sceneManager);
763             if (m_layer) {
764                 if (sceneManager)
765                     sceneManager->qsgDynamicTextures << m_layer;
766                 m_sceneManagerForLayer = sceneManager;
767             }
768 
769             // If m_sourceItem was an inline declared item (very common, e.g.
770             // Texture { sourceItem: Rectangle { ... } } then it is highly
771             // likely it won't be associated with a window (Qt Quick scene)
772             // yet. Associate with one as soon as possible, do not leave it to
773             // updateSpatialNode, because that, while safe, would defer
774             // rendering into the texture to a future frame (adding a 2 frame
775             // lag for the first rendering of the mesh textured with the 2D
776             // item content), since a refWindow needs to be followed by a
777             // scenegraph sync round to get QSGNodes created (updatePaintNode),
778             // whereas updateSpatialNode is in the middle of a sync round, so
779             // would need to wait for another one, etc.
780             if (sceneManager && m_sourceItem && !m_sourceItem->window()) {
781                 if (sceneManager->window()) {
782                     QQuickItemPrivate::get(m_sourceItem)->refWindow(sceneManager->window());
783                 } else {
784                     m_sceneManagerWindowChangeConnection = connect(sceneManager.data(), &QQuick3DSceneManager::windowChanged, this,
785                                                                    [this, sceneManager]
786                     {
787                         if (m_sourceItem && !m_sourceItem->window() && sceneManager->window())
788                             QQuickItemPrivate::get(m_sourceItem)->refWindow(sceneManager->window());
789                     });
790                 }
791             }
792         }
793     }
794 }
795 
itemGeometryChanged(QQuickItem * item,QQuickGeometryChange change,const QRectF & geometry)796 void QQuick3DTexture::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &geometry)
797 {
798     Q_ASSERT(item == m_sourceItem);
799     Q_UNUSED(item)
800     Q_UNUSED(geometry)
801     if (change.sizeChange()) {
802         auto renderImage = getRenderImage();
803         if (renderImage)
804             renderImage->m_flags.setFlag(QSSGRenderImage::Flag::ItemSizeDirty);
805         m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
806         update();
807     }
808 }
809 
sourceItemDestroyed(QObject * item)810 void QQuick3DTexture::sourceItemDestroyed(QObject *item)
811 {
812     Q_ASSERT(item == m_sourceItem);
813     Q_UNUSED(item);
814 
815     m_sourceItem = nullptr;
816 
817     m_dirtyFlags.setFlag(DirtyFlag::SourceDirty);
818     m_dirtyFlags.setFlag(DirtyFlag::SourceItemDirty);
819     emit sourceItemChanged();
820     update();
821 }
822 
getRenderImage()823 QSSGRenderImage *QQuick3DTexture::getRenderImage()
824 {
825     QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(this);
826     return static_cast<QSSGRenderImage *>(p->spatialNode);
827 }
828 
markAllDirty()829 void QQuick3DTexture::markAllDirty()
830 {
831     m_dirtyFlags = DirtyFlags(DirtyFlag::TransformDirty) | DirtyFlags(DirtyFlag::SourceDirty) | DirtyFlags(DirtyFlag::SourceItemDirty);
832     QQuick3DObject::markAllDirty();
833 }
834 
835 QT_END_NAMESPACE
836