diff options
author | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-05-13 13:15:57 +0200 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-05-27 17:36:45 +0200 |
commit | b70dc1196c87c3d4b8d02c189a54b166d6d6146d (patch) | |
tree | ffd477944953567aa8518c2bd731170ebda37ba7 | |
parent | 5deb57432fb139d3147c7932cecb715d239a4e08 (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]>
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 + } + } + } + } + } + } +} |