aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <[email protected]>2024-05-13 13:15:57 +0200
committerEskil Abrahamsen Blomfeldt <[email protected]>2024-05-27 17:36:45 +0200
commitb70dc1196c87c3d4b8d02c189a54b166d6d6146d (patch)
treeffd477944953567aa8518c2bd731170ebda37ba7
parent5deb57432fb139d3147c7932cecb715d239a4e08 (diff)
Texture fill for shapes
This introduces a "fillItem" property to ShapePath. Similar to gradient, this enables filling a shape with any texture provider item, such as an image, a ShaderEffectSource or a layer-enabled item. Fixes: QTBUG-104121 Change-Id: I8748a90c825e8eb4655a4ac90648c6ae74420527 Reviewed-by: Eirik Aavitsland <[email protected]>
-rw-r--r--examples/quick/quickshapes/shapes/CMakeLists.txt1
-rw-r--r--examples/quick/quickshapes/shapes/fillItem.qml50
-rw-r--r--examples/quick/quickshapes/shapes/shapegallery.qml4
-rw-r--r--src/quick/CMakeLists.txt37
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode.cpp1
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.cpp141
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.h62
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p_p.h13
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.frag11
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.vert11
-rw-r--r--src/quickshapes/CMakeLists.txt2
-rw-r--r--src/quickshapes/qquickshape.cpp91
-rw-r--r--src/quickshapes/qquickshape_p.h6
-rw-r--r--src/quickshapes/qquickshape_p_p.h10
-rw-r--r--src/quickshapes/qquickshapecurverenderer.cpp46
-rw-r--r--src/quickshapes/qquickshapecurverenderer_p.h5
-rw-r--r--src/quickshapes/qquickshapegenericrenderer.cpp241
-rw-r--r--src/quickshapes/qquickshapegenericrenderer_p.h69
-rw-r--r--src/quickshapes/qquickshapesoftwarerenderer.cpp12
-rw-r--r--src/quickshapes/qquickshapesoftwarerenderer_p.h2
-rw-r--r--src/quickshapes/shaders_ng/texturefill.frag25
-rw-r--r--src/quickshapes/shaders_ng/texturefill.vert31
-rw-r--r--tests/baseline/scenegraph/data/shape/shape_fillItem.qml177
23 files changed, 986 insertions, 62 deletions
diff --git a/examples/quick/quickshapes/shapes/CMakeLists.txt b/examples/quick/quickshapes/shapes/CMakeLists.txt
index 32a0723926..4fcb118d81 100644
--- a/examples/quick/quickshapes/shapes/CMakeLists.txt
+++ b/examples/quick/quickshapes/shapes/CMakeLists.txt
@@ -54,6 +54,7 @@ qt_add_qml_module(shapesexample
"zoomtiger.qml"
"fillTransform.qml"
"rectangle.qml"
+ "fillItem.qml"
)
install(TARGETS shapesexample
diff --git a/examples/quick/quickshapes/shapes/fillItem.qml b/examples/quick/quickshapes/shapes/fillItem.qml
new file mode 100644
index 0000000000..84fef37eeb
--- /dev/null
+++ b/examples/quick/quickshapes/shapes/fillItem.qml
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Shapes
+import shared
+
+Rectangle {
+ color: "lightGray"
+ width: 256
+ height: 256
+
+ Image {
+ id: image
+ source: Images.qtLogo
+ visible: false
+ }
+
+ Shape {
+ id: shape
+ anchors.centerIn: parent
+ width: 200
+ height: 100
+
+ ShapePath {
+ strokeColor: "black"
+ strokeWidth: 1
+ fillItem: image
+ fillTransform: PlanarTransform.fromScale(0.5, 0.5)
+ startX: shape.width / 2 - 40
+ startY: shape.height / 2 - 40
+
+ PathArc {
+ x: shape.width / 2 + 40
+ y: shape.height / 2 + 40
+ radiusX: 40
+ radiusY: 40
+ useLargeArc: true
+ }
+
+ PathArc {
+ x: shape.width / 2 - 40
+ y: shape.height / 2 - 40
+ radiusX: 40
+ radiusY: 40
+ useLargeArc: true
+ }
+ }
+ }
+}
diff --git a/examples/quick/quickshapes/shapes/shapegallery.qml b/examples/quick/quickshapes/shapes/shapegallery.qml
index ece85c38f5..2e79e4515b 100644
--- a/examples/quick/quickshapes/shapes/shapegallery.qml
+++ b/examples/quick/quickshapes/shapes/shapegallery.qml
@@ -154,6 +154,10 @@ Rectangle {
name: qsTr("Shape Rectangle")
shapeUrl: "rectangle.qml"
}
+ ListElement {
+ name: qsTr("Fill item")
+ shapeUrl: "fillItem.qml"
+ }
}
}
}
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index e8b9345293..9e95ad7eec 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -464,6 +464,43 @@ qt_internal_add_shaders(Quick "scenegraph_curve_shaders_cg_derivatives"
"scenegraph/shaders_ng/shapecurve_cg_derivatives.vert.qsb"
)
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_tf"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "TEXTUREFILL"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_tf.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_tf.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_tf_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "TEXTUREFILL"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_tf_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_tf_derivatives.vert.qsb"
+)
+
qt_internal_extend_target(Quick CONDITION QT_FEATURE_qml_network
LIBRARIES
Qt::Network
diff --git a/src/quick/scenegraph/qsgcurvefillnode.cpp b/src/quick/scenegraph/qsgcurvefillnode.cpp
index 9fa526bb0a..0a4a42341e 100644
--- a/src/quick/scenegraph/qsgcurvefillnode.cpp
+++ b/src/quick/scenegraph/qsgcurvefillnode.cpp
@@ -9,6 +9,7 @@ QT_BEGIN_NAMESPACE
QSGCurveFillNode::QSGCurveFillNode()
{
setFlag(OwnsGeometry, true);
+ setFlag(UsePreprocess, true);
setGeometry(new QSGGeometry(attributes(), 0, 0));
updateMaterial();
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.cpp b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
index 5b8fa7484a..331620cf94 100644
--- a/src/quick/scenegraph/qsgcurvefillnode_p.cpp
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
@@ -6,6 +6,7 @@
#include "util/qsggradientcache_p.h"
#include <private/qsgtexture_p.h>
+#include <private/qsgplaintexture_p.h>
QT_BEGIN_NAMESPACE
@@ -15,6 +16,7 @@ namespace {
{
public:
QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
bool useDerivatives,
int viewCount);
@@ -24,6 +26,7 @@ namespace {
};
QSGCurveFillMaterialShader::QSGCurveFillMaterialShader(QGradient::Type gradientType,
+ bool useTextureFill,
bool useDerivatives,
int viewCount)
{
@@ -35,6 +38,8 @@ namespace {
baseName += QStringLiteral("_rg");
} else if (gradientType == QGradient::ConicalGradient) {
baseName += QStringLiteral("_cg");
+ } else if (useTextureFill) {
+ baseName += QStringLiteral("_tf");
}
if (useDerivatives)
@@ -48,15 +53,50 @@ namespace {
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
Q_UNUSED(oldMaterial);
- const QSGCurveFillMaterial *m = static_cast<QSGCurveFillMaterial *>(newMaterial);
+ QSGCurveFillMaterial *m = static_cast<QSGCurveFillMaterial *>(newMaterial);
const QSGCurveFillNode *node = m->node();
- if (binding != 1 || node->gradientType() == QGradient::NoGradient)
+ if (binding != 1
+ || (node->gradientType() == QGradient::NoGradient && node->fillTextureProvider() == nullptr)) {
return;
+ }
+
+ QSGTexture *t = nullptr;
+ if (node->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCacheKey cacheKey(node->fillGradient()->stops,
+ node->fillGradient()->spread);
+ t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ } else if (node->fillTextureProvider() != nullptr) {
+ t = node->fillTextureProvider()->texture();
+ if (t != nullptr && t->isAtlasTexture()) {
+ // Create a non-atlas copy to make texture coordinate wrapping work. This
+ // texture copy is owned by the QSGTexture so memory is managed with the original
+ // texture provider.
+ QSGTexture *newTexture = t->removedFromAtlas(state.resourceUpdateBatch());
+ if (newTexture != nullptr)
+ t = newTexture;
+ }
+
+ }
+
+ if (t != nullptr) {
+ t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ } else {
+ if (m->dummyTexture() == nullptr) {
+ QSGPlainTexture *dummyTexture = new QSGPlainTexture;
+ dummyTexture->setFiltering(QSGTexture::Nearest);
+ dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
+ dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ dummyTexture->setImage(img);
+ dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+
+ m->setDummyTexture(dummyTexture);
+ }
+
+ t = m->dummyTexture();
+ }
- const QSGGradientCacheKey cacheKey(node->fillGradient()->stops,
- node->fillGradient()->spread);
- QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
- t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = t;
}
@@ -105,7 +145,8 @@ namespace {
}
offset += 8;
- if (newNode->gradientType() == QGradient::NoGradient) {
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() == nullptr) {
Q_ASSERT(buf->size() >= offset + 16);
QVector4D newColor = QVector4D(newNode->color().redF(),
@@ -136,7 +177,26 @@ namespace {
offset += 64;
}
- if (newNode->gradientType() == QGradient::LinearGradient) {
+ if (newNode->gradientType() == QGradient::NoGradient
+ && newNode->fillTextureProvider() != nullptr) {
+ Q_ASSERT(buf->size() >= offset + 8);
+ const QSizeF newTextureSize = newNode->fillTextureProvider()->texture() != nullptr
+ ? newNode->fillTextureProvider()->texture()->textureSize()
+ : QSizeF(0, 0);
+ const QVector2D newBoundsSize(newTextureSize.width() / state.devicePixelRatio(),
+ newTextureSize.height() / state.devicePixelRatio());
+ const QVector2D oldBoundsSize = oldNode != nullptr
+ ? oldNode->boundsSize()
+ : QVector2D{};
+
+ if (oldEffect == nullptr || newBoundsSize != oldBoundsSize) {
+ newNode->setBoundsSize(newBoundsSize);
+ memcpy(buf->data() + offset, &newBoundsSize, 8);
+ changed = true;
+ }
+ offset += 8;
+
+ } else if (newNode->gradientType() == QGradient::LinearGradient) {
Q_ASSERT(buf->size() >= offset + 8 + 8);
QVector2D newGradientStart = QVector2D(newNode->fillGradient()->a);
@@ -244,6 +304,11 @@ QSGCurveFillMaterial::QSGCurveFillMaterial(QSGCurveFillNode *node)
setFlag(RequiresDeterminant, true);
}
+QSGCurveFillMaterial::~QSGCurveFillMaterial()
+{
+ delete m_dummyTexture;
+}
+
int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
{
if (other->type() != type())
@@ -257,7 +322,7 @@ int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
if (a == b)
return 0;
- if (a->gradientType() == QGradient::NoGradient) {
+ if (a->gradientType() == QGradient::NoGradient && a->fillTextureProvider() == nullptr) {
if (int d = a->color().red() - b->color().red())
return d;
if (int d = a->color().green() - b->color().green())
@@ -267,55 +332,63 @@ int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
if (int d = a->color().alpha() - b->color().alpha())
return d;
} else {
- const QSGGradientCache::GradientDesc &ga = *a->fillGradient();
- const QSGGradientCache::GradientDesc &gb = *b->fillGradient();
+ if (a->gradientType() != QGradient::NoGradient) {
+ const QSGGradientCache::GradientDesc &ga = *a->fillGradient();
+ const QSGGradientCache::GradientDesc &gb = *b->fillGradient();
- if (int d = ga.a.x() - gb.a.x())
- return d;
- if (int d = ga.a.y() - gb.a.y())
- return d;
- if (int d = ga.b.x() - gb.b.x())
- return d;
- if (int d = ga.b.y() - gb.b.y())
- return d;
-
- if (int d = ga.v0 - gb.v0)
- return d;
- if (int d = ga.v1 - gb.v1)
- return d;
-
- if (int d = ga.spread - gb.spread)
- return d;
+ if (int d = ga.a.x() - gb.a.x())
+ return d;
+ if (int d = ga.a.y() - gb.a.y())
+ return d;
+ if (int d = ga.b.x() - gb.b.x())
+ return d;
+ if (int d = ga.b.y() - gb.b.y())
+ return d;
- if (int d = ga.stops.size() - gb.stops.size())
- return d;
+ if (int d = ga.v0 - gb.v0)
+ return d;
+ if (int d = ga.v1 - gb.v1)
+ return d;
- for (int i = 0; i < ga.stops.size(); ++i) {
- if (int d = ga.stops[i].first - gb.stops[i].first)
+ if (int d = ga.spread - gb.spread)
return d;
- if (int d = ga.stops[i].second.rgba() - gb.stops[i].second.rgba())
+
+ if (int d = ga.stops.size() - gb.stops.size())
return d;
+
+ for (int i = 0; i < ga.stops.size(); ++i) {
+ if (int d = ga.stops[i].first - gb.stops[i].first)
+ return d;
+ if (int d = ga.stops[i].second.rgba() - gb.stops[i].second.rgba())
+ return d;
+ }
}
if (int d = a->fillTransform()->compareTo(*b->fillTransform()))
return d;
}
- return 0;
+ const qintptr diff = qintptr(a->fillTextureProvider()) - qintptr(b->fillTextureProvider());
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
}
QSGMaterialType *QSGCurveFillMaterial::type() const
{
- static QSGMaterialType type[4];
+ static QSGMaterialType type[5];
uint index = node()->gradientType();
Q_ASSERT((index & ~3) == 0); // Only two first bits for gradient type
+ if (node()->gradientType() == QGradient::NoGradient && node()->fillTextureProvider() != nullptr)
+ index = 5;
+
return &type[index];
}
QSGMaterialShader *QSGCurveFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
return new QSGCurveFillMaterialShader(node()->gradientType(),
+ node()->gradientType() == QGradient::NoGradient
+ && node()->fillTextureProvider() != nullptr,
renderMode == QSGRendererInterface::RenderMode3D,
viewCount());
}
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p.h b/src/quick/scenegraph/qsgcurvefillnode_p.h
index 8a4180ccfe..5d0c02f5d9 100644
--- a/src/quick/scenegraph/qsgcurvefillnode_p.h
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.h
@@ -10,6 +10,7 @@
#include <QtQuick/private/qsggradientcache_p.h>
#include <QtQuick/private/qsgtransform_p.h>
#include <QtQuick/qsgnode.h>
+#include <QtQuick/qsgtextureprovider.h>
#include "qsgcurveabstractnode_p.h"
@@ -26,8 +27,11 @@
QT_BEGIN_NAMESPACE
-class Q_QUICK_EXPORT QSGCurveFillNode : public QSGCurveAbstractNode
+class QSGTextureProvider;
+
+class Q_QUICK_EXPORT QSGCurveFillNode : public QObject, public QSGCurveAbstractNode
{
+ Q_OBJECT
public:
QSGCurveFillNode();
@@ -44,6 +48,30 @@ public:
return m_color;
}
+ void setFillTextureProvider(QSGTextureProvider *provider)
+ {
+ if (m_textureProvider != nullptr) {
+ disconnect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ disconnect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+
+ m_textureProvider = provider;
+
+ if (m_textureProvider != nullptr) {
+ connect(m_textureProvider, &QSGTextureProvider::textureChanged,
+ this, &QSGCurveFillNode::handleTextureChanged);
+ connect(m_textureProvider, &QSGTextureProvider::destroyed,
+ this, &QSGCurveFillNode::handleTextureProviderDestroyed);
+ }
+ }
+
+ QSGTextureProvider *fillTextureProvider() const
+ {
+ return m_textureProvider;
+ }
+
void setFillGradient(const QSGGradientCache::GradientDesc &fillGradient)
{
m_fillGradient = fillGradient;
@@ -182,6 +210,36 @@ public:
m_uncookedVertexes.reserve(size);
}
+ void preprocess() override
+ {
+ if (m_textureProvider != nullptr) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(m_textureProvider->texture()))
+ texture->updateTexture();
+ }
+ }
+
+ QVector2D boundsSize() const
+ {
+ return m_boundsSize;
+ }
+
+ void setBoundsSize(const QVector2D &boundsSize)
+ {
+ m_boundsSize = boundsSize;
+ }
+
+private Q_SLOTS:
+ void handleTextureChanged()
+ {
+ markDirty(DirtyMaterial);
+ }
+
+ void handleTextureProviderDestroyed()
+ {
+ m_textureProvider = nullptr;
+ markDirty(DirtyMaterial);
+ }
+
private:
struct CurveNodeVertex
{
@@ -203,6 +261,8 @@ private:
QVector<CurveNodeVertex> m_uncookedVertexes;
QVector<quint32> m_uncookedIndexes;
+ QSGTextureProvider *m_textureProvider = nullptr;
+ QVector2D m_boundsSize;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurvefillnode_p_p.h b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
index f2a6535c6f..9cc80f3dca 100644
--- a/src/quick/scenegraph/qsgcurvefillnode_p_p.h
+++ b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
@@ -21,10 +21,12 @@
QT_BEGIN_NAMESPACE
class QSGCurveFillNode;
+class QSGPlainTexture;
class Q_QUICK_EXPORT QSGCurveFillMaterial : public QSGMaterial
{
public:
QSGCurveFillMaterial(QSGCurveFillNode *node);
+ ~QSGCurveFillMaterial() override;
int compare(const QSGMaterial *other) const override;
QSGCurveFillNode *node() const
@@ -32,11 +34,22 @@ public:
return m_node;
}
+ QSGPlainTexture *dummyTexture() const
+ {
+ return m_dummyTexture;
+ }
+
+ void setDummyTexture(QSGPlainTexture *dummyTexture)
+ {
+ m_dummyTexture = dummyTexture;
+ }
+
private:
QSGMaterialType *type() const override;
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
QSGCurveFillNode *m_node;
+ QSGPlainTexture *m_dummyTexture = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.frag b/src/quick/scenegraph/shaders_ng/shapecurve.frag
index f170ad780b..cc97f375ab 100644
--- a/src/quick/scenegraph/shaders_ng/shapecurve.frag
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.frag
@@ -10,9 +10,10 @@ layout(location = 1) in vec4 gradient;
layout(location = 2) in float gradTabIndex;
#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
layout(location = 2) in vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) in vec2 textureCoord;
#endif
-
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
@@ -26,7 +27,7 @@ layout(std140, binding = 0) uniform buf {
float debug;
float reserved3;
-#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
mat4 gradientMatrix;
#endif
#if defined(LINEARGRADIENT)
@@ -40,6 +41,8 @@ layout(std140, binding = 0) uniform buf {
#elif defined(CONICALGRADIENT)
vec2 translationPoint;
float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
#else
vec4 color;
#endif
@@ -49,6 +52,8 @@ layout(std140, binding = 0) uniform buf {
#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
layout(binding = 1) uniform sampler2D gradTabTexture;
+#elif defined(TEXTUREFILL)
+layout(binding = 1) uniform sampler2D sourceTexture;
#endif
vec4 baseColor()
@@ -77,6 +82,8 @@ vec4 baseColor()
else
t = (atan(-coord.y, coord.x) + ubuf.angle) * INVERSE_2PI;
return texture(gradTabTexture, vec2(t - floor(t), 0.5));
+#elif defined(TEXTUREFILL)
+ return texture(sourceTexture, textureCoord);
#else
return vec4(ubuf.color.rgb, 1.0) * ubuf.color.a;
#endif
diff --git a/src/quick/scenegraph/shaders_ng/shapecurve.vert b/src/quick/scenegraph/shaders_ng/shapecurve.vert
index ce6fa41f7b..3ff53978b8 100644
--- a/src/quick/scenegraph/shaders_ng/shapecurve.vert
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.vert
@@ -12,6 +12,8 @@ layout(location = 1) out vec4 gradient;
layout(location = 2) out float gradTabIndex;
#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
layout(location = 2) out vec2 coord;
+#elif defined(TEXTUREFILL)
+layout(location = 2) out vec2 textureCoord;
#endif
layout(std140, binding = 0) uniform buf {
@@ -25,7 +27,7 @@ layout(std140, binding = 0) uniform buf {
float debug;
float reserved3;
-#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
mat4 gradientMatrix;
#endif
#if defined(LINEARGRADIENT)
@@ -39,6 +41,8 @@ layout(std140, binding = 0) uniform buf {
#elif defined(CONICALGRADIENT)
vec2 translationPoint;
float angle;
+#elif defined(TEXTUREFILL)
+ vec2 boundsSize;
#else
vec4 color;
#endif
@@ -67,7 +71,7 @@ void main()
gradient = vertexGradient / ubuf.matrixScale;
-#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
+#if defined(LINEARGRADIENT) || defined(RADIALGRADIENT) || defined(CONICALGRADIENT) || defined(TEXTUREFILL)
vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
#endif
#if defined(LINEARGRADIENT)
@@ -75,6 +79,9 @@ void main()
gradTabIndex = dot(gradVec, gradVertexCoord - ubuf.gradientStart) / dot(gradVec, gradVec);
#elif defined(RADIALGRADIENT) || defined(CONICALGRADIENT)
coord = gradVertexCoord - ubuf.translationPoint;
+#elif defined(TEXTUREFILL)
+ textureCoord = vec2(gradVertexCoord.x / ubuf.boundsSize.x,
+ gradVertexCoord.y / ubuf.boundsSize.y);
#endif
#if QSHADER_VIEW_COUNT >= 2
diff --git a/src/quickshapes/CMakeLists.txt b/src/quickshapes/CMakeLists.txt
index 7b52826751..f9fbfb75e7 100644
--- a/src/quickshapes/CMakeLists.txt
+++ b/src/quickshapes/CMakeLists.txt
@@ -52,6 +52,8 @@ qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders"
"shaders_ng/radialgradient.frag"
"shaders_ng/conicalgradient.vert"
"shaders_ng/conicalgradient.frag"
+ "shaders_ng/texturefill.vert"
+ "shaders_ng/texturefill.frag"
"shaders_ng/wireframe.frag"
"shaders_ng/wireframe.vert"
)
diff --git a/src/quickshapes/qquickshape.cpp b/src/quickshapes/qquickshape.cpp
index 6505adcb39..df3baf539c 100644
--- a/src/quickshapes/qquickshape.cpp
+++ b/src/quickshapes/qquickshape.cpp
@@ -88,7 +88,8 @@ QQuickShapeStrokeFillParams::QQuickShapeStrokeFillParams()
capStyle(QQuickShapePath::SquareCap),
strokeStyle(QQuickShapePath::SolidLine),
dashOffset(0),
- fillGradient(nullptr)
+ fillGradient(nullptr),
+ fillItem(nullptr)
{
dashPattern << 4 << 2; // 4 * strokeWidth dash followed by 2 * strokeWidth space
}
@@ -240,6 +241,9 @@ void QQuickShapePath::setStrokeWidth(qreal w)
When set to \c transparent, no filling occurs.
The default value is \c white.
+
+ \note If either \l fillGradient or \l fillItem are set to something other than \c null, these
+ will take precedence over \c fillColor. The \c fillColor will be ignored in this case.
*/
QColor QQuickShapePath::fillColor() const
@@ -474,14 +478,14 @@ void QQuickShapePath::setDashPattern(const QVector<qreal> &array)
\qmlproperty ShapeGradient QtQuick.Shapes::ShapePath::fillGradient
This property defines the fill gradient. By default no gradient is enabled
- and the value is \c null. In this case the fill uses a solid color based
- on the value of ShapePath.fillColor.
-
- When set, ShapePath.fillColor is ignored and filling is done using one of
- the ShapeGradient subtypes.
+ and the value is \c null. In this case the fill will either be based on the \l fillItem
+ property if it is set, and otherwise the \l{fillColor} property will be used.
\note The Gradient type cannot be used here. Rather, prefer using one of
the advanced subtypes, like LinearGradient.
+
+ \note If set to something other than \c{null}, the \c fillGradient will take precedence over
+ both \l fillItem and \l fillColor.
*/
QQuickShapeGradient *QQuickShapePath::fillGradient() const
@@ -506,6 +510,11 @@ void QQuickShapePath::setFillGradient(QQuickShapeGradient *gradient)
}
}
+void QQuickShapePath::resetFillGradient()
+{
+ setFillGradient(nullptr);
+}
+
void QQuickShapePathPrivate::_q_fillGradientChanged()
{
Q_Q(QQuickShapePath);
@@ -513,9 +522,58 @@ void QQuickShapePathPrivate::_q_fillGradientChanged()
emit q->shapePathChanged();
}
-void QQuickShapePath::resetFillGradient()
+/*!
+ \qmlproperty Item QtQuick.Shapes::ShapePath::fillItem
+ \since 6.8
+
+ This property defines another Qt Quick Item to use as fill by the shape. The item must be
+ texture provider (such as a \l {Item Layers} {layered item}, a \l{ShaderEffectSource} or an
+ \l{Image}). If it is not a valid texture provider, this property will be ignored.
+
+ \note When using a layered item as a \c fillItem, you may see pixelation effects when
+ transforming the fill. Setting the \l layer.smooth property to true will give better visual
+ results in this case.
+
+ By default no fill item is set and the value is \c null.
+
+ \note If set to something other than \c null, the \c fillItem property takes precedence over
+ \l fillColor. The \l fillGradient property in turn takes precedence over both \c fillItem and
+ \l{fillColor}.
+ */
+
+QQuickItem *QQuickShapePath::fillItem() const
{
- setFillGradient(nullptr);
+ Q_D(const QQuickShapePath);
+ return d->sfp.fillItem;
+}
+
+void QQuickShapePath::setFillItem(QQuickItem *fillItem)
+{
+ Q_D(QQuickShapePath);
+ if (d->sfp.fillItem != fillItem) {
+ if (d->sfp.fillItem != nullptr) {
+ qmlobject_disconnect(d->sfp.fillItem, QQuickItem, SIGNAL(destroyed()),
+ this, QQuickShapePath, SLOT(_q_fillItemDestroyed()));
+ }
+ d->sfp.fillItem = fillItem;
+ if (d->sfp.fillItem != nullptr) {
+ qmlobject_connect(d->sfp.fillItem, QQuickItem, SIGNAL(destroyed()),
+ this, QQuickShapePath, SLOT(_q_fillItemDestroyed()));
+ }
+ emit fillItemChanged();
+
+ d->dirty |= QQuickShapePathPrivate::DirtyFillItem;
+ emit shapePathChanged();
+ }
+}
+
+void QQuickShapePathPrivate::_q_fillItemDestroyed()
+{
+ Q_Q(QQuickShapePath);
+ sfp.fillItem = nullptr;
+ dirty |= DirtyFillItem;
+ emit q->fillItemChanged();
+ emit q->shapePathChanged();
}
/*!
@@ -726,6 +784,12 @@ void QQuickShapePrivate::_q_shapePathChanged()
q->setImplicitSize(br.right(), br.bottom());
}
+void QQuickShapePrivate::handleSceneChange(QQuickWindow *w)
+{
+ if (renderer != nullptr)
+ renderer->handleSceneChange(w);
+}
+
void QQuickShapePrivate::setStatus(QQuickShape::Status newStatus)
{
Q_Q(QQuickShape);
@@ -1190,6 +1254,7 @@ void QQuickShape::itemChange(ItemChange change, const ItemChangeData &data)
for (int i = 0; i < d->sp.size(); ++i)
QQuickShapePathPrivate::get(d->sp[i])->dirty = QQuickShapePathPrivate::DirtyAll;
d->_q_shapePathChanged();
+ d->handleSceneChange(data.window);
}
QQuickItem::itemChange(change, data);
@@ -1396,6 +1461,16 @@ void QQuickShapePrivate::sync()
renderer->setFillGradient(i, p->fillGradient());
if (dirty & QQuickShapePathPrivate::DirtyFillTransform)
renderer->setFillTransform(i, QQuickShapePathPrivate::get(p)->sfp.fillTransform);
+ if (dirty & QQuickShapePathPrivate::DirtyFillItem) {
+ if (p->fillItem() == nullptr) {
+ renderer->setFillTextureProvider(i, nullptr);
+ } else if (p->fillItem()->isTextureProvider()) {
+ renderer->setFillTextureProvider(i, p->fillItem());
+ } else {
+ renderer->setFillTextureProvider(i, nullptr);
+ qWarning() << "QQuickShape: Fill item is not texture provider";
+ }
+ }
dirty = 0;
}
diff --git a/src/quickshapes/qquickshape_p.h b/src/quickshapes/qquickshape_p.h
index 30be74b775..3ed4927c2d 100644
--- a/src/quickshapes/qquickshape_p.h
+++ b/src/quickshapes/qquickshape_p.h
@@ -198,6 +198,7 @@ class Q_QUICKSHAPES_EXPORT QQuickShapePath : public QQuickPath
Q_PROPERTY(QSizeF scale READ scale WRITE setScale NOTIFY scaleChanged REVISION(1, 14))
Q_PROPERTY(PathHints pathHints READ pathHints WRITE setPathHints NOTIFY pathHintsChanged REVISION(6, 7) FINAL)
Q_PROPERTY(QMatrix4x4 fillTransform READ fillTransform WRITE setFillTransform NOTIFY fillTransformChanged REVISION(6, 8) FINAL)
+ Q_PROPERTY(QQuickItem *fillItem READ fillItem WRITE setFillItem NOTIFY fillItemChanged REVISION(6, 8) FINAL)
QML_NAMED_ELEMENT(ShapePath)
QML_ADDED_IN_VERSION(1, 0)
@@ -283,6 +284,9 @@ public:
QMatrix4x4 fillTransform() const;
void setFillTransform(const QMatrix4x4 &matrix);
+ QQuickItem *fillItem() const;
+ void setFillItem(QQuickItem *newFillItem);
+
Q_SIGNALS:
void shapePathChanged();
void strokeColorChanged();
@@ -298,11 +302,13 @@ Q_SIGNALS:
Q_REVISION(6, 7) void pathHintsChanged();
Q_REVISION(6, 8) void fillTransformChanged();
+ Q_REVISION(6, 8) void fillItemChanged();
private:
Q_DISABLE_COPY(QQuickShapePath)
Q_DECLARE_PRIVATE(QQuickShapePath)
Q_PRIVATE_SLOT(d_func(), void _q_fillGradientChanged())
+ Q_PRIVATE_SLOT(d_func(), void _q_fillItemDestroyed())
};
class Q_QUICKSHAPES_EXPORT QQuickShape : public QQuickItem
diff --git a/src/quickshapes/qquickshape_p_p.h b/src/quickshapes/qquickshape_p_p.h
index ffa1068de9..cce52d876f 100644
--- a/src/quickshapes/qquickshape_p_p.h
+++ b/src/quickshapes/qquickshape_p_p.h
@@ -57,8 +57,10 @@ public:
virtual void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
qreal dashOffset, const QVector<qreal> &dashPattern) = 0;
virtual void setFillGradient(int index, QQuickShapeGradient *gradient) = 0;
+ virtual void setFillTextureProvider(int index, QQuickItem *textureProviderItem) = 0;
virtual void setFillTransform(int index, const QSGTransform &transform) = 0;
virtual void setTriangulationScale(qreal) { }
+ virtual void handleSceneChange(QQuickWindow *window) = 0;
// Render thread, with gui blocked
virtual void updateNode() = 0;
@@ -82,6 +84,7 @@ struct QQuickShapeStrokeFillParams
QVector<qreal> dashPattern;
QQuickShapeGradient *fillGradient;
QSGTransform fillTransform;
+ QQuickItem *fillItem;
};
class Q_QUICKSHAPES_EXPORT QQuickShapePathPrivate : public QQuickPathPrivate
@@ -99,14 +102,18 @@ public:
DirtyDash = 0x40,
DirtyFillGradient = 0x80,
DirtyFillTransform = 0x100,
+ DirtyFillItem = 0x200,
- DirtyAll = 0x1FF
+ DirtyAll = 0x3FF
};
QQuickShapePathPrivate();
void _q_pathChanged();
void _q_fillGradientChanged();
+ void _q_fillItemDestroyed();
+
+ void handleSceneChange();
static QQuickShapePathPrivate *get(QQuickShapePath *p) { return p->d_func(); }
@@ -130,6 +137,7 @@ public:
void _q_shapePathChanged();
void setStatus(QQuickShape::Status newStatus);
+ void handleSceneChange(QQuickWindow *w);
static QQuickShapePrivate *get(QQuickShape *item) { return item->d_func(); }
diff --git a/src/quickshapes/qquickshapecurverenderer.cpp b/src/quickshapes/qquickshapecurverenderer.cpp
index a163fd7781..677adb5ded 100644
--- a/src/quickshapes/qquickshapecurverenderer.cpp
+++ b/src/quickshapes/qquickshapecurverenderer.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickshapecurverenderer_p.h"
@@ -271,7 +271,32 @@ void QQuickShapeCurveRenderer::setFillTransform(int index, const QSGTransform &t
{
auto &pathData = m_paths[index];
pathData.fillTransform = transform;
- pathData.m_dirty |= FillDirty;
+ pathData.m_dirty |= FillDirty | UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
+{
+ auto &pathData = m_paths[index];
+ if ((pathData.fillTextureProviderItem == nullptr) != (textureProviderItem == nullptr))
+ pathData.m_dirty |= FillDirty;
+ if (pathData.fillTextureProviderItem != nullptr)
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
+ pathData.fillTextureProviderItem = textureProviderItem;
+ if (pathData.fillTextureProviderItem != nullptr)
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(m_item->window());
+ pathData.m_dirty |= UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::handleSceneChange(QQuickWindow *window)
+{
+ for (auto &pathData : m_paths) {
+ if (pathData.fillTextureProviderItem != nullptr) {
+ if (window == nullptr)
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
+ else
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
+ }
+ }
}
void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data)
@@ -364,8 +389,23 @@ void QQuickShapeCurveRenderer::updateNode()
return;
auto updateUniforms = [](const PathData &pathData) {
- for (auto &pathNode : std::as_const(pathData.fillNodes))
+ for (auto &pathNode : std::as_const(pathData.fillNodes)) {
pathNode->setColor(pathData.fillColor);
+
+ QSGCurveFillNode *fillNode = static_cast<QSGCurveFillNode *>(pathNode);
+ bool needsUpdate = pathData.fillTextureProviderItem == nullptr && fillNode->fillTextureProvider() != nullptr;
+ if (!needsUpdate
+ && pathData.fillTextureProviderItem != nullptr
+ && fillNode->fillTextureProvider() != pathData.fillTextureProviderItem->textureProvider()) {
+ needsUpdate = true;
+ }
+
+ if (needsUpdate) {
+ fillNode->setFillTextureProvider(pathData.fillTextureProviderItem != nullptr
+ ? pathData.fillTextureProviderItem->textureProvider()
+ : nullptr);
+ }
+ }
for (auto &strokeNode : std::as_const(pathData.strokeNodes))
strokeNode->setColor(pathData.pen.color());
};
diff --git a/src/quickshapes/qquickshapecurverenderer_p.h b/src/quickshapes/qquickshapecurverenderer_p.h
index a6f80824fd..2960f7e85e 100644
--- a/src/quickshapes/qquickshapecurverenderer_p.h
+++ b/src/quickshapes/qquickshapecurverenderer_p.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQUICKSHAPECURVERENDERER_P_H
@@ -55,10 +55,12 @@ public:
void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
qreal dashOffset, const QVector<qreal> &dashPattern) override;
void setFillGradient(int index, QQuickShapeGradient *gradient) override;
+ void setFillTextureProvider(int index, QQuickItem *textureProviderItem) override;
void setFillTransform(int index, const QSGTransform &transform) override;
void endSync(bool async) override;
void setAsyncCallback(void (*)(void *), void *) override;
Flags flags() const override { return SupportsAsync; }
+ void handleSceneChange(QQuickWindow *window) override;
void updateNode() override;
@@ -112,6 +114,7 @@ private:
NodeList strokeNodes;
QQuickShapeCurveRunnable *currentRunner = nullptr;
+ QQuickItem *fillTextureProviderItem = nullptr;
};
void createRunner(PathData *pathData);
diff --git a/src/quickshapes/qquickshapegenericrenderer.cpp b/src/quickshapes/qquickshapegenericrenderer.cpp
index 20dfe21040..34c7fa96a0 100644
--- a/src/quickshapes/qquickshapegenericrenderer.cpp
+++ b/src/quickshapes/qquickshapegenericrenderer.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickshapegenericrenderer_p.h"
@@ -6,6 +6,8 @@
#include <QtGui/private/qtriangulatingstroker_p.h>
#include <rhi/qrhi.h>
#include <QSGVertexColorMaterial>
+#include <QSGTextureProvider>
+#include <private/qsgplaintexture_p.h>
#include <QtQuick/private/qsggradientcache_p.h>
@@ -42,6 +44,7 @@ QQuickShapeGenericStrokeFillNode::QQuickShapeGenericStrokeFillNode(QQuickWindow
: m_material(nullptr)
{
setFlag(QSGNode::OwnsGeometry, true);
+ setFlag(QSGNode::UsePreprocess, true);
setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0));
activateMaterial(window, MatSolidColor);
#ifdef QSG_RUNTIME_DESCRIPTION
@@ -66,6 +69,9 @@ void QQuickShapeGenericStrokeFillNode::activateMaterial(QQuickWindow *window, Ma
case MatConicalGradient:
m_material.reset(QQuickShapeGenericMaterialFactory::createConicalGradient(window, this));
break;
+ case MatTextureFill:
+ m_material.reset(QQuickShapeGenericMaterialFactory::createTextureFill(window, this));
+ break;
default:
qWarning("Unknown material %d", m);
return;
@@ -75,6 +81,25 @@ void QQuickShapeGenericStrokeFillNode::activateMaterial(QQuickWindow *window, Ma
setMaterial(m_material.data());
}
+void QQuickShapeGenericStrokeFillNode::preprocess()
+{
+ if (m_fillTextureProvider != nullptr) {
+ if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(m_fillTextureProvider->texture()))
+ texture->updateTexture();
+ }
+}
+
+void QQuickShapeGenericStrokeFillNode::handleTextureChanged()
+{
+ markDirty(QSGNode::DirtyMaterial);
+}
+
+void QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed()
+{
+ m_fillTextureProvider = nullptr;
+ markDirty(QSGNode::DirtyMaterial);
+}
+
QQuickShapeGenericRenderer::~QQuickShapeGenericRenderer()
{
for (ShapePathData &d : m_sp) {
@@ -202,6 +227,32 @@ void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient
d.syncDirty |= DirtyFillGradient;
}
+void QQuickShapeGenericRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
+{
+ ShapePathData &d(m_sp[index]);
+ if ((d.fillTextureProviderItem == nullptr) != (textureProviderItem == nullptr))
+ d.syncDirty |= DirtyFillGeom;
+ if (d.fillTextureProviderItem != nullptr)
+ QQuickItemPrivate::get(d.fillTextureProviderItem)->derefWindow();
+ d.fillTextureProviderItem = textureProviderItem;
+ if (d.fillTextureProviderItem != nullptr)
+ QQuickItemPrivate::get(d.fillTextureProviderItem)->refWindow(m_item->window());
+ d.syncDirty |= DirtyFillTexture;
+}
+
+void QQuickShapeGenericRenderer::handleSceneChange(QQuickWindow *window)
+{
+ for (auto &pathData : m_sp) {
+ if (pathData.fillTextureProviderItem != nullptr) {
+ if (window == nullptr)
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
+ else
+ QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
+ }
+ }
+}
+
+
void QQuickShapeGenericRenderer::setFillTransform(int index, const QSGTransform &transform)
{
ShapePathData &d(m_sp[index]);
@@ -498,7 +549,7 @@ void QQuickShapeGenericRenderer::updateNode()
QQuickShapeGenericNode *node = *nodePtr;
if (m_accDirty & DirtyList)
- d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform;
+ d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture;
if (!d.effectiveDirty) {
prevNode = node;
@@ -551,16 +602,44 @@ void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuick
if (d->fillGradientActive) {
if (d->effectiveDirty & DirtyFillGradient)
n->m_fillGradient = d->fillGradient;
- if (d->effectiveDirty & DirtyFillTransform)
- n->m_fillTransform = d->fillTransform;
}
+ if (d->effectiveDirty & DirtyFillTexture) {
+ bool needsUpdate = d->fillTextureProviderItem == nullptr && n->m_fillTextureProvider != nullptr;
+ if (!needsUpdate
+ && d->fillTextureProviderItem != nullptr
+ && n->m_fillTextureProvider != d->fillTextureProviderItem->textureProvider()) {
+ needsUpdate = true;
+ }
+
+ if (needsUpdate) {
+ if (n->m_fillTextureProvider != nullptr) {
+ QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
+ n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
+ QObject::disconnect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
+ n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
+ }
+
+ n->m_fillTextureProvider = d->fillTextureProviderItem == nullptr
+ ? nullptr
+ : d->fillTextureProviderItem->textureProvider();
+
+ if (n->m_fillTextureProvider != nullptr) {
+ QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::textureChanged,
+ n, &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
+ QObject::connect(n->m_fillTextureProvider, &QSGTextureProvider::destroyed,
+ n, &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
+ }
+ }
+ }
+ if (d->effectiveDirty & DirtyFillTransform)
+ n->m_fillTransform = d->fillTransform;
}
void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
{
if (!node->m_fillNode)
return;
- if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform)))
+ if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture)))
return;
// Make a copy of the data that will be accessed by the material on
@@ -600,10 +679,14 @@ void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGen
if (!(d->effectiveDirty & DirtyFillGeom))
return;
}
+ } else if (d->fillTextureProviderItem != nullptr) {
+ n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatTextureFill);
+ if (d->effectiveDirty & DirtyFillTexture)
+ n->markDirty(QSGNode::DirtyMaterial);
} else {
n->activateMaterial(m_item->window(), QQuickShapeGenericStrokeFillNode::MatSolidColor);
// fast path for updating only color values when no change in vertex positions
- if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom)) {
+ if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom) && d->fillTextureProviderItem == nullptr) {
ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
for (int i = 0; i < g->vertexCount(); ++i)
vdst[i].set(vdst[i].x, vdst[i].y, d->fillColor);
@@ -622,6 +705,7 @@ void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGen
g->allocate(d->fillVertices.size(), indexCount);
}
g->setDrawingMode(QSGGeometry::DrawTriangles);
+
memcpy(g->vertexData(), d->fillVertices.constData(), g->vertexCount() * g->sizeOfVertex());
memcpy(g->indexData(), d->fillIndices.constData(), g->indexCount() * g->sizeOfIndex());
@@ -712,6 +796,18 @@ QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWind
return nullptr;
}
+QSGMaterial *QQuickShapeGenericMaterialFactory::createTextureFill(QQuickWindow *window,
+ QQuickShapeGenericStrokeFillNode *node)
+{
+ QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
+
+ if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
+ return new QQuickShapeTextureFillMaterial(node);
+
+ qWarning("Texture fill material: Unsupported graphics API %d", api);
+ return nullptr;
+}
+
QQuickShapeLinearGradientRhiShader::QQuickShapeLinearGradientRhiShader(int viewCount)
{
setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"), viewCount);
@@ -1104,6 +1200,139 @@ QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererI
return new QQuickShapeConicalGradientRhiShader(viewCount());
}
+QQuickShapeTextureFillRhiShader::QQuickShapeTextureFillRhiShader(int viewCount)
+{
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.frag.qsb"), viewCount);
+}
+
+bool QQuickShapeTextureFillRhiShader::updateUniformData(RenderState &state,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
+{
+ Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
+ QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
+ Q_ASSERT(buf->size() >= 64 * shaderMatrixCount + 64 + 8 + 4);
+
+ if (state.isMatrixDirty()) {
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ const QMatrix4x4 m = state.combinedMatrix();
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
+ }
+
+ QQuickShapeGenericStrokeFillNode *node = m->node();
+
+ if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
+ memcpy(buf->data() + 64 * shaderMatrixCount, node->m_fillTransform.invertedData(), 64);
+ m_fillTransform = node->m_fillTransform;
+ changed = true;
+ }
+
+ const QSizeF boundsSize = node->m_fillTextureProvider != nullptr && node->m_fillTextureProvider->texture() != nullptr
+ ? node->m_fillTextureProvider->texture()->textureSize()
+ : QSizeF(0, 0);
+
+
+ const QVector2D boundsVector(boundsSize.width() / state.devicePixelRatio(),
+ boundsSize.height() / state.devicePixelRatio());
+ if (!oldMaterial || m_boundsSize != boundsVector) {
+ m_boundsSize = boundsVector;
+ Q_ASSERT(sizeof(m_boundsSize) == 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_boundsSize, 8);
+ changed = true;
+ }
+
+ if (state.isOpacityDirty()) {
+ const float opacity = state.opacity();
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &opacity, 4);
+ changed = true;
+ }
+
+ return changed;
+}
+
+void QQuickShapeTextureFillRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *)
+{
+ if (binding != 1)
+ return;
+
+ QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
+ QQuickShapeGenericStrokeFillNode *node = m->node();
+ if (node->m_fillTextureProvider != nullptr) {
+ QSGTexture *providedTexture = node->m_fillTextureProvider->texture();
+ if (providedTexture != nullptr) {
+ if (providedTexture->isAtlasTexture()) {
+ // Create a non-atlas copy to make texture coordinate wrapping work. This
+ // texture copy is owned by the QSGTexture so memory is managed with the original
+ // texture provider.
+ QSGTexture *newTexture = providedTexture->removedFromAtlas(state.resourceUpdateBatch());
+ if (newTexture != nullptr)
+ providedTexture = newTexture;
+ }
+
+ providedTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+ *texture = providedTexture;
+ return;
+ }
+ }
+
+ if (m->dummyTexture() == nullptr) {
+ QSGPlainTexture *dummyTexture = new QSGPlainTexture;
+ dummyTexture->setFiltering(QSGTexture::Nearest);
+ dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
+ dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
+ QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+ dummyTexture->setImage(img);
+ dummyTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
+
+ m->setDummyTexture(dummyTexture);
+ }
+
+ *texture = m->dummyTexture();
+}
+
+QQuickShapeTextureFillMaterial::~QQuickShapeTextureFillMaterial()
+{
+ delete m_dummyTexture;
+}
+
+QSGMaterialType *QQuickShapeTextureFillMaterial::type() const
+{
+ static QSGMaterialType type;
+ return &type;
+}
+
+int QQuickShapeTextureFillMaterial::compare(const QSGMaterial *other) const
+{
+ Q_ASSERT(other && type() == other->type());
+ const QQuickShapeTextureFillMaterial *m = static_cast<const QQuickShapeTextureFillMaterial *>(other);
+
+ QQuickShapeGenericStrokeFillNode *a = node();
+ QQuickShapeGenericStrokeFillNode *b = m->node();
+ Q_ASSERT(a && b);
+ if (a == b)
+ return 0;
+
+ if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
+ return d;
+
+ const qintptr diff = qintptr(a->m_fillTextureProvider) - qintptr(b->m_fillTextureProvider);
+ return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
+}
+
+QSGMaterialShader *QQuickShapeTextureFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
+{
+ Q_UNUSED(renderMode);
+ return new QQuickShapeTextureFillRhiShader(viewCount());
+}
+
QT_END_NAMESPACE
#include "moc_qquickshapegenericrenderer_p.cpp"
diff --git a/src/quickshapes/qquickshapegenericrenderer_p.h b/src/quickshapes/qquickshapegenericrenderer_p.h
index f4a0f21487..7f5ce81da0 100644
--- a/src/quickshapes/qquickshapegenericrenderer_p.h
+++ b/src/quickshapes/qquickshapegenericrenderer_p.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQUICKSHAPEGENERICRENDERER_P_H
@@ -41,7 +41,8 @@ public:
DirtyColor = 0x04,
DirtyFillGradient = 0x08,
DirtyFillTransform = 0x10,
- DirtyList = 0x20 // only for accDirty
+ DirtyFillTexture = 0x20,
+ DirtyList = 0x40 // only for accDirty
};
QQuickShapeGenericRenderer(QQuickItem *item)
@@ -65,11 +66,13 @@ public:
void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
qreal dashOffset, const QVector<qreal> &dashPattern) override;
void setFillGradient(int index, QQuickShapeGradient *gradient) override;
+ void setFillTextureProvider(int index, QQuickItem *textureProviderItem) override;
void setFillTransform(int index, const QSGTransform &transform) override;
void setTriangulationScale(qreal scale) override;
void endSync(bool async) override;
void setAsyncCallback(void (*)(void *), void *) override;
Flags flags() const override { return SupportsAsync; }
+ void handleSceneChange(QQuickWindow *window) override;
void updateNode() override;
@@ -77,6 +80,7 @@ public:
struct Color4ub { unsigned char r, g, b, a; };
typedef QVector<QSGGeometry::ColoredPoint2D> VertexContainerType;
+ typedef QVector<QSGGeometry::TexturedPoint2D> TexturedVertexContainerType;
typedef QVector<quint32> IndexContainerType;
static void triangulateFill(const QPainterPath &path,
@@ -105,6 +109,7 @@ private:
QPainterPath path;
FillGradientType fillGradientActive;
QSGGradientCache::GradientDesc fillGradient;
+ QQuickItem *fillTextureProviderItem = nullptr;
QSGTransform fillTransform;
VertexContainerType fillVertices;
IndexContainerType fillIndices;
@@ -177,8 +182,9 @@ Q_SIGNALS:
void done(QQuickShapeStrokeRunnable *self);
};
-class QQuickShapeGenericStrokeFillNode : public QSGGeometryNode
+class QQuickShapeGenericStrokeFillNode : public QObject, public QSGGeometryNode
{
+ Q_OBJECT
public:
QQuickShapeGenericStrokeFillNode(QQuickWindow *window);
@@ -186,14 +192,21 @@ public:
MatSolidColor,
MatLinearGradient,
MatRadialGradient,
- MatConicalGradient
+ MatConicalGradient,
+ MatTextureFill
};
void activateMaterial(QQuickWindow *window, Material m);
// shadow data for custom materials
QSGGradientCache::GradientDesc m_fillGradient;
+ QSGTextureProvider *m_fillTextureProvider = nullptr;
QSGTransform m_fillTransform;
+ void preprocess() override;
+
+private Q_SLOTS:
+ void handleTextureChanged();
+ void handleTextureProviderDestroyed();
private:
QScopedPointer<QSGMaterial> m_material;
@@ -216,6 +229,7 @@ public:
static QSGMaterial *createLinearGradient(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node);
static QSGMaterial *createRadialGradient(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node);
static QSGMaterial *createConicalGradient(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node);
+ static QSGMaterial *createTextureFill(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node);
};
class QQuickShapeLinearGradientRhiShader : public QSGMaterialShader
@@ -330,6 +344,53 @@ private:
QQuickShapeGenericStrokeFillNode *m_node;
};
+class QQuickShapeTextureFillRhiShader : public QSGMaterialShader
+{
+public:
+ QQuickShapeTextureFillRhiShader(int viewCount);
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
+ QSGMaterial *oldMaterial) override;
+ void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
+
+private:
+ QSGTransform m_fillTransform;
+ QVector2D m_boundsOffset;
+ QVector2D m_boundsSize;
+};
+
+class QQuickShapeTextureFillMaterial : public QSGMaterial
+{
+public:
+ QQuickShapeTextureFillMaterial(QQuickShapeGenericStrokeFillNode *node)
+ : m_node(node)
+ {
+ setFlag(Blending | RequiresFullMatrix);
+ }
+ ~QQuickShapeTextureFillMaterial() override;
+
+ QSGMaterialType *type() const override;
+ int compare(const QSGMaterial *other) const override;
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
+
+ QQuickShapeGenericStrokeFillNode *node() const { return m_node; }
+
+ QSGPlainTexture *dummyTexture() const
+ {
+ return m_dummyTexture;
+ }
+
+ void setDummyTexture(QSGPlainTexture *texture)
+ {
+ m_dummyTexture = texture;
+ }
+
+private:
+ QQuickShapeGenericStrokeFillNode *m_node;
+ QSGPlainTexture *m_dummyTexture = nullptr;
+};
+
QT_END_NAMESPACE
#endif // QQUICKSHAPEGENERICRENDERER_P_H
diff --git a/src/quickshapes/qquickshapesoftwarerenderer.cpp b/src/quickshapes/qquickshapesoftwarerenderer.cpp
index 12142a690e..f1a2e3dc67 100644
--- a/src/quickshapes/qquickshapesoftwarerenderer.cpp
+++ b/src/quickshapes/qquickshapesoftwarerenderer.cpp
@@ -138,6 +138,18 @@ void QQuickShapeSoftwareRenderer::setFillGradient(int index, QQuickShapeGradient
m_accDirty |= DirtyBrush;
}
+void QQuickShapeSoftwareRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
+{
+ Q_UNUSED(index);
+ Q_UNUSED(textureProviderItem);
+}
+
+void QQuickShapeSoftwareRenderer::handleSceneChange(QQuickWindow *window)
+{
+ Q_UNUSED(window);
+ // No action needed
+}
+
void QQuickShapeSoftwareRenderer::setFillTransform(int index, const QSGTransform &transform)
{
ShapePathGuiData &d(m_sp[index]);
diff --git a/src/quickshapes/qquickshapesoftwarerenderer_p.h b/src/quickshapes/qquickshapesoftwarerenderer_p.h
index 12e4d0f038..94eded84e5 100644
--- a/src/quickshapes/qquickshapesoftwarerenderer_p.h
+++ b/src/quickshapes/qquickshapesoftwarerenderer_p.h
@@ -47,8 +47,10 @@ public:
void setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
qreal dashOffset, const QVector<qreal> &dashPattern) override;
void setFillGradient(int index, QQuickShapeGradient *gradient) override;
+ void setFillTextureProvider(int index, QQuickItem *textureProviderItem) override;
void setFillTransform(int index, const QSGTransform &transform) override;
void endSync(bool async) override;
+ void handleSceneChange(QQuickWindow *window) override;
void updateNode() override;
diff --git a/src/quickshapes/shaders_ng/texturefill.frag b/src/quickshapes/shaders_ng/texturefill.frag
new file mode 100644
index 0000000000..c8a7f280f1
--- /dev/null
+++ b/src/quickshapes/shaders_ng/texturefill.frag
@@ -0,0 +1,25 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#version 440
+
+layout(location = 0) in vec2 textureCoord;
+layout(location = 0) out vec4 fragColor;
+
+layout(binding = 1) uniform sampler2D sourceTexture;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ mat4 fillMatrix;
+ vec2 boundsSize;
+ float qt_Opacity;
+} ubuf;
+
+void main()
+{
+ fragColor = texture(sourceTexture, textureCoord) * ubuf.qt_Opacity;
+}
diff --git a/src/quickshapes/shaders_ng/texturefill.vert b/src/quickshapes/shaders_ng/texturefill.vert
new file mode 100644
index 0000000000..c9d52469dc
--- /dev/null
+++ b/src/quickshapes/shaders_ng/texturefill.vert
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec4 vertexColor;
+
+layout(location = 0) out vec2 textureCoord;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+ mat4 fillMatrix;
+ vec2 boundsSize;
+ float qt_Opacity;
+} ubuf;
+
+void main()
+{
+ vec2 xformed = (ubuf.fillMatrix * vertexCoord).xy;
+ textureCoord = vec2(xformed.x / ubuf.boundsSize.x, xformed.y / ubuf.boundsSize.y);
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = ubuf.qt_Matrix * vertexCoord;
+#endif
+}
diff --git a/tests/baseline/scenegraph/data/shape/shape_fillItem.qml b/tests/baseline/scenegraph/data/shape/shape_fillItem.qml
new file mode 100644
index 0000000000..0862e364b5
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shape/shape_fillItem.qml
@@ -0,0 +1,177 @@
+import QtQuick
+import QtQuick.Shapes
+
+Item {
+ width: 640
+ height: 840
+
+ ListModel {
+ id: renderers
+ ListElement { renderer: Shape.GeometryRenderer; rotationAmount: 0 }
+ ListElement { renderer: Shape.GeometryRenderer; rotationAmount: 30 }
+ ListElement { renderer: Shape.CurveRenderer; rotationAmount: 0 }
+ ListElement { renderer: Shape.CurveRenderer; rotationAmount: 30 }
+ }
+
+ Image {
+ id: image
+ visible: false
+ source: "../shared/col320x480.jpg"
+ }
+
+ Image {
+ id: tiledImage
+ visible: false
+ source: "../shared/col320x480.jpg"
+ layer.enabled: true
+ layer.smooth: true
+ layer.wrapMode: ShaderEffectSource.Repeat
+ }
+
+ Image {
+ id: asynchronousImage
+ visible: false
+ source: "../shared/col320x480.jpg"
+ layer.enabled: true
+ layer.smooth: true
+ layer.wrapMode: ShaderEffectSource.Repeat
+ asynchronous: true
+ }
+
+ Rectangle {
+ id: item
+ visible: false
+ layer.enabled: true
+ layer.smooth: true
+ layer.wrapMode: ShaderEffectSource.Repeat
+ color: "cyan"
+ width: 20
+ height: 20
+ Text {
+ anchors.centerIn: parent
+ text: "😊"
+ }
+ }
+
+ Rectangle {
+ id: sourceItem
+ color: "cyan"
+ width: 20
+ height: 20
+ Text {
+ anchors.centerIn: parent
+ text: "😁"
+ }
+ }
+
+ ShaderEffectSource {
+ id: shaderEffectSource
+ sourceItem: sourceItem
+ width: 20
+ height: 20
+ wrapMode: ShaderEffectSource.Repeat
+ visible: false
+ hideSource: true
+ smooth: true
+ }
+
+ Row {
+ anchors.fill: parent
+ Repeater {
+ model: renderers
+ Column {
+ Shape {
+ id: shape
+ preferredRendererType: renderer
+ width: 160
+ height: 700
+ property real rotate: rotationAmount
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: image
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+
+ PathRectangle {
+ x: 10; y: 10
+ width: 140
+ height: 100
+ }
+
+ // startX: 10; startY: 10
+ // PathLine { relativeX: 140; relativeY: 0 }
+ // PathLine { relativeX: 0; relativeY: 100 }
+ // PathLine { relativeX: -140; relativeY: 0 }
+ // PathLine { relativeX: 0; relativeY: -100 }
+ }
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: tiledImage
+
+ PathRectangle {
+ x: 10; y: 10 + 1 * 140
+ width: 140
+ height: 100
+ }
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+ }
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: item
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+
+ PathRectangle {
+ x: 10; y: 10 + 2 * 140
+ width: 140
+ height: 100
+ }
+ }
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: asynchronousImage
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+
+ PathRectangle {
+ x: 10; y: 10 + 3 * 140
+ width: 140
+ height: 100
+ }
+ }
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: shaderEffectSource
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+
+ PathRectangle {
+ x: 10; y: 10 + 4 * 140
+ width: 140
+ height: 100
+ }
+ }
+ }
+
+ Shape {
+ preferredRendererType: renderer
+ width: 160
+ height: 200
+ x: 10
+
+ ShapePath {
+ strokeColor: "transparent"
+ fillItem: image
+ fillTransform: PlanarTransform.fromRotate(shape.rotate)
+
+ PathRectangle {
+ width: 140
+ height: 100
+ }
+ }
+ }
+ }
+ }
+ }
+}