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