aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickshapes
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickshapes')
-rw-r--r--src/quickshapes/CMakeLists.txt33
-rw-r--r--src/quickshapes/qquickshape.cpp634
-rw-r--r--src/quickshapes/qquickshape_p.h94
-rw-r--r--src/quickshapes/qquickshape_p_p.h70
-rw-r--r--src/quickshapes/qquickshapecurverenderer.cpp782
-rw-r--r--src/quickshapes/qquickshapecurverenderer_p.h164
-rw-r--r--src/quickshapes/qquickshapecurverenderer_p_p.h28
-rw-r--r--src/quickshapes/qquickshapegenericrenderer.cpp389
-rw-r--r--src/quickshapes/qquickshapegenericrenderer_p.h91
-rw-r--r--src/quickshapes/qquickshapesglobal_p.h4
-rw-r--r--src/quickshapes/qquickshapesoftwarerenderer.cpp21
-rw-r--r--src/quickshapes/qquickshapesoftwarerenderer_p.h3
-rwxr-xr-xsrc/quickshapes/shaders_ng/compile.bat15
-rw-r--r--src/quickshapes/shaders_ng/conicalgradient.frag8
-rw-r--r--src/quickshapes/shaders_ng/conicalgradient.frag.qsbbin2284 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/conicalgradient.vert14
-rw-r--r--src/quickshapes/shaders_ng/conicalgradient.vert.qsbbin1856 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/lineargradient.frag8
-rw-r--r--src/quickshapes/shaders_ng/lineargradient.frag.qsbbin1500 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/lineargradient.vert14
-rw-r--r--src/quickshapes/shaders_ng/lineargradient.vert.qsbbin2003 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/radialgradient.frag8
-rw-r--r--src/quickshapes/shaders_ng/radialgradient.frag.qsbbin2555 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/radialgradient.vert14
-rw-r--r--src/quickshapes/shaders_ng/radialgradient.vert.qsbbin1956 -> 0 bytes
-rw-r--r--src/quickshapes/shaders_ng/texturefill.frag25
-rw-r--r--src/quickshapes/shaders_ng/texturefill.vert31
-rw-r--r--src/quickshapes/shaders_ng/wireframe.frag25
-rw-r--r--src/quickshapes/shaders_ng/wireframe.vert23
29 files changed, 2189 insertions, 309 deletions
diff --git a/src/quickshapes/CMakeLists.txt b/src/quickshapes/CMakeLists.txt
index ba3155e5d9..78297fb532 100644
--- a/src/quickshapes/CMakeLists.txt
+++ b/src/quickshapes/CMakeLists.txt
@@ -20,15 +20,14 @@ qt_internal_add_qml_module(QuickShapesPrivate
qquickshape_p_p.h
qquickshapegenericrenderer.cpp qquickshapegenericrenderer_p.h
qquickshapesglobal.h qquickshapesglobal_p.h
+ qquickshapecurverenderer.cpp qquickshapecurverenderer_p.h qquickshapecurverenderer_p_p.h
qquickshapesoftwarerenderer.cpp qquickshapesoftwarerenderer_p.h
PUBLIC_LIBRARIES
Qt::Core
Qt::GuiPrivate
Qt::Qml
Qt::QuickPrivate
- GENERATE_CPP_EXPORTS
- GENERATE_PRIVATE_CPP_EXPORTS
-)
+ )
# We need to do additional initialization, so we have to provide our own
# plugin class rather than using the generated one
@@ -37,20 +36,24 @@ qt_internal_extend_target(qmlshapesplugin
LIBRARIES Qt::QuickShapesPrivate
)
-# Resources:
-set(qtquickshapes_resource_files
- "shaders_ng/conicalgradient.frag.qsb"
- "shaders_ng/conicalgradient.vert.qsb"
- "shaders_ng/lineargradient.frag.qsb"
- "shaders_ng/lineargradient.vert.qsb"
- "shaders_ng/radialgradient.frag.qsb"
- "shaders_ng/radialgradient.vert.qsb"
-)
-
-qt_internal_add_resource(QuickShapesPrivate "qtquickshapes"
+qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
PREFIX
"/qt-project.org/shapes"
FILES
- ${qtquickshapes_resource_files}
+ "shaders_ng/lineargradient.vert"
+ "shaders_ng/lineargradient.frag"
+ "shaders_ng/radialgradient.vert"
+ "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 494047d8e8..88a4fe7d3f 100644
--- a/src/quickshapes/qquickshape.cpp
+++ b/src/quickshapes/qquickshape.cpp
@@ -5,23 +5,24 @@
#include "qquickshape_p_p.h"
#include "qquickshapegenericrenderer_p.h"
#include "qquickshapesoftwarerenderer_p.h"
+#include "qquickshapecurverenderer_p.h"
#include <private/qsgplaintexture_p.h>
#include <private/qquicksvgparser_p.h>
#include <QtGui/private/qdrawhelper_p.h>
#include <QOpenGLFunctions>
#include <QLoggingCategory>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
static void initResources()
{
#if defined(QT_STATIC)
- Q_INIT_RESOURCE(qtquickshapes);
+ Q_INIT_RESOURCE(qtquickshapes_shaders);
#endif
}
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync")
+Q_STATIC_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync")
/*!
\qmlmodule QtQuick.Shapes 1.\QtMinorVersion
@@ -34,6 +35,35 @@ Q_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync")
\qml
import QtQuick.Shapes
\endqml
+
+ Qt Quick Shapes provides tools for drawing arbitrary shapes in a Qt Quick scene.
+ \l{Shape}{Shapes} can be constructed from basic building blocks like \l{PathLine}{lines} and
+ \l{PathCubic}{curves} that define sub-shapes. The sub-shapes can then be filled with solid
+ colors or gradients, and an outline stroke can be defined.
+
+ Qt Quick Shapes also supports higher level path element types, such as \l{PathText}{text} and
+ \l{PathSvg}{SVG path descriptions}. The currently supported element types is: PathMove,
+ PathLine, PathQuad, PathCubic, PathArc, PathText and PathSvg.
+
+ Qt Quick Shapes triangulates the shapes and renders the corresponding triangles on the GPU.
+ Therefore, altering the control points of elements will lead to re-triangulation of the
+ affected paths, at some performance cost. In addition, curves are flattened before they are
+ rendered, so applying a very high scale to the shape may show artifacts where it is visible
+ that the curves are represented by a sequence of smaller, straight lines.
+
+ \note By default, Qt Quick Shapes relies on multi-sampling for anti-aliasing. This can be
+ enabled for the entire application or window using the corresponding settings in QSurfaceFormat.
+ It can also be enabled for only the shape, by setting its \l{Item::layer.enabled}{layer.enabled}
+ property to true and then adjusting the \l{Item::layer.samples}{layer.samples} property. In the
+ latter case, multi-sampling will not be applied to the entire scene, but the shape will be
+ rendered via an intermediate off-screen buffer. Alternatively, the
+ \l{QtQuick.Shapes::Shape::preferredRendererType}{preferredRendererType} property can be set
+ to \c{Shape.CurveRenderer}. This has anti-aliasing built in and generally renders the shapes
+ at a higher quality, but at some additional performance cost.
+
+ For further information, the \l{Qt Quick Examples - Shapes}{Shapes example} shows how to
+ implement different types of shapes, fills and strokes, and the \l{Weather Forecast Example}
+ shows examples of different ways shapes might be useful in a user interface.
*/
void QQuickShapes_initializeModule()
@@ -58,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
}
@@ -119,7 +150,7 @@ QQuickShapeStrokeFillParams::QQuickShapeStrokeFillParams()
\image visualpath-code-example.png
- \sa {Qt Quick Examples - Shapes}, Shape
+ \sa {Qt Quick Examples - Shapes}, {Weather Forecast Example}, Shape
*/
QQuickShapePathPrivate::QQuickShapePathPrivate()
@@ -210,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
@@ -360,10 +394,8 @@ void QQuickShapePath::setCapStyle(CapStyle style)
This property defines the style of stroking. The default value is
ShapePath.SolidLine.
- \list
- \li ShapePath.SolidLine - A plain line.
- \li ShapePath.DashLine - Dashes separated by a few pixels.
- \endlist
+ \value ShapePath.SolidLine A plain line.
+ \value ShapePath.DashLine Dashes separated by a few pixels.
*/
QQuickShapePath::StrokeStyle QQuickShapePath::strokeStyle() const
@@ -446,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
@@ -478,6 +510,11 @@ void QQuickShapePath::setFillGradient(QQuickShapeGradient *gradient)
}
}
+void QQuickShapePath::resetFillGradient()
+{
+ setFillGradient(nullptr);
+}
+
void QQuickShapePathPrivate::_q_fillGradientChanged()
{
Q_Q(QQuickShapePath);
@@ -485,9 +522,135 @@ 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();
+}
+
+/*!
+ \qmlproperty PathHints QtQuick.Shapes::ShapePath::pathHints
+ \since 6.7
+
+ This property describes characteristics of the shape. If set, these hints may allow
+ optimized rendering. By default, no hints are set. It can be a combination of the following
+ values:
+
+ \value ShapePath.PathLinear
+ The path only has straight lines, no curves.
+ \value ShapePath.PathQuadratic
+ The path does not have any cubic curves: only lines and quadratic Bezier curves.
+ \value ShapePath.PathConvex
+ The path does not have any dents or holes. All straight lines between two points
+ inside the shape will be completely inside the shape.
+ \value ShapePath.PathFillOnRight
+ The path follows the TrueType convention where outlines around solid fill have their
+ control points ordered clockwise, and outlines around holes in the shape have their
+ control points ordered counter-clockwise.
+ \value ShapePath.PathSolid
+ The path has no holes, or mathematically speaking it is \e{simply connected}.
+ \value ShapePath.PathNonIntersecting
+ The path outline does not cross itself.
+ \value ShapePath.PathNonOverlappingControlPointTriangles
+ The triangles defined by the curve control points do not overlap with each other,
+ or with any of the line segments. Also, no line segments intersect.
+ This implies \c PathNonIntersecting.
+
+ Not all hints are logically independent, but the dependencies are not enforced.
+ For example, \c PathLinear implies \c PathQuadratic, but it is valid to have \c PathLinear
+ without \c PathQuadratic.
+
+ The pathHints property describes a set of statements known to be true; the absence of a hint
+ does not necessarily mean that the corresponding statement is false.
+*/
+
+QQuickShapePath::PathHints QQuickShapePath::pathHints() const
+{
+ Q_D(const QQuickShapePath);
+ return d->pathHints;
+}
+
+void QQuickShapePath::setPathHints(PathHints newPathHints)
+{
+ Q_D(QQuickShapePath);
+ if (d->pathHints == newPathHints)
+ return;
+ d->pathHints = newPathHints;
+ emit pathHintsChanged();
+}
+
+/*!
+ \qmlproperty matrix4x4 QtQuick.Shapes::ShapePath::fillTransform
+ \since 6.8
+
+ This property defines a transform to be applied to the path's fill pattern (gradient). It has
+ no effect if the fill is a solid color or transparent. By default no fill transform is enabled
+ and the value of this property is the \c identity matrix.
+*/
+
+QMatrix4x4 QQuickShapePath::fillTransform() const
+{
+ Q_D(const QQuickShapePath);
+ return d->sfp.fillTransform.matrix();
+}
+
+void QQuickShapePath::setFillTransform(const QMatrix4x4 &matrix)
+{
+ Q_D(QQuickShapePath);
+ if (d->sfp.fillTransform != matrix) {
+ d->sfp.fillTransform.setMatrix(matrix);
+ d->dirty |= QQuickShapePathPrivate::DirtyFillTransform;
+ emit fillTransformChanged();
+ emit shapePathChanged();
+ }
}
/*!
@@ -514,7 +677,7 @@ void QQuickShapePath::resetFillGradient()
Shape. However, not all Shape implementations support all path
element types, while some may not make sense for PathView. Shape's
currently supported subset is: PathMove, PathLine, PathQuad, PathCubic,
- PathArc, and PathSvg.
+ PathArc, PathText and PathSvg.
See \l Path for a detailed overview of the supported path elements.
@@ -598,7 +761,7 @@ void QQuickShapePath::resetFillGradient()
\endlist
- \sa {Qt Quick Examples - Shapes}, Path, PathMove, PathLine, PathQuad, PathCubic, PathArc, PathSvg
+ \sa {Qt Quick Examples - Shapes}, {Weather Forecast Example}, Path, PathMove, PathLine, PathQuad, PathCubic, PathArc, PathSvg
*/
QQuickShapePrivate::QQuickShapePrivate()
@@ -616,6 +779,15 @@ void QQuickShapePrivate::_q_shapePathChanged()
Q_Q(QQuickShape);
spChanged = true;
q->polish();
+ emit q->boundingRectChanged();
+ auto br = q->boundingRect();
+ q->setImplicitSize(br.right(), br.bottom());
+}
+
+void QQuickShapePrivate::handleSceneChange(QQuickWindow *w)
+{
+ if (renderer != nullptr)
+ renderer->handleSceneChange(w);
}
void QQuickShapePrivate::setStatus(QQuickShape::Status newStatus)
@@ -627,6 +799,18 @@ void QQuickShapePrivate::setStatus(QQuickShape::Status newStatus)
}
}
+qreal QQuickShapePrivate::getImplicitWidth() const
+{
+ Q_Q(const QQuickShape);
+ return q->boundingRect().right();
+}
+
+qreal QQuickShapePrivate::getImplicitHeight() const
+{
+ Q_Q(const QQuickShape);
+ return q->boundingRect().bottom();
+}
+
QQuickShape::QQuickShape(QQuickItem *parent)
: QQuickItem(*(new QQuickShapePrivate), parent)
{
@@ -639,6 +823,7 @@ QQuickShape::~QQuickShape()
/*!
\qmlproperty enumeration QtQuick.Shapes::Shape::rendererType
+ \readonly
This property determines which path rendering backend is active.
@@ -646,15 +831,33 @@ QQuickShape::~QQuickShape()
The renderer is unknown.
\value Shape.GeometryRenderer
- The generic, driver independent solution for OpenGL. Uses the same
+ The generic, driver independent solution for GPU rendering. Uses the same
CPU-based triangulation approach as QPainter's OpenGL 2 paint
- engine. This is the default when the OpenGL Qt Quick scenegraph
+ engine. This is the default when the RHI-based Qt Quick scenegraph
backend is in use.
\value Shape.SoftwareRenderer
Pure QPainter drawing using the raster paint engine. This is the
default, and only, option when the Qt Quick scenegraph is running
with the \c software backend.
+
+ \value Shape.CurveRenderer
+ GPU-based renderer that aims to preserve curvature at any scale.
+ In contrast to \c Shape.GeometryRenderer, curves are not approximated by short straight
+ lines. Instead, curves are rendered using a specialized fragment shader. This improves
+ visual quality and avoids re-tesselation performance hit when zooming. Also,
+ \c Shape.CurveRenderer provides native, high-quality anti-aliasing, without the
+ performance cost of multi- or supersampling.
+
+ By default, \c Shape.GeometryRenderer will be selected unless the Qt Quick scenegraph is running
+ with the \c software backend. In that case, \c Shape.SoftwareRenderer will be used.
+ \c Shape.CurveRenderer may be requested using the \l preferredRendererType property.
+
+ \note The \c Shape.CurveRenderer will approximate cubic curves with quadratic ones and may
+ therefore diverge slightly from the mathematically correct visualization of the shape. In
+ addition, if the shape is being rendered into a Qt Quick 3D scene and the OpenGL backend for
+ RHI is active, the \c GL_OES_standard_derivatives extension to OpenGL is required (this is
+ available by default on OpenGL ES 3 and later, but optional in OpenGL ES 2.)
*/
QQuickShape::RendererType QQuickShape::rendererType() const
@@ -664,12 +867,58 @@ QQuickShape::RendererType QQuickShape::rendererType() const
}
/*!
+ \qmlproperty enumeration QtQuick.Shapes::Shape::preferredRendererType
+ \since 6.6
+
+ Requests a specific backend to use for rendering the shape. The possible values are the same as
+ for \l rendererType. The default is \c Shape.UnknownRenderer, indicating no particular preference.
+
+ If the requested renderer type is not supported for the current Qt Quick backend, the default
+ renderer for that backend will be used instead. This will be reflected in the \l rendererType
+ when the backend is initialized.
+
+ \c Shape.SoftwareRenderer can currently not be selected without running the scenegraph with
+ the \c software backend, in which case it will be selected regardless of the
+ \c preferredRendererType.
+
+ See \l rendererType for more information on the implications.
+*/
+
+QQuickShape::RendererType QQuickShape::preferredRendererType() const
+{
+ Q_D(const QQuickShape);
+ return d->preferredType;
+}
+
+void QQuickShape::setPreferredRendererType(QQuickShape::RendererType preferredType)
+{
+ Q_D(QQuickShape);
+ if (d->preferredType == preferredType)
+ return;
+
+ d->preferredType = preferredType;
+ // (could bail out here if selectRenderType shows no change?)
+
+ for (int i = 0; i < d->sp.size(); ++i) {
+ QQuickShapePath *p = d->sp[i];
+ QQuickShapePathPrivate *pp = QQuickShapePathPrivate::get(p);
+ pp->dirty |= QQuickShapePathPrivate::DirtyAll;
+ }
+ d->spChanged = true;
+ d->_q_shapePathChanged();
+ polish();
+ update();
+
+ emit preferredRendererTypeChanged();
+}
+
+/*!
\qmlproperty bool QtQuick.Shapes::Shape::asynchronous
- When rendererType is \c Shape.GeometryRenderer, the input path is
- triangulated on the CPU during the polishing phase of the Shape. This is
- potentially expensive. To offload this work to separate worker threads,
- set this property to \c true.
+ When rendererType is \c Shape.GeometryRenderer or \c Shape.CurveRenderer, a certain amount of
+ preprocessing of the input path is performed on the CPU during the polishing phase of the
+ Shape. This is potentially expensive. To offload this work to separate worker threads, set this
+ property to \c true.
When enabled, making a Shape visible will not wait for the content to
become available. Instead, the GUI/main thread is not blocked and the
@@ -697,6 +946,26 @@ void QQuickShape::setAsynchronous(bool async)
}
/*!
+ \qmlproperty rect QtQuick.Shapes::Shape::boundingRect
+ \readonly
+ \since 6.6
+
+ Contains the united bounding rect of all sub paths in the shape.
+ */
+QRectF QQuickShape::boundingRect() const
+{
+ Q_D(const QQuickShape);
+ QRectF brect;
+ for (QQuickShapePath *path : d->sp) {
+ qreal pw = path->strokeColor().alpha() ? path->strokeWidth() : 0;
+ qreal d = path->capStyle() == QQuickShapePath::SquareCap ? pw * M_SQRT1_2 : pw / 2;
+ brect = brect.united(path->path().boundingRect().adjusted(-d, -d, d, d));
+ }
+
+ return brect;
+}
+
+/*!
\qmlproperty bool QtQuick.Shapes::Shape::vendorExtensionsEnabled
This property controls the usage of non-standard OpenGL extensions.
@@ -723,6 +992,7 @@ void QQuickShape::setVendorExtensionsEnabled(bool enable)
/*!
\qmlproperty enumeration QtQuick.Shapes::Shape::status
+ \readonly
This property determines the status of the Shape and is relevant when
Shape.asynchronous is set to \c true.
@@ -799,6 +1069,80 @@ bool QQuickShape::contains(const QPointF &point) const
return false;
}
+/*!
+ \qmlproperty enumeration QtQuick.Shapes::Shape::fillMode
+ \since QtQuick.Shapes 6.7
+
+ Set this property to define what happens when the path has a different size
+ than the item.
+
+ \value Shape.NoResize the shape is rendered at its native size, independent of the size of the item. This is the default
+ \value Shape.Stretch the shape is scaled to fit the item, changing the aspect ratio if necessary.
+ Note that non-uniform scaling may cause reduced quality of anti-aliasing when using the curve renderer
+ \value Shape.PreserveAspectFit the shape is scaled uniformly to fit inside the item
+ \value Shape.PreserveAspectCrop the shape is scaled uniformly to fill the item fully, extending outside the item if necessary.
+ Note that this only actually crops the content if \l clip is true
+*/
+
+QQuickShape::FillMode QQuickShape::fillMode() const
+{
+ Q_D(const QQuickShape);
+ return d->fillMode;
+}
+
+void QQuickShape::setFillMode(FillMode newFillMode)
+{
+ Q_D(QQuickShape);
+ if (d->fillMode == newFillMode)
+ return;
+ d->fillMode = newFillMode;
+ emit fillModeChanged();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick.Shapes::Shape::horizontalAlignment
+ \qmlproperty enumeration QtQuick.Shapes::Shape::verticalAlignment
+ \since 6.7
+
+ Sets the horizontal and vertical alignment of the shape within the item.
+ By default, the shape is aligned with \c{(0,0)} on the top left corner.
+
+ The valid values for \c horizontalAlignment are \c Shape.AlignLeft,
+ \c Shape.AlignRight and \c Shape.AlignHCenter. The valid values for
+ \c verticalAlignment are \c Shape.AlignTop, \c Shape.AlignBottom and
+ \c Shape.AlignVCenter.
+*/
+
+QQuickShape::HAlignment QQuickShape::horizontalAlignment() const
+{
+ Q_D(const QQuickShape);
+ return d->horizontalAlignment;
+}
+
+void QQuickShape::setHorizontalAlignment(HAlignment newHorizontalAlignment)
+{
+ Q_D(QQuickShape);
+ if (d->horizontalAlignment == newHorizontalAlignment)
+ return;
+ d->horizontalAlignment = newHorizontalAlignment;
+ emit horizontalAlignmentChanged();
+}
+
+QQuickShape::VAlignment QQuickShape::verticalAlignment() const
+{
+ Q_D(const QQuickShape);
+ return d->verticalAlignment;
+}
+
+void QQuickShape::setVerticalAlignment(VAlignment newVerticalAlignment)
+{
+ Q_D(QQuickShape);
+ if (d->verticalAlignment == newVerticalAlignment)
+ return;
+ d->verticalAlignment = newVerticalAlignment;
+ emit verticalAlignmentChanged();
+}
+
static void vpe_append(QQmlListProperty<QObject> *property, QObject *obj)
{
QQuickShape *item = static_cast<QQuickShape *>(property->object);
@@ -879,6 +1223,12 @@ void QQuickShape::updatePolish()
d->spChanged = false;
d->effectRefCount = currentEffectRefCount;
+ QQuickShape::RendererType expectedRenderer = d->selectRendererType();
+ if (d->rendererType != expectedRenderer) {
+ delete d->renderer;
+ d->renderer = nullptr;
+ }
+
if (!d->renderer) {
d->createRenderer();
if (!d->renderer)
@@ -904,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);
@@ -913,38 +1264,111 @@ QSGNode *QQuickShape::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
// Called on the render thread, with the gui thread blocked. We can now
// safely access gui thread data.
-
Q_D(QQuickShape);
- if (d->renderer) {
- if (!node)
+
+ if (d->renderer || d->rendererChanged) {
+ if (!node || d->rendererChanged) {
+ d->rendererChanged = false;
+ delete node;
node = d->createNode();
- d->renderer->updateNode();
+ }
+ if (d->renderer)
+ d->renderer->updateNode();
+
+ // TODO: only add transform node when needed (and then make sure static_cast is safe)
+ QMatrix4x4 fillModeTransform;
+ qreal xScale = 1.0;
+ qreal yScale = 1.0;
+
+ if (d->fillMode != NoResize) {
+ xScale = width() / implicitWidth();
+ yScale = height() / implicitHeight();
+
+ if (d->fillMode == PreserveAspectFit)
+ xScale = yScale = qMin(xScale, yScale);
+ else if (d->fillMode == PreserveAspectCrop)
+ xScale = yScale = qMax(xScale, yScale);
+ fillModeTransform.scale(xScale, yScale);
+ }
+ if (d->horizontalAlignment != AlignLeft || d->verticalAlignment != AlignTop) {
+ qreal tx = 0;
+ qreal ty = 0;
+ qreal w = xScale * implicitWidth();
+ qreal h = yScale * implicitHeight();
+ if (d->horizontalAlignment == AlignRight)
+ tx = width() - w;
+ else if (d->horizontalAlignment == AlignHCenter)
+ tx = (width() - w) / 2;
+ if (d->verticalAlignment == AlignBottom)
+ ty = height() - h;
+ else if (d->verticalAlignment == AlignVCenter)
+ ty = (height() - h) / 2;
+ fillModeTransform.translate(tx / xScale, ty / yScale);
+ }
+
+ QSGTransformNode *transformNode = static_cast<QSGTransformNode *>(node);
+ if (fillModeTransform != transformNode->matrix())
+ transformNode->setMatrix(fillModeTransform);
}
return node;
}
-// the renderer object lives on the gui thread
-void QQuickShapePrivate::createRenderer()
+QQuickShape::RendererType QQuickShapePrivate::selectRendererType()
{
+ QQuickShape::RendererType res = QQuickShape::UnknownRenderer;
Q_Q(QQuickShape);
QSGRendererInterface *ri = q->window()->rendererInterface();
if (!ri)
- return;
+ return res;
+
+ static const bool environmentPreferCurve =
+ qEnvironmentVariable("QT_QUICKSHAPES_BACKEND").toLower() == QLatin1String("curverenderer");
switch (ri->graphicsApi()) {
case QSGRendererInterface::Software:
- rendererType = QQuickShape::SoftwareRenderer;
- renderer = new QQuickShapeSoftwareRenderer;
+ res = QQuickShape::SoftwareRenderer;
break;
default:
if (QSGRendererInterface::isApiRhiBased(ri->graphicsApi())) {
- rendererType = QQuickShape::GeometryRenderer;
- renderer = new QQuickShapeGenericRenderer(q);
+ if (preferredType == QQuickShape::CurveRenderer || environmentPreferCurve) {
+ res = QQuickShape::CurveRenderer;
+ } else {
+ res = QQuickShape::GeometryRenderer;
+ }
} else {
qWarning("No path backend for this graphics API yet");
}
break;
}
+
+ return res;
+}
+
+// the renderer object lives on the gui thread
+void QQuickShapePrivate::createRenderer()
+{
+ Q_Q(QQuickShape);
+ QQuickShape::RendererType selectedType = selectRendererType();
+ if (selectedType == QQuickShape::UnknownRenderer)
+ return;
+
+ rendererType = selectedType;
+ rendererChanged = true;
+
+ switch (selectedType) {
+ case QQuickShape::SoftwareRenderer:
+ renderer = new QQuickShapeSoftwareRenderer;
+ break;
+ case QQuickShape::GeometryRenderer:
+ renderer = new QQuickShapeGenericRenderer(q);
+ break;
+ case QQuickShape::CurveRenderer:
+ renderer = new QQuickShapeCurveRenderer(q);
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
}
// the node lives on the render thread
@@ -952,29 +1376,39 @@ QSGNode *QQuickShapePrivate::createNode()
{
Q_Q(QQuickShape);
QSGNode *node = nullptr;
- if (!q->window())
+ if (!q->window() || !renderer)
return node;
QSGRendererInterface *ri = q->window()->rendererInterface();
if (!ri)
return node;
+ QSGNode *pathNode = nullptr;
switch (ri->graphicsApi()) {
case QSGRendererInterface::Software:
- node = new QQuickShapeSoftwareRenderNode(q);
+ pathNode = new QQuickShapeSoftwareRenderNode(q);
static_cast<QQuickShapeSoftwareRenderer *>(renderer)->setNode(
- static_cast<QQuickShapeSoftwareRenderNode *>(node));
+ static_cast<QQuickShapeSoftwareRenderNode *>(pathNode));
break;
default:
if (QSGRendererInterface::isApiRhiBased(ri->graphicsApi())) {
- node = new QQuickShapeGenericNode;
- static_cast<QQuickShapeGenericRenderer *>(renderer)->setRootNode(
- static_cast<QQuickShapeGenericNode *>(node));
+ if (rendererType == QQuickShape::CurveRenderer) {
+ pathNode = new QSGNode;
+ static_cast<QQuickShapeCurveRenderer *>(renderer)->setRootNode(pathNode);
+ } else {
+ pathNode = new QQuickShapeGenericNode;
+ static_cast<QQuickShapeGenericRenderer *>(renderer)->setRootNode(
+ static_cast<QQuickShapeGenericNode *>(pathNode));
+ }
} else {
qWarning("No path backend for this graphics API yet");
}
break;
}
+ // TODO: only create transform node when needed
+ node = new QSGTransformNode;
+ node->appendChildNode(pathNode);
+
return node;
}
@@ -1028,6 +1462,18 @@ void QQuickShapePrivate::sync()
renderer->setStrokeStyle(i, p->strokeStyle(), p->dashOffset(), p->dashPattern());
if (dirty & QQuickShapePathPrivate::DirtyFillGradient)
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;
}
@@ -1434,112 +1880,6 @@ void QQuickShapeConicalGradient::setAngle(qreal v)
}
}
-static void generateGradientColorTable(const QQuickShapeGradientCacheKey &gradient,
- uint *colorTable, int size, float opacity)
-{
- int pos = 0;
- const QGradientStops &s = gradient.stops;
- Q_ASSERT(!s.isEmpty());
- const bool colorInterpolation = true;
-
- uint alpha = qRound(opacity * 256);
- uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
- qreal incr = 1.0 / qreal(size);
- qreal fpos = 1.5 * incr;
- colorTable[pos++] = ARGB2RGBA(qPremultiply(current_color));
-
- while (fpos <= s.first().first) {
- colorTable[pos] = colorTable[pos - 1];
- pos++;
- fpos += incr;
- }
-
- if (colorInterpolation)
- current_color = qPremultiply(current_color);
-
- const int sLast = s.size() - 1;
- for (int i = 0; i < sLast; ++i) {
- qreal delta = 1/(s[i+1].first - s[i].first);
- uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
- if (colorInterpolation)
- next_color = qPremultiply(next_color);
-
- while (fpos < s[i+1].first && pos < size) {
- int dist = int(256 * ((fpos - s[i].first) * delta));
- int idist = 256 - dist;
- if (colorInterpolation)
- colorTable[pos] = ARGB2RGBA(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist));
- else
- colorTable[pos] = ARGB2RGBA(qPremultiply(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)));
- ++pos;
- fpos += incr;
- }
- current_color = next_color;
- }
-
- uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
- for ( ; pos < size; ++pos)
- colorTable[pos] = last_color;
-
- colorTable[size-1] = last_color;
-}
-
-QQuickShapeGradientCache::~QQuickShapeGradientCache()
-{
- qDeleteAll(m_textures);
-}
-
-QQuickShapeGradientCache *QQuickShapeGradientCache::cacheForRhi(QRhi *rhi)
-{
- static QHash<QRhi *, QQuickShapeGradientCache *> caches;
- auto it = caches.constFind(rhi);
- if (it != caches.constEnd())
- return *it;
-
- QQuickShapeGradientCache *cache = new QQuickShapeGradientCache;
- rhi->addCleanupCallback([cache](QRhi *rhi) {
- caches.remove(rhi);
- delete cache;
- });
- caches.insert(rhi, cache);
- return cache;
-}
-
-QSGTexture *QQuickShapeGradientCache::get(const QQuickShapeGradientCacheKey &grad)
-{
- QSGPlainTexture *tx = m_textures[grad];
- if (!tx) {
- static const int W = 1024; // texture size is 1024x1
- QImage gradTab(W, 1, QImage::Format_RGBA8888_Premultiplied);
- if (!grad.stops.isEmpty())
- generateGradientColorTable(grad, reinterpret_cast<uint *>(gradTab.bits()), W, 1.0f);
- else
- gradTab.fill(Qt::black);
- tx = new QSGPlainTexture;
- tx->setImage(gradTab);
- switch (grad.spread) {
- case QQuickShapeGradient::PadSpread:
- tx->setHorizontalWrapMode(QSGTexture::ClampToEdge);
- tx->setVerticalWrapMode(QSGTexture::ClampToEdge);
- break;
- case QQuickShapeGradient::RepeatSpread:
- tx->setHorizontalWrapMode(QSGTexture::Repeat);
- tx->setVerticalWrapMode(QSGTexture::Repeat);
- break;
- case QQuickShapeGradient::ReflectSpread:
- tx->setHorizontalWrapMode(QSGTexture::MirroredRepeat);
- tx->setVerticalWrapMode(QSGTexture::MirroredRepeat);
- break;
- default:
- qWarning("Unknown gradient spread mode %d", grad.spread);
- break;
- }
- tx->setFiltering(QSGTexture::Linear);
- m_textures[grad] = tx;
- }
- return tx;
-}
-
QT_END_NAMESPACE
#include "moc_qquickshape_p.cpp"
diff --git a/src/quickshapes/qquickshape_p.h b/src/quickshapes/qquickshape_p.h
index f1e2eea546..3ed4927c2d 100644
--- a/src/quickshapes/qquickshape_p.h
+++ b/src/quickshapes/qquickshape_p.h
@@ -27,15 +27,15 @@ QT_BEGIN_NAMESPACE
class QQuickShapePathPrivate;
class QQuickShapePrivate;
-void Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapes_initializeModule();
+void Q_QUICKSHAPES_EXPORT QQuickShapes_initializeModule();
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapesModule
+class Q_QUICKSHAPES_EXPORT QQuickShapesModule
{
public:
static void defineModule();
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapeGradient : public QQuickGradient
+class Q_QUICKSHAPES_EXPORT QQuickShapeGradient : public QQuickGradient
{
Q_OBJECT
Q_PROPERTY(SpreadMode spread READ spread WRITE setSpread NOTIFY spreadChanged)
@@ -65,7 +65,7 @@ private:
SpreadMode m_spread;
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapeLinearGradient : public QQuickShapeGradient
+class Q_QUICKSHAPES_EXPORT QQuickShapeLinearGradient : public QQuickShapeGradient
{
Q_OBJECT
Q_PROPERTY(qreal x1 READ x1 WRITE setX1 NOTIFY x1Changed)
@@ -99,7 +99,7 @@ private:
QPointF m_end;
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapeRadialGradient : public QQuickShapeGradient
+class Q_QUICKSHAPES_EXPORT QQuickShapeRadialGradient : public QQuickShapeGradient
{
Q_OBJECT
Q_PROPERTY(qreal centerX READ centerX WRITE setCenterX NOTIFY centerXChanged)
@@ -148,7 +148,7 @@ private:
qreal m_focalRadius = 0;
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapeConicalGradient : public QQuickShapeGradient
+class Q_QUICKSHAPES_EXPORT QQuickShapeConicalGradient : public QQuickShapeGradient
{
Q_OBJECT
Q_PROPERTY(qreal centerX READ centerX WRITE setCenterX NOTIFY centerXChanged)
@@ -180,7 +180,7 @@ private:
qreal m_angle = 0;
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapePath : public QQuickPath
+class Q_QUICKSHAPES_EXPORT QQuickShapePath : public QQuickPath
{
Q_OBJECT
@@ -196,6 +196,9 @@ class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapePath : public QQuickPath
Q_PROPERTY(QVector<qreal> dashPattern READ dashPattern WRITE setDashPattern NOTIFY dashPatternChanged)
Q_PROPERTY(QQuickShapeGradient *fillGradient READ fillGradient WRITE setFillGradient RESET resetFillGradient)
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)
@@ -226,6 +229,18 @@ public:
};
Q_ENUM(StrokeStyle)
+ enum PathHint {
+ PathLinear = 0x1,
+ PathQuadratic = 0x2,
+ PathConvex = 0x4,
+ PathFillOnRight = 0x8,
+ PathSolid = 0x10,
+ PathNonIntersecting = 0x20,
+ PathNonOverlappingControlPointTriangles = 0x40
+ };
+ Q_DECLARE_FLAGS(PathHints, PathHint)
+ Q_FLAG(PathHints)
+
QQuickShapePath(QObject *parent = nullptr);
~QQuickShapePath();
@@ -263,6 +278,15 @@ public:
void setFillGradient(QQuickShapeGradient *gradient);
void resetFillGradient();
+ PathHints pathHints() const;
+ void setPathHints(PathHints newPathHints);
+
+ QMatrix4x4 fillTransform() const;
+ void setFillTransform(const QMatrix4x4 &matrix);
+
+ QQuickItem *fillItem() const;
+ void setFillItem(QQuickItem *newFillItem);
+
Q_SIGNALS:
void shapePathChanged();
void strokeColorChanged();
@@ -276,20 +300,32 @@ Q_SIGNALS:
void dashOffsetChanged();
void dashPatternChanged();
+ 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_PRIVATE_EXPORT QQuickShape : public QQuickItem
+class Q_QUICKSHAPES_EXPORT QQuickShape : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(RendererType rendererType READ rendererType NOTIFY rendererChanged)
Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged)
Q_PROPERTY(bool vendorExtensionsEnabled READ vendorExtensionsEnabled WRITE setVendorExtensionsEnabled NOTIFY vendorExtensionsEnabledChanged)
+ Q_PROPERTY(RendererType preferredRendererType READ preferredRendererType
+ WRITE setPreferredRendererType NOTIFY preferredRendererTypeChanged REVISION(6, 6) FINAL)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(ContainsMode containsMode READ containsMode WRITE setContainsMode NOTIFY containsModeChanged REVISION(1, 11))
+ Q_PROPERTY(QRectF boundingRect READ boundingRect NOTIFY boundingRectChanged REVISION(6, 6) FINAL)
+ Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged REVISION(6, 7) FINAL)
+ Q_PROPERTY(HAlignment horizontalAlignment READ horizontalAlignment WRITE setHorizontalAlignment NOTIFY horizontalAlignmentChanged REVISION(6, 7) FINAL)
+ Q_PROPERTY(VAlignment verticalAlignment READ verticalAlignment WRITE setVerticalAlignment NOTIFY verticalAlignmentChanged REVISION(6, 7) FINAL)
+
Q_PROPERTY(QQmlListProperty<QObject> data READ data)
Q_CLASSINFO("DefaultProperty", "data")
QML_NAMED_ELEMENT(Shape)
@@ -300,7 +336,8 @@ public:
UnknownRenderer,
GeometryRenderer,
NvprRenderer,
- SoftwareRenderer
+ SoftwareRenderer,
+ CurveRenderer
};
Q_ENUM(RendererType)
@@ -317,6 +354,23 @@ public:
};
Q_ENUM(ContainsMode)
+ enum FillMode {
+ NoResize,
+ PreserveAspectFit,
+ PreserveAspectCrop,
+ Stretch
+ };
+ Q_ENUM(FillMode)
+
+ enum HAlignment { AlignLeft = Qt::AlignLeft,
+ AlignRight = Qt::AlignRight,
+ AlignHCenter = Qt::AlignHCenter };
+ Q_ENUM(HAlignment)
+ enum VAlignment { AlignTop = Qt::AlignTop,
+ AlignBottom = Qt::AlignBottom,
+ AlignVCenter = Qt::AlignVCenter };
+ Q_ENUM(VAlignment)
+
QQuickShape(QQuickItem *parent = nullptr);
~QQuickShape();
@@ -325,6 +379,11 @@ public:
bool asynchronous() const;
void setAsynchronous(bool async);
+ Q_REVISION(6, 6) RendererType preferredRendererType() const;
+ Q_REVISION(6, 6) void setPreferredRendererType(RendererType preferredType);
+
+ Q_REVISION(6, 6) QRectF boundingRect() const override;
+
bool vendorExtensionsEnabled() const;
void setVendorExtensionsEnabled(bool enable);
@@ -337,6 +396,15 @@ public:
QQmlListProperty<QObject> data();
+ Q_REVISION(6, 7) FillMode fillMode() const;
+ Q_REVISION(6, 7) void setFillMode(FillMode newFillMode);
+
+ Q_REVISION(6, 7) HAlignment horizontalAlignment() const;
+ Q_REVISION(6, 7) void setHorizontalAlignment(HAlignment newHorizontalAlignment);
+
+ Q_REVISION(6, 7) VAlignment verticalAlignment() const;
+ Q_REVISION(6, 7) void setVerticalAlignment(VAlignment newVerticalAlignment);
+
protected:
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
void updatePolish() override;
@@ -349,8 +417,14 @@ Q_SIGNALS:
void asynchronousChanged();
void vendorExtensionsEnabledChanged();
void statusChanged();
+ Q_REVISION(6, 6) void preferredRendererTypeChanged();
+ Q_REVISION(6, 6) void boundingRectChanged();
Q_REVISION(1, 11) void containsModeChanged();
+ Q_REVISION(6, 7) void fillModeChanged();
+ Q_REVISION(6, 7) void horizontalAlignmentChanged();
+ Q_REVISION(6, 7) void verticalAlignmentChanged();
+
private:
Q_DISABLE_COPY(QQuickShape)
Q_DECLARE_PRIVATE(QQuickShape)
@@ -359,6 +433,4 @@ private:
QT_END_NAMESPACE
-QML_DECLARE_TYPE(QQuickShape)
-
#endif // QQUICKSHAPE_P_H
diff --git a/src/quickshapes/qquickshape_p_p.h b/src/quickshapes/qquickshape_p_p.h
index 245ba659b2..cce52d876f 100644
--- a/src/quickshapes/qquickshape_p_p.h
+++ b/src/quickshapes/qquickshape_p_p.h
@@ -17,7 +17,8 @@
#include <QtQuickShapes/private/qquickshapesglobal_p.h>
#include <QtQuickShapes/private/qquickshape_p.h>
-#include <QtQuick/private/qquickitem_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qsgtransform_p.h>
#include <QPainterPath>
#include <QColor>
#include <QBrush>
@@ -37,16 +38,7 @@ public:
SupportsAsync = 0x01
};
Q_DECLARE_FLAGS(Flags, Flag)
-
enum FillGradientType { NoGradient = 0, LinearGradient, RadialGradient, ConicalGradient };
- struct GradientDesc { // can fully describe a linear/radial/conical gradient
- QGradientStops stops;
- QQuickShapeGradient::SpreadMode spread;
- QPointF a; // start (L) or center point (R/C)
- QPointF b; // end (L) or focal point (R)
- qreal v0; // center radius (R) or start angle (C)
- qreal v1; // focal radius (R)
- };
virtual ~QQuickAbstractPathRenderer() { }
@@ -65,7 +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;
@@ -88,9 +83,11 @@ struct QQuickShapeStrokeFillParams
qreal dashOffset;
QVector<qreal> dashPattern;
QQuickShapeGradient *fillGradient;
+ QSGTransform fillTransform;
+ QQuickItem *fillItem;
};
-class Q_QUICKSHAPES_PRIVATE_EXPORT QQuickShapePathPrivate : public QQuickPathPrivate
+class Q_QUICKSHAPES_EXPORT QQuickShapePathPrivate : public QQuickPathPrivate
{
Q_DECLARE_PUBLIC(QQuickShapePath)
@@ -104,19 +101,25 @@ public:
DirtyStyle = 0x20,
DirtyDash = 0x40,
DirtyFillGradient = 0x80,
+ DirtyFillTransform = 0x100,
+ DirtyFillItem = 0x200,
- DirtyAll = 0xFF
+ DirtyAll = 0x3FF
};
QQuickShapePathPrivate();
void _q_pathChanged();
void _q_fillGradientChanged();
+ void _q_fillItemDestroyed();
+
+ void handleSceneChange();
static QQuickShapePathPrivate *get(QQuickShapePath *p) { return p->d_func(); }
int dirty;
QQuickShapeStrokeFillParams sfp;
+ QQuickShapePath::PathHints pathHints;
};
class QQuickShapePrivate : public QQuickItemPrivate
@@ -127,17 +130,22 @@ public:
QQuickShapePrivate();
~QQuickShapePrivate();
+ QQuickShape::RendererType selectRendererType();
void createRenderer();
QSGNode *createNode();
void sync();
void _q_shapePathChanged();
void setStatus(QQuickShape::Status newStatus);
+ void handleSceneChange(QQuickWindow *w);
static QQuickShapePrivate *get(QQuickShape *item) { return item->d_func(); }
static void asyncShapeReady(void *data);
+ qreal getImplicitWidth() const override;
+ qreal getImplicitHeight() const override;
+
int effectRefCount;
QVector<QQuickShapePath *> sp;
QElapsedTimer syncTimer;
@@ -146,46 +154,20 @@ public:
int syncTimeCounter = 0;
QQuickShape::Status status = QQuickShape::Null;
QQuickShape::RendererType rendererType = QQuickShape::UnknownRenderer;
+ QQuickShape::RendererType preferredType = QQuickShape::UnknownRenderer;
QQuickShape::ContainsMode containsMode = QQuickShape::BoundingRectContains;
+ QQuickShape::FillMode fillMode = QQuickShape::NoResize;
+ QQuickShape::HAlignment horizontalAlignment = QQuickShape::AlignLeft;
+ QQuickShape::VAlignment verticalAlignment = QQuickShape::AlignTop;
+
bool spChanged = false;
+ bool rendererChanged = false;
bool async = false;
bool enableVendorExts = false;
bool syncTimingActive = false;
qreal triangulationScale = 1.0;
};
-struct QQuickShapeGradientCacheKey
-{
- QQuickShapeGradientCacheKey(const QGradientStops &stops, QQuickShapeGradient::SpreadMode spread)
- : stops(stops), spread(spread)
- { }
- QGradientStops stops;
- QQuickShapeGradient::SpreadMode spread;
- bool operator==(const QQuickShapeGradientCacheKey &other) const
- {
- return spread == other.spread && stops == other.stops;
- }
-};
-
-inline size_t qHash(const QQuickShapeGradientCacheKey &v, size_t seed = 0)
-{
- size_t h = seed + v.spread;
- for (int i = 0; i < 3 && i < v.stops.size(); ++i)
- h += v.stops[i].second.rgba();
- return h;
-}
-
-class QQuickShapeGradientCache
-{
-public:
- ~QQuickShapeGradientCache();
- static QQuickShapeGradientCache *cacheForRhi(QRhi *rhi);
- QSGTexture *get(const QQuickShapeGradientCacheKey &grad);
-
-private:
- QHash<QQuickShapeGradientCacheKey, QSGPlainTexture *> m_textures;
-};
-
QT_END_NAMESPACE
#endif
diff --git a/src/quickshapes/qquickshapecurverenderer.cpp b/src/quickshapes/qquickshapecurverenderer.cpp
new file mode 100644
index 0000000000..6c16df449b
--- /dev/null
+++ b/src/quickshapes/qquickshapecurverenderer.cpp
@@ -0,0 +1,782 @@
+// 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"
+#include "qquickshapecurverenderer_p_p.h"
+
+#if QT_CONFIG(thread)
+#include <QtCore/qthreadpool.h>
+#endif
+
+#include <QtGui/qvector2d.h>
+#include <QtGui/qvector4d.h>
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtGui/private/qtriangulatingstroker_p.h>
+#include <QtGui/private/qrhi_p.h>
+
+#include <QtQuick/private/qsgcurvefillnode_p.h>
+#include <QtQuick/private/qsgcurvestrokenode_p.h>
+#include <QtQuick/private/qquadpath_p.h>
+#include <QtQuick/private/qsgcurveprocessor_p.h>
+#include <QtQuick/qsgmaterial.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcShapeCurveRenderer, "qt.shape.curverenderer");
+
+namespace {
+
+class QQuickShapeWireFrameMaterialShader : public QSGMaterialShader
+{
+public:
+ QQuickShapeWireFrameMaterialShader(int viewCount)
+ {
+ setShaderFileName(VertexStage,
+ QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage,
+ QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"), viewCount);
+ }
+
+ bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *) override
+ {
+ bool changed = false;
+ QByteArray *buf = state.uniformData();
+ Q_ASSERT(buf->size() >= 64);
+ const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
+
+ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
+ if (state.isMatrixDirty()) {
+ const QMatrix4x4 m = state.combinedMatrix(viewIndex);
+ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+};
+
+class QQuickShapeWireFrameMaterial : public QSGMaterial
+{
+public:
+ QQuickShapeWireFrameMaterial()
+ {
+ setFlag(Blending, true);
+ }
+
+ int compare(const QSGMaterial *other) const override
+ {
+ return (type() - other->type());
+ }
+
+protected:
+ QSGMaterialType *type() const override
+ {
+ static QSGMaterialType t;
+ return &t;
+ }
+ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
+ {
+ return new QQuickShapeWireFrameMaterialShader(viewCount());
+ }
+
+};
+
+class QQuickShapeWireFrameNode : public QSGCurveAbstractNode
+{
+public:
+ struct WireFrameVertex
+ {
+ float x, y, u, v, w;
+ };
+
+ QQuickShapeWireFrameNode()
+ {
+ setFlag(OwnsGeometry, true);
+ setGeometry(new QSGGeometry(attributes(), 0, 0));
+ activateMaterial();
+ }
+
+ void setColor(QColor col) override
+ {
+ Q_UNUSED(col);
+ }
+
+ void activateMaterial()
+ {
+ m_material.reset(new QQuickShapeWireFrameMaterial);
+ setMaterial(m_material.data());
+ }
+
+ static const QSGGeometry::AttributeSet &attributes()
+ {
+ static QSGGeometry::Attribute data[] = {
+ QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
+ QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
+ };
+ static QSGGeometry::AttributeSet attrs = { 2, sizeof(WireFrameVertex), data };
+ return attrs;
+ }
+
+ void cookGeometry() override
+ {
+ // Intentionally empty
+ }
+
+protected:
+ QScopedPointer<QQuickShapeWireFrameMaterial> m_material;
+};
+}
+
+QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer()
+{
+ for (const PathData &pd : std::as_const(m_paths)) {
+ if (pd.currentRunner)
+ pd.currentRunner->orphaned = true;
+ }
+}
+
+void QQuickShapeCurveRenderer::beginSync(int totalCount, bool *countChanged)
+{
+ if (countChanged != nullptr && totalCount != m_paths.size())
+ *countChanged = true;
+ m_paths.resize(totalCount);
+}
+
+void QQuickShapeCurveRenderer::setPath(int index, const QQuickPath *path)
+{
+ constexpr QQuickShapePath::PathHints noHints;
+ const auto *shapePath = qobject_cast<const QQuickShapePath *>(path);
+ setPath(index, path->path(), shapePath ? shapePath->pathHints() : noHints);
+}
+
+void QQuickShapeCurveRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints)
+{
+ auto &pathData = m_paths[index];
+ pathData.originalPath = path;
+ pathData.pathHints = pathHints;
+ pathData.m_dirty |= PathDirty;
+}
+
+void QQuickShapeCurveRenderer::setStrokeColor(int index, const QColor &color)
+{
+ auto &pathData = m_paths[index];
+ const bool wasVisible = pathData.isStrokeVisible();
+ pathData.pen.setColor(color);
+ if (pathData.isStrokeVisible() != wasVisible)
+ pathData.m_dirty |= StrokeDirty;
+ else
+ pathData.m_dirty |= UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::setStrokeWidth(int index, qreal w)
+{
+ auto &pathData = m_paths[index];
+ if (w > 0) {
+ pathData.validPenWidth = true;
+ pathData.pen.setWidthF(w);
+ } else {
+ pathData.validPenWidth = false;
+ }
+ pathData.m_dirty |= StrokeDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillColor(int index, const QColor &color)
+{
+ auto &pathData = m_paths[index];
+ const bool wasVisible = pathData.isFillVisible();
+ pathData.fillColor = color;
+ if (pathData.isFillVisible() != wasVisible)
+ pathData.m_dirty |= FillDirty;
+ else
+ pathData.m_dirty |= UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
+{
+ auto &pathData = m_paths[index];
+ pathData.fillRule = Qt::FillRule(fillRule);
+ pathData.m_dirty |= PathDirty;
+}
+
+void QQuickShapeCurveRenderer::setJoinStyle(int index,
+ QQuickShapePath::JoinStyle joinStyle,
+ int miterLimit)
+{
+ auto &pathData = m_paths[index];
+ pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
+ pathData.pen.setMiterLimit(miterLimit);
+ pathData.m_dirty |= StrokeDirty;
+}
+
+void QQuickShapeCurveRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
+{
+ auto &pathData = m_paths[index];
+ pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
+ pathData.m_dirty |= StrokeDirty;
+}
+
+void QQuickShapeCurveRenderer::setStrokeStyle(int index,
+ QQuickShapePath::StrokeStyle strokeStyle,
+ qreal dashOffset,
+ const QVector<qreal> &dashPattern)
+{
+ auto &pathData = m_paths[index];
+ pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
+ if (strokeStyle == QQuickShapePath::DashLine) {
+ pathData.pen.setDashPattern(dashPattern);
+ pathData.pen.setDashOffset(dashOffset);
+ }
+ pathData.m_dirty |= StrokeDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
+{
+ PathData &pd(m_paths[index]);
+ const bool wasVisible = pd.isFillVisible();
+ pd.gradientType = QGradient::NoGradient;
+ if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) {
+ pd.gradientType = QGradient::LinearGradient;
+ pd.gradient.stops = gradient->gradientStops();
+ pd.gradient.spread = QGradient::Spread(gradient->spread());
+ pd.gradient.a = QPointF(g->x1(), g->y1());
+ pd.gradient.b = QPointF(g->x2(), g->y2());
+ } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) {
+ pd.gradientType = QGradient::RadialGradient;
+ pd.gradient.a = QPointF(g->centerX(), g->centerY());
+ pd.gradient.b = QPointF(g->focalX(), g->focalY());
+ pd.gradient.v0 = g->centerRadius();
+ pd.gradient.v1 = g->focalRadius();
+ } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(gradient)) {
+ pd.gradientType = QGradient::ConicalGradient;
+ pd.gradient.a = QPointF(g->centerX(), g->centerY());
+ pd.gradient.v0 = g->angle();
+ } else if (gradient != nullptr) {
+ static bool warned = false;
+ if (!warned) {
+ warned = true;
+ qCWarning(lcShapeCurveRenderer) << "Unsupported gradient fill";
+ }
+ }
+
+ if (pd.gradientType != QGradient::NoGradient) {
+ pd.gradient.stops = gradient->gradientStops();
+ pd.gradient.spread = QGradient::Spread(gradient->spread());
+ }
+
+ pd.m_dirty |= (pd.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillTransform(int index, const QSGTransform &transform)
+{
+ auto &pathData = m_paths[index];
+ pathData.fillTransform = transform;
+ pathData.m_dirty |= UniformsDirty;
+}
+
+void QQuickShapeCurveRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
+{
+ auto &pathData = m_paths[index];
+ const bool wasVisible = pathData.isFillVisible();
+ 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 |= (pathData.isFillVisible() != wasVisible) ? FillDirty : 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)
+{
+ m_asyncCallback = callback;
+ m_asyncCallbackData = data;
+}
+
+void QQuickShapeCurveRenderer::endSync(bool async)
+{
+ bool didKickOffAsync = false;
+
+ for (PathData &pathData : m_paths) {
+ if (!pathData.m_dirty)
+ continue;
+
+ if (pathData.m_dirty == UniformsDirty) {
+ // Requires no curve node computation, gets handled directly in updateNode()
+ continue;
+ }
+
+ if (pathData.currentRunner) {
+ // Already performing async computing. New dirty flags will be handled in the next sync
+ // after the current computation is done and the item is updated
+ continue;
+ }
+
+ createRunner(&pathData);
+
+#if QT_CONFIG(thread)
+ if (async) {
+ pathData.currentRunner->isAsync = true;
+ QThreadPool::globalInstance()->start(pathData.currentRunner);
+ didKickOffAsync = true;
+ } else
+#endif
+ {
+ pathData.currentRunner->run();
+ }
+ }
+
+ if (async && !didKickOffAsync && m_asyncCallback)
+ m_asyncCallback(m_asyncCallbackData);
+}
+
+void QQuickShapeCurveRenderer::createRunner(PathData *pathData)
+{
+ Q_ASSERT(!pathData->currentRunner);
+ QQuickShapeCurveRunnable *runner = new QQuickShapeCurveRunnable;
+ runner->setAutoDelete(false);
+ runner->pathData = *pathData;
+ runner->pathData.fillNodes.clear();
+ runner->pathData.strokeNodes.clear();
+ runner->pathData.currentRunner = nullptr;
+
+ pathData->currentRunner = runner;
+ pathData->m_dirty = 0;
+ QObject::connect(runner, &QQuickShapeCurveRunnable::done, qApp,
+ [this](QQuickShapeCurveRunnable *r) {
+ r->isDone = true;
+ if (r->orphaned) {
+ r->deleteLater(); // Renderer was destroyed
+ } else if (r->isAsync) {
+ maybeUpdateAsyncItem();
+ }
+ });
+}
+
+void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
+{
+ for (const PathData &pd : std::as_const(m_paths)) {
+ if (pd.currentRunner && !pd.currentRunner->isDone)
+ return;
+ }
+ if (m_item)
+ m_item->update();
+ if (m_asyncCallback)
+ m_asyncCallback(m_asyncCallbackData);
+}
+
+void QQuickShapeCurveRunnable::run()
+{
+ QQuickShapeCurveRenderer::processPath(&pathData);
+ emit done(this);
+}
+
+void QQuickShapeCurveRenderer::updateNode()
+{
+ if (!m_rootNode)
+ return;
+
+ auto updateUniforms = [](const PathData &pathData) {
+ for (auto &pathNode : std::as_const(pathData.fillNodes)) {
+ QSGCurveFillNode *fillNode = static_cast<QSGCurveFillNode *>(pathNode);
+ fillNode->setColor(pathData.fillColor);
+ fillNode->setGradientType(pathData.gradientType);
+ fillNode->setFillGradient(pathData.gradient);
+ fillNode->setFillTransform(pathData.fillTransform);
+ fillNode->setFillTextureProvider(pathData.fillTextureProviderItem != nullptr
+ ? pathData.fillTextureProviderItem->textureProvider()
+ : nullptr);
+ }
+ for (auto &strokeNode : std::as_const(pathData.strokeNodes))
+ strokeNode->setColor(pathData.pen.color());
+ };
+
+ NodeList toBeDeleted;
+
+ for (int i = 0; i < m_paths.size(); i++) {
+ PathData &pathData = m_paths[i];
+ if (pathData.currentRunner) {
+ if (!pathData.currentRunner->isDone)
+ continue;
+ // Find insertion point for new nodes. Default is the first stroke node of this path
+ QSGNode *nextNode = pathData.strokeNodes.value(0);
+ // If that is 0, use the first node (stroke or fill) of later paths, if any
+ for (int j = i + 1; !nextNode && j < m_paths.size(); j++) {
+ const PathData &pd = m_paths[j];
+ nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(0) : pd.fillNodes.value(0);
+ }
+
+ const PathData &newData = pathData.currentRunner->pathData;
+ if (newData.m_dirty & PathDirty)
+ pathData.path = newData.path;
+ if (newData.m_dirty & FillDirty) {
+ pathData.fillPath = newData.fillPath;
+ for (auto *node : std::as_const(newData.fillNodes)) {
+ if (nextNode)
+ m_rootNode->insertChildNodeBefore(node, nextNode);
+ else
+ m_rootNode->appendChildNode(node);
+ }
+ toBeDeleted += pathData.fillNodes;
+ pathData.fillNodes = newData.fillNodes;
+ }
+ if (newData.m_dirty & StrokeDirty) {
+ for (auto *node : std::as_const(newData.strokeNodes)) {
+ if (nextNode)
+ m_rootNode->insertChildNodeBefore(node, nextNode);
+ else
+ m_rootNode->appendChildNode(node);
+ }
+ toBeDeleted += pathData.strokeNodes;
+ pathData.strokeNodes = newData.strokeNodes;
+ }
+
+ if (newData.m_dirty & UniformsDirty)
+ updateUniforms(pathData);
+
+ // if (pathData.m_dirty && pathData.m_dirty != UniformsDirty && currentRunner.isAsync)
+ // qDebug("### should enqueue a new sync?");
+
+ pathData.currentRunner->deleteLater();
+ pathData.currentRunner = nullptr;
+ }
+
+ if (pathData.m_dirty == UniformsDirty) {
+ // Simple case so no runner was created in endSync(); handle it directly here
+ updateUniforms(pathData);
+ pathData.m_dirty = 0;
+ }
+ }
+ qDeleteAll(toBeDeleted); // also removes them from m_rootNode's child list
+}
+
+void QQuickShapeCurveRenderer::processPath(PathData *pathData)
+{
+ static const bool doOverlapSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
+ static const bool doIntersetionSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
+ static const bool useTriangulatingStroker = qEnvironmentVariableIntValue("QT_QUICKSHAPES_TRIANGULATING_STROKER");
+ static const bool simplifyPath = qEnvironmentVariableIntValue("QT_QUICKSHAPES_SIMPLIFY_PATHS");
+
+ int &dirtyFlags = pathData->m_dirty;
+
+ if (dirtyFlags & PathDirty) {
+ if (simplifyPath)
+ pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified(), QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
+ else
+ pathData->path = QQuadPath::fromPainterPath(pathData->originalPath, QQuadPath::PathHints(int(pathData->pathHints)));
+ pathData->path.setFillRule(pathData->fillRule);
+ pathData->fillPath = {};
+ dirtyFlags |= (FillDirty | StrokeDirty);
+ }
+
+ if (dirtyFlags & FillDirty) {
+ if (pathData->isFillVisible()) {
+ if (pathData->fillPath.isEmpty()) {
+ pathData->fillPath = pathData->path.subPathsClosed();
+ if (doIntersetionSolving)
+ QSGCurveProcessor::solveIntersections(pathData->fillPath);
+ pathData->fillPath.addCurvatureData();
+ if (doOverlapSolving)
+ QSGCurveProcessor::solveOverlaps(pathData->fillPath);
+ }
+ pathData->fillNodes = addFillNodes(pathData->fillPath);
+ dirtyFlags |= (StrokeDirty | UniformsDirty);
+ }
+ }
+
+ if (dirtyFlags & StrokeDirty) {
+ if (pathData->isStrokeVisible()) {
+ const QPen &pen = pathData->pen;
+ if (pen.style() == Qt::SolidLine)
+ pathData->strokePath = pathData->path;
+ else
+ pathData->strokePath = pathData->path.dashed(pen.widthF(), pen.dashPattern(), pen.dashOffset());
+
+ if (useTriangulatingStroker)
+ pathData->strokeNodes = addTriangulatingStrokerNodes(*pathData);
+ else
+ pathData->strokeNodes = addCurveStrokeNodes(*pathData);
+ }
+ }
+}
+
+QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const QQuadPath &path)
+{
+ auto *node = new QSGCurveFillNode;
+ const qsizetype approxDataCount = 20 * path.elementCount();
+ node->reserve(approxDataCount);
+
+ NodeList ret;
+ QPainterPath internalHull;
+ internalHull.setFillRule(path.fillRule());
+
+ bool visualizeDebug = debugVisualization() & DebugCurves;
+ const float dbg = visualizeDebug ? 0.5f : 0.0f;
+ node->setDebug(dbg);
+
+ QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
+ wfVertices.reserve(approxDataCount);
+
+ QSGCurveProcessor::processFill(path,
+ path.fillRule(),
+ [&wfVertices, &node](const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n,
+ QSGCurveProcessor::uvForPointCallback uvForPoint)
+ {
+ node->appendTriangle(v, n, uvForPoint);
+
+ wfVertices.append({v.at(0).x(), v.at(0).y(), 1.0f, 0.0f, 0.0f}); // 0
+ wfVertices.append({v.at(1).x(), v.at(1).y(), 0.0f, 1.0f, 0.0f}); // 1
+ wfVertices.append({v.at(2).x(), v.at(2).y(), 0.0f, 0.0f, 1.0f}); // 2
+ });
+
+ QVector<quint32> indices = node->uncookedIndexes();
+ if (indices.size() > 0) {
+ node->cookGeometry();
+ ret.append(node);
+ }
+
+ const bool wireFrame = debugVisualization() & DebugWireframe;
+ if (wireFrame) {
+ QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
+ QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
+ wfVertices.size(),
+ indices.size(),
+ QSGGeometry::UnsignedIntType);
+ wfNode->setGeometry(wfg);
+
+ wfg->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(wfg->indexData(),
+ indices.data(),
+ indices.size() * wfg->sizeOfIndex());
+ memcpy(wfg->vertexData(),
+ wfVertices.data(),
+ wfg->vertexCount() * wfg->sizeOfVertex());
+
+ ret.append(wfNode);
+ }
+
+ return ret;
+}
+
+QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const PathData &pathData)
+{
+ NodeList ret;
+ const QColor &color = pathData.pen.color();
+
+ QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
+
+ QTriangulatingStroker stroker;
+ const auto painterPath = pathData.strokePath.toPainterPath();
+ const QVectorPath &vp = qtVectorPathForPath(painterPath);
+ QPen pen = pathData.pen;
+ stroker.process(vp, pen, {}, {});
+
+ auto *node = new QSGCurveFillNode;
+ node->setGradientType(pathData.gradientType);
+
+ auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p)
+ {
+ double divisor = v1.x() * v2.y() - v2.x() * v1.y();
+
+ float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
+ float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
+
+ return QVector2D(u, v);
+ };
+
+ // Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
+ // also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
+ auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
+ {
+ QVector2D v1 = 2 * (p1 - p0);
+ QVector2D v2 = p2 - v1 - p0;
+ return uvForPoint(v1, v2, p - p0);
+ };
+
+ auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){
+
+ QVector2D baseLine = endPoint - startPoint;
+ QVector2D insideVector = referencePoint - startPoint;
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle
+
+ bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
+
+ return swap ? startPoint + normal : startPoint - normal;
+ };
+
+ static bool disableExtraTriangles = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
+
+ auto addStrokeTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, bool){
+ if (p1 == p2 || p2 == p3) {
+ return;
+ }
+
+ auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
+ auto uv = curveUv(p1, p2, p3, p);
+ return QVector3D(uv.x(), uv.y(), 0.0f); // Line
+ };
+
+ node->appendTriangle(p1, p2, p3, uvForPoint);
+
+
+ wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); // 0
+ wfVertices.append({p2.x(), p2.y(), 0.0f, 0.1f, 0.0f}); // 1
+ wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f}); // 2
+
+ if (!disableExtraTriangles) {
+ // Add a triangle on the outer side of the line to get some more AA
+ // The new point replaces p2 (currentVertex+1)
+ QVector2D op = findPointOtherSide(p1, p3, p2);
+ node->appendTriangle(p1, op, p3, uvForPoint);
+
+ wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f});
+ wfVertices.append({op.x(), op.y(), 0.0f, 1.0f, 0.0f}); // replacing p2
+ wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f});
+ }
+ };
+
+ const int vertCount = stroker.vertexCount() / 2;
+ const float *verts = stroker.vertices();
+ for (int i = 0; i < vertCount - 2; ++i) {
+ QVector2D p[3];
+ for (int j = 0; j < 3; ++j) {
+ p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
+ }
+ bool isOdd = i % 2;
+ addStrokeTriangle(p[0], p[1], p[2], isOdd);
+ }
+
+ QVector<quint32> indices = node->uncookedIndexes();
+ if (indices.size() > 0) {
+ node->setColor(color);
+ node->setFillGradient(pathData.gradient);
+
+ node->cookGeometry();
+ ret.append(node);
+ }
+ const bool wireFrame = debugVisualization() & DebugWireframe;
+ if (wireFrame) {
+ QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
+ QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
+ wfVertices.size(),
+ indices.size(),
+ QSGGeometry::UnsignedIntType);
+ wfNode->setGeometry(wfg);
+
+ wfg->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(wfg->indexData(),
+ indices.data(),
+ indices.size() * wfg->sizeOfIndex());
+ memcpy(wfg->vertexData(),
+ wfVertices.data(),
+ wfg->vertexCount() * wfg->sizeOfVertex());
+
+ ret.append(wfNode);
+ }
+
+ return ret;
+}
+
+void QQuickShapeCurveRenderer::setRootNode(QSGNode *node)
+{
+ m_rootNode = node;
+}
+
+int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
+
+int QQuickShapeCurveRenderer::debugVisualization()
+{
+ static const int envFlags = qEnvironmentVariableIntValue("QT_QUICKSHAPES_DEBUG");
+ return debugVisualizationFlags | envFlags;
+}
+
+void QQuickShapeCurveRenderer::setDebugVisualization(int options)
+{
+ if (debugVisualizationFlags == options)
+ return;
+ debugVisualizationFlags = options;
+}
+
+QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData)
+{
+ NodeList ret;
+ const QColor &color = pathData.pen.color();
+
+ const bool debug = debugVisualization() & DebugCurves;
+ auto *node = new QSGCurveStrokeNode;
+ node->setDebug(0.2f * debug);
+ QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
+
+ const float miterLimit = pathData.pen.miterLimit();
+ const float penWidth = pathData.pen.widthF();
+
+ static const int subdivisions = qEnvironmentVariable("QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral("3")).toInt();
+
+ QSGCurveProcessor::processStroke(pathData.strokePath,
+ miterLimit,
+ penWidth,
+ pathData.pen.joinStyle(),
+ pathData.pen.capStyle(),
+ [&wfVertices, &node](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine)
+ {
+ const QVector2D &p0 = s.at(0);
+ const QVector2D &p1 = s.at(1);
+ const QVector2D &p2 = s.at(2);
+ if (isLine)
+ node->appendTriangle(s, std::array<QVector2D, 2>{p.at(0), p.at(2)}, n);
+ else
+ node->appendTriangle(s, p, n);
+
+ wfVertices.append({p0.x(), p0.y(), 1.0f, 0.0f, 0.0f});
+ wfVertices.append({p1.x(), p1.y(), 0.0f, 1.0f, 0.0f});
+ wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f});
+ },
+ subdivisions);
+
+ auto indexCopy = node->uncookedIndexes(); // uncookedIndexes get delete on cooking
+
+ node->setColor(color);
+ node->setStrokeWidth(pathData.pen.widthF());
+ node->cookGeometry();
+ ret.append(node);
+
+ const bool wireFrame = debugVisualization() & DebugWireframe;
+ if (wireFrame) {
+ QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
+
+ QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
+ wfVertices.size(),
+ indexCopy.size(),
+ QSGGeometry::UnsignedIntType);
+ wfNode->setGeometry(wfg);
+
+ wfg->setDrawingMode(QSGGeometry::DrawTriangles);
+ memcpy(wfg->indexData(),
+ indexCopy.data(),
+ indexCopy.size() * wfg->sizeOfIndex());
+ memcpy(wfg->vertexData(),
+ wfVertices.data(),
+ wfg->vertexCount() * wfg->sizeOfVertex());
+
+ ret.append(wfNode);
+ }
+
+ return ret;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickshapes/qquickshapecurverenderer_p.h b/src/quickshapes/qquickshapecurverenderer_p.h
new file mode 100644
index 0000000000..a664b5efb4
--- /dev/null
+++ b/src/quickshapes/qquickshapecurverenderer_p.h
@@ -0,0 +1,164 @@
+// 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
+#define QQUICKSHAPECURVERENDERER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuickShapes/private/qquickshapesglobal_p.h>
+#include <QtQuickShapes/private/qquickshape_p_p.h>
+#include <QtQuick/private/qquadpath_p.h>
+#include <QtQuick/private/qsgcurveabstractnode_p.h>
+#include <QtQuick/private/qsggradientcache_p.h>
+#include <qsgnode.h>
+#include <qsggeometry.h>
+#include <qsgmaterial.h>
+#include <qsgrendererinterface.h>
+#include <qsgtexture.h>
+#include <QtCore/qrunnable.h>
+#include <QRunnable>
+
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtQuick/private/qsgcurvefillnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickShapeCurveRunnable;
+
+class Q_QUICKSHAPES_EXPORT QQuickShapeCurveRenderer : public QQuickAbstractPathRenderer
+{
+public:
+ QQuickShapeCurveRenderer(QQuickItem *item)
+ : m_item(item)
+ { }
+ ~QQuickShapeCurveRenderer() override;
+
+ void beginSync(int totalCount, bool *countChanged) override;
+ void setPath(int index, const QQuickPath *path) override;
+ void setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints = {});
+ void setStrokeColor(int index, const QColor &color) override;
+ void setStrokeWidth(int index, qreal w) override;
+ void setFillColor(int index, const QColor &color) override;
+ void setFillRule(int index, QQuickShapePath::FillRule fillRule) override;
+ void setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit) override;
+ void setCapStyle(int index, QQuickShapePath::CapStyle capStyle) override;
+ 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;
+
+ void setRootNode(QSGNode *node);
+
+ using NodeList = QVector<QSGCurveAbstractNode *>;
+
+ enum DirtyFlag
+ {
+ PathDirty = 0x01,
+ FillDirty = 0x02,
+ StrokeDirty = 0x04,
+ UniformsDirty = 0x08
+ };
+
+ enum DebugVisualizationOption {
+ NoDebug = 0,
+ DebugCurves = 0x01,
+ DebugWireframe = 0x02
+ };
+
+ static int debugVisualization();
+ static void setDebugVisualization(int options);
+
+private:
+ struct PathData {
+
+ bool isFillVisible() const
+ {
+ return gradientType != QGradient::NoGradient
+ || fillTextureProviderItem != nullptr
+ || fillColor.alpha() > 0;
+ }
+
+ bool isStrokeVisible() const
+ {
+ return validPenWidth && pen.color().alpha() > 0 && pen.style() != Qt::NoPen;
+ }
+
+ QGradient::Type gradientType = QGradient::NoGradient;
+ QSGGradientCache::GradientDesc gradient;
+ QSGTransform fillTransform;
+ QColor fillColor;
+ Qt::FillRule fillRule = Qt::OddEvenFill;
+ QPen pen;
+ bool validPenWidth = true;
+ int m_dirty = 0;
+ QQuickShapePath::PathHints pathHints;
+
+ QPainterPath originalPath;
+ QQuadPath path;
+ QQuadPath fillPath;
+ QQuadPath strokePath;
+
+ NodeList fillNodes;
+ NodeList strokeNodes;
+
+ QQuickShapeCurveRunnable *currentRunner = nullptr;
+ QQuickItem *fillTextureProviderItem = nullptr;
+ };
+
+ void createRunner(PathData *pathData);
+ void maybeUpdateAsyncItem();
+
+ static void processPath(PathData *pathData);
+ static NodeList addFillNodes(const QQuadPath &path);
+ static NodeList addTriangulatingStrokerNodes(const PathData &pathData);
+ static NodeList addCurveStrokeNodes(const PathData &pathData);
+
+ void solveIntersections(QQuadPath &path);
+ QQuickItem *m_item;
+ QSGNode *m_rootNode = nullptr;
+ QVector<PathData> m_paths;
+ void (*m_asyncCallback)(void *) = nullptr;
+ void *m_asyncCallbackData = nullptr;
+ static int debugVisualizationFlags;
+
+ friend class QQuickShapeCurveRunnable;
+};
+
+class QQuickShapeCurveRunnable : public QObject, public QRunnable
+{
+ Q_OBJECT
+
+public:
+ void run() override;
+
+ bool isAsync = false;
+ bool isDone = false;
+ bool orphaned = false;
+
+ // input / output
+ QQuickShapeCurveRenderer::PathData pathData;
+
+Q_SIGNALS:
+ void done(QQuickShapeCurveRunnable *self);
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKSHAPECURVERENDERER_P_H
diff --git a/src/quickshapes/qquickshapecurverenderer_p_p.h b/src/quickshapes/qquickshapecurverenderer_p_p.h
new file mode 100644
index 0000000000..8829bd89a8
--- /dev/null
+++ b/src/quickshapes/qquickshapecurverenderer_p_p.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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_P_H
+#define QQUICKSHAPECURVERENDERER_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuickShapes/private/qquickshapesglobal_p.h>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcShapeCurveRenderer);
+
+QT_END_NAMESPACE
+
+#endif //QQUICKSHAPECURVERENDERER_P_P_H
diff --git a/src/quickshapes/qquickshapegenericrenderer.cpp b/src/quickshapes/qquickshapegenericrenderer.cpp
index b2b6af041f..34c7fa96a0 100644
--- a/src/quickshapes/qquickshapegenericrenderer.cpp
+++ b/src/quickshapes/qquickshapegenericrenderer.cpp
@@ -1,11 +1,15 @@
-// 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"
#include <QtGui/private/qtriangulator_p.h>
#include <QtGui/private/qtriangulatingstroker_p.h>
-#include <QtGui/private/qrhi_p.h>
+#include <rhi/qrhi.h>
#include <QSGVertexColorMaterial>
+#include <QSGTextureProvider>
+#include <private/qsgplaintexture_p.h>
+
+#include <QtQuick/private/qsggradientcache_p.h>
#if QT_CONFIG(thread)
#include <QThreadPool>
@@ -40,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
@@ -64,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;
@@ -73,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) {
@@ -176,7 +203,7 @@ void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient
ShapePathData &d(m_sp[index]);
if (gradient) {
d.fillGradient.stops = gradient->gradientStops(); // sorted
- d.fillGradient.spread = gradient->spread();
+ d.fillGradient.spread = QGradient::Spread(gradient->spread());
if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) {
d.fillGradientActive = LinearGradient;
d.fillGradient.a = QPointF(g->x1(), g->y1());
@@ -200,6 +227,39 @@ 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]);
+ d.fillTransform = transform;
+ d.syncDirty |= DirtyFillTransform;
+}
+
void QQuickShapeGenericRenderer::setTriangulationScale(qreal scale)
{
// No dirty, this is called at the start of every sync. Just store the value.
@@ -489,7 +549,7 @@ void QQuickShapeGenericRenderer::updateNode()
QQuickShapeGenericNode *node = *nodePtr;
if (m_accDirty & DirtyList)
- d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient;
+ d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture;
if (!d.effectiveDirty) {
prevNode = node;
@@ -543,13 +603,43 @@ void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuick
if (d->effectiveDirty & DirtyFillGradient)
n->m_fillGradient = d->fillGradient;
}
+ 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)))
+ if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture)))
return;
// Make a copy of the data that will be accessed by the material on
@@ -582,17 +672,21 @@ void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGen
Q_UNREACHABLE_RETURN();
}
n->activateMaterial(m_item->window(), gradMat);
- if (d->effectiveDirty & DirtyFillGradient) {
+ if (d->effectiveDirty & (DirtyFillGradient | DirtyFillTransform)) {
// Gradients are implemented via a texture-based material.
n->markDirty(QSGNode::DirtyMaterial);
- // stop here if only the gradient changed; no need to touch the geometry
+ // stop here if only the gradient or filltransform changed; no need to touch the geometry
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);
@@ -611,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());
@@ -701,10 +796,22 @@ QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWind
return nullptr;
}
-QQuickShapeLinearGradientRhiShader::QQuickShapeLinearGradientRhiShader()
+QSGMaterial *QQuickShapeGenericMaterialFactory::createTextureFill(QQuickWindow *window,
+ QQuickShapeGenericStrokeFillNode *node)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"));
+ 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);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"), viewCount);
}
bool QQuickShapeLinearGradientRhiShader::updateUniformData(RenderState &state,
@@ -714,32 +821,42 @@ bool QQuickShapeLinearGradientRhiShader::updateUniformData(RenderState &state,
QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
bool changed = false;
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= 84);
+ Q_ASSERT(buf->size() >= 84 + 64);
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ 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;
+ }
+
if (!oldMaterial || m_gradA.x() != node->m_fillGradient.a.x() || m_gradA.y() != node->m_fillGradient.a.y()) {
m_gradA = QVector2D(node->m_fillGradient.a.x(), node->m_fillGradient.a.y());
Q_ASSERT(sizeof(m_gradA) == 8);
- memcpy(buf->data() + 64, &m_gradA, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_gradA, 8);
changed = true;
}
if (!oldMaterial || m_gradB.x() != node->m_fillGradient.b.x() || m_gradB.y() != node->m_fillGradient.b.y()) {
m_gradB = QVector2D(node->m_fillGradient.b.x(), node->m_fillGradient.b.y());
- memcpy(buf->data() + 72, &m_gradB, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_gradB, 8);
changed = true;
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 80, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &opacity, 4);
changed = true;
}
@@ -754,8 +871,8 @@ void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state,
QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
QQuickShapeGenericStrokeFillNode *node = m->node();
- const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
- QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
+ QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = t;
}
@@ -777,8 +894,8 @@ int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
if (a == b)
return 0;
- const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
- const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
+ const QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
+ const QSGGradientCache::GradientDesc *gb = &b->m_fillGradient;
if (int d = ga->spread - gb->spread)
return d;
@@ -802,19 +919,22 @@ int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
return d;
}
+ if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
+ return d;
+
return 0;
}
QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QQuickShapeLinearGradientRhiShader;
+ return new QQuickShapeLinearGradientRhiShader(viewCount());
}
-QQuickShapeRadialGradientRhiShader::QQuickShapeRadialGradientRhiShader()
+QQuickShapeRadialGradientRhiShader::QQuickShapeRadialGradientRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"), viewCount);
}
bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
@@ -824,16 +944,26 @@ bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
bool changed = false;
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= 92);
+ Q_ASSERT(buf->size() >= 92 + 64);
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ 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 QPointF centerPoint = node->m_fillGradient.a;
const QPointF focalPoint = node->m_fillGradient.b;
const QPointF focalToCenter = centerPoint - focalPoint;
@@ -843,32 +973,32 @@ bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
Q_ASSERT(sizeof(m_focalPoint) == 8);
- memcpy(buf->data() + 64, &m_focalPoint, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_focalPoint, 8);
changed = true;
}
if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
Q_ASSERT(sizeof(m_focalToCenter) == 8);
- memcpy(buf->data() + 72, &m_focalToCenter, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_focalToCenter, 8);
changed = true;
}
if (!oldMaterial || m_centerRadius != centerRadius) {
m_centerRadius = centerRadius;
- memcpy(buf->data() + 80, &m_centerRadius, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, &m_centerRadius, 4);
changed = true;
}
if (!oldMaterial || m_focalRadius != focalRadius) {
m_focalRadius = focalRadius;
- memcpy(buf->data() + 84, &m_focalRadius, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4, &m_focalRadius, 4);
changed = true;
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 88, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4 + 4, &opacity, 4);
changed = true;
}
@@ -883,8 +1013,8 @@ void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state,
QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
QQuickShapeGenericStrokeFillNode *node = m->node();
- const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
- QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
+ QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = t;
}
@@ -906,8 +1036,8 @@ int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
if (a == b)
return 0;
- const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
- const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
+ const QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
+ const QSGGradientCache::GradientDesc *gb = &b->m_fillGradient;
if (int d = ga->spread - gb->spread)
return d;
@@ -936,19 +1066,22 @@ int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
return d;
}
+ if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
+ return d;
+
return 0;
}
QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QQuickShapeRadialGradientRhiShader;
+ return new QQuickShapeRadialGradientRhiShader(viewCount());
}
-QQuickShapeConicalGradientRhiShader::QQuickShapeConicalGradientRhiShader()
+QQuickShapeConicalGradientRhiShader::QQuickShapeConicalGradientRhiShader(int viewCount)
{
- setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"));
- setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"));
+ setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"), viewCount);
+ setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"), viewCount);
}
bool QQuickShapeConicalGradientRhiShader::updateUniformData(RenderState &state,
@@ -958,35 +1091,45 @@ bool QQuickShapeConicalGradientRhiShader::updateUniformData(RenderState &state,
QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
bool changed = false;
QByteArray *buf = state.uniformData();
- Q_ASSERT(buf->size() >= 80);
+ Q_ASSERT(buf->size() >= 80 + 64);
+ const int shaderMatrixCount = newMaterial->viewCount();
+ const int matrixCount = qMin(state.projectionMatrixCount(), shaderMatrixCount);
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
- memcpy(buf->data(), m.constData(), 64);
- changed = true;
+ 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 QPointF centerPoint = node->m_fillGradient.a;
const float angle = -qDegreesToRadians(node->m_fillGradient.v0);
if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
Q_ASSERT(sizeof(m_centerPoint) == 8);
- memcpy(buf->data() + 64, &m_centerPoint, 8);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64, &m_centerPoint, 8);
changed = true;
}
if (!oldMaterial || m_angle != angle) {
m_angle = angle;
- memcpy(buf->data() + 72, &m_angle, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8, &m_angle, 4);
changed = true;
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
- memcpy(buf->data() + 76, &opacity, 4);
+ memcpy(buf->data() + 64 * shaderMatrixCount + 64 + 8 + 4, &opacity, 4);
changed = true;
}
@@ -1001,8 +1144,8 @@ void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state,
QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
QQuickShapeGenericStrokeFillNode *node = m->node();
- const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
- QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
+ QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = t;
}
@@ -1024,8 +1167,8 @@ int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
if (a == b)
return 0;
- const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
- const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
+ const QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
+ const QSGGradientCache::GradientDesc *gb = &b->m_fillGradient;
if (int d = ga->a.x() - gb->a.x())
return d;
@@ -1045,13 +1188,149 @@ int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
return d;
}
+ if (int d = a->m_fillTransform.compareTo(b->m_fillTransform))
+ return d;
+
return 0;
}
QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
Q_UNUSED(renderMode);
- return new QQuickShapeConicalGradientRhiShader;
+ 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
diff --git a/src/quickshapes/qquickshapegenericrenderer_p.h b/src/quickshapes/qquickshapegenericrenderer_p.h
index a5ec01329d..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
@@ -17,6 +17,7 @@
#include <QtQuickShapes/private/qquickshapesglobal_p.h>
#include <QtQuickShapes/private/qquickshape_p_p.h>
+#include <QtQuick/private/qsggradientcache_p.h>
#include <qsgnode.h>
#include <qsggeometry.h>
#include <qsgmaterial.h>
@@ -39,7 +40,9 @@ public:
DirtyStrokeGeom = 0x02,
DirtyColor = 0x04,
DirtyFillGradient = 0x08,
- DirtyList = 0x10 // only for accDirty
+ DirtyFillTransform = 0x10,
+ DirtyFillTexture = 0x20,
+ DirtyList = 0x40 // only for accDirty
};
QQuickShapeGenericRenderer(QQuickItem *item)
@@ -63,10 +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;
@@ -74,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,
@@ -96,12 +103,14 @@ private:
struct ShapePathData {
float strokeWidth;
QPen pen;
- Color4ub strokeColor;
- Color4ub fillColor;
+ Color4ub strokeColor = { uchar(0), uchar(0), uchar(0), uchar(0) };
+ Color4ub fillColor = { uchar(0), uchar(0), uchar(0), uchar(0) };
Qt::FillRule fillRule;
QPainterPath path;
FillGradientType fillGradientActive;
- GradientDesc fillGradient;
+ QSGGradientCache::GradientDesc fillGradient;
+ QQuickItem *fillTextureProviderItem = nullptr;
+ QSGTransform fillTransform;
VertexContainerType fillVertices;
IndexContainerType fillIndices;
QSGGeometry::Type indexType;
@@ -173,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);
@@ -182,13 +192,21 @@ public:
MatSolidColor,
MatLinearGradient,
MatRadialGradient,
- MatConicalGradient
+ MatConicalGradient,
+ MatTextureFill
};
void activateMaterial(QQuickWindow *window, Material m);
// shadow data for custom materials
- QQuickAbstractPathRenderer::GradientDesc m_fillGradient;
+ 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;
@@ -211,12 +229,13 @@ 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
{
public:
- QQuickShapeLinearGradientRhiShader();
+ QQuickShapeLinearGradientRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
QSGMaterial *oldMaterial) override;
@@ -224,6 +243,7 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
private:
+ QSGTransform m_fillTransform;
QVector2D m_gradA;
QVector2D m_gradB;
};
@@ -255,7 +275,7 @@ private:
class QQuickShapeRadialGradientRhiShader : public QSGMaterialShader
{
public:
- QQuickShapeRadialGradientRhiShader();
+ QQuickShapeRadialGradientRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
QSGMaterial *oldMaterial) override;
@@ -263,6 +283,7 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
private:
+ QSGTransform m_fillTransform;
QVector2D m_focalPoint;
QVector2D m_focalToCenter;
float m_centerRadius;
@@ -291,7 +312,7 @@ private:
class QQuickShapeConicalGradientRhiShader : public QSGMaterialShader
{
public:
- QQuickShapeConicalGradientRhiShader();
+ QQuickShapeConicalGradientRhiShader(int viewCount);
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
QSGMaterial *oldMaterial) override;
@@ -299,6 +320,7 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
private:
+ QSGTransform m_fillTransform;
QVector2D m_centerPoint;
float m_angle;
};
@@ -322,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/qquickshapesglobal_p.h b/src/quickshapes/qquickshapesglobal_p.h
index 0efd1a88ce..eb26c41a20 100644
--- a/src/quickshapes/qquickshapesglobal_p.h
+++ b/src/quickshapes/qquickshapesglobal_p.h
@@ -16,11 +16,11 @@
//
#include "qquickshapesglobal.h"
-#include <QtQuickShapes/private/qtquickshapesexports_p.h>
+#include <QtQuickShapes/qtquickshapesexports.h>
QT_BEGIN_NAMESPACE
-void Q_QUICKSHAPES_PRIVATE_EXPORT qml_register_types_QtQuick_Shapes();
+void Q_QUICKSHAPES_EXPORT qml_register_types_QtQuick_Shapes();
QT_END_NAMESPACE
diff --git a/src/quickshapes/qquickshapesoftwarerenderer.cpp b/src/quickshapes/qquickshapesoftwarerenderer.cpp
index 60efcf1db9..f1a2e3dc67 100644
--- a/src/quickshapes/qquickshapesoftwarerenderer.cpp
+++ b/src/quickshapes/qquickshapesoftwarerenderer.cpp
@@ -138,6 +138,27 @@ 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]);
+ if (!(transform.isIdentity() && d.brush.transform().isIdentity())) // No need to copy if both==I
+ d.brush.setTransform(transform.matrix().toTransform());
+ d.dirty |= DirtyBrush;
+ m_accDirty |= DirtyBrush;
+}
+
void QQuickShapeSoftwareRenderer::endSync(bool)
{
}
diff --git a/src/quickshapes/qquickshapesoftwarerenderer_p.h b/src/quickshapes/qquickshapesoftwarerenderer_p.h
index d08145bb1b..94eded84e5 100644
--- a/src/quickshapes/qquickshapesoftwarerenderer_p.h
+++ b/src/quickshapes/qquickshapesoftwarerenderer_p.h
@@ -47,7 +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/compile.bat b/src/quickshapes/shaders_ng/compile.bat
deleted file mode 100755
index be21daf62f..0000000000
--- a/src/quickshapes/shaders_ng/compile.bat
+++ /dev/null
@@ -1,15 +0,0 @@
-:: Copyright (C) 2019 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
-
-:: For HLSL we invoke fxc.exe (-c argument) and store the resulting intermediate format
-:: instead of HLSL source, so this needs to be run on Windows from a developer command prompt.
-
-:: For SPIR-V the optimizer is requested (-O argument) which means spirv-opt must be
-:: invokable (e.g. because it's in the PATH from the Vulkan SDK)
-
-qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o lineargradient.vert.qsb lineargradient.vert
-qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o lineargradient.frag.qsb lineargradient.frag
-qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o radialgradient.vert.qsb radialgradient.vert
-qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o radialgradient.frag.qsb radialgradient.frag
-qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o conicalgradient.vert.qsb conicalgradient.vert
-qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -O -c -o conicalgradient.frag.qsb conicalgradient.frag
diff --git a/src/quickshapes/shaders_ng/conicalgradient.frag b/src/quickshapes/shaders_ng/conicalgradient.frag
index 0b1e01bae2..59862f991a 100644
--- a/src/quickshapes/shaders_ng/conicalgradient.frag
+++ b/src/quickshapes/shaders_ng/conicalgradient.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 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 coord;
@@ -6,7 +9,12 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D gradTabTexture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 translationPoint;
float angle;
float opacity;
diff --git a/src/quickshapes/shaders_ng/conicalgradient.frag.qsb b/src/quickshapes/shaders_ng/conicalgradient.frag.qsb
deleted file mode 100644
index df58050128..0000000000
--- a/src/quickshapes/shaders_ng/conicalgradient.frag.qsb
+++ /dev/null
Binary files differ
diff --git a/src/quickshapes/shaders_ng/conicalgradient.vert b/src/quickshapes/shaders_ng/conicalgradient.vert
index 3db027294b..cdaab16842 100644
--- a/src/quickshapes/shaders_ng/conicalgradient.vert
+++ b/src/quickshapes/shaders_ng/conicalgradient.vert
@@ -6,16 +6,24 @@ layout(location = 1) in vec4 vertexColor;
layout(location = 0) out vec2 coord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 translationPoint;
float angle;
float opacity;
} ubuf;
-out gl_PerVertex { vec4 gl_Position; };
-
void main()
{
- coord = vertexCoord.xy - ubuf.translationPoint;
+ vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
+ coord = gradVertexCoord - ubuf.translationPoint;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.matrix[gl_ViewIndex] * vertexCoord;
+#else
gl_Position = ubuf.matrix * vertexCoord;
+#endif
}
diff --git a/src/quickshapes/shaders_ng/conicalgradient.vert.qsb b/src/quickshapes/shaders_ng/conicalgradient.vert.qsb
deleted file mode 100644
index 958a75e918..0000000000
--- a/src/quickshapes/shaders_ng/conicalgradient.vert.qsb
+++ /dev/null
Binary files differ
diff --git a/src/quickshapes/shaders_ng/lineargradient.frag b/src/quickshapes/shaders_ng/lineargradient.frag
index 16894fc764..b6f0dc172a 100644
--- a/src/quickshapes/shaders_ng/lineargradient.frag
+++ b/src/quickshapes/shaders_ng/lineargradient.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 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 float gradTabIndex;
@@ -6,7 +9,12 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D gradTabTexture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 gradStart;
vec2 gradEnd;
float opacity;
diff --git a/src/quickshapes/shaders_ng/lineargradient.frag.qsb b/src/quickshapes/shaders_ng/lineargradient.frag.qsb
deleted file mode 100644
index be3a820afd..0000000000
--- a/src/quickshapes/shaders_ng/lineargradient.frag.qsb
+++ /dev/null
Binary files differ
diff --git a/src/quickshapes/shaders_ng/lineargradient.vert b/src/quickshapes/shaders_ng/lineargradient.vert
index b4eb868186..13168b1c0f 100644
--- a/src/quickshapes/shaders_ng/lineargradient.vert
+++ b/src/quickshapes/shaders_ng/lineargradient.vert
@@ -6,17 +6,25 @@ layout(location = 1) in vec4 vertexColor;
layout(location = 0) out float gradTabIndex;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 gradStart;
vec2 gradEnd;
float opacity;
} ubuf;
-out gl_PerVertex { vec4 gl_Position; };
-
void main()
{
+ vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
vec2 gradVec = ubuf.gradEnd - ubuf.gradStart;
- gradTabIndex = dot(gradVec, vertexCoord.xy - ubuf.gradStart) / (gradVec.x * gradVec.x + gradVec.y * gradVec.y);
+ gradTabIndex = dot(gradVec, gradVertexCoord - ubuf.gradStart) / (gradVec.x * gradVec.x + gradVec.y * gradVec.y);
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.matrix[gl_ViewIndex] * vertexCoord;
+#else
gl_Position = ubuf.matrix * vertexCoord;
+#endif
}
diff --git a/src/quickshapes/shaders_ng/lineargradient.vert.qsb b/src/quickshapes/shaders_ng/lineargradient.vert.qsb
deleted file mode 100644
index 9e76279ca5..0000000000
--- a/src/quickshapes/shaders_ng/lineargradient.vert.qsb
+++ /dev/null
Binary files differ
diff --git a/src/quickshapes/shaders_ng/radialgradient.frag b/src/quickshapes/shaders_ng/radialgradient.frag
index 411e589295..cfbb44ac69 100644
--- a/src/quickshapes/shaders_ng/radialgradient.frag
+++ b/src/quickshapes/shaders_ng/radialgradient.frag
@@ -1,3 +1,6 @@
+// Copyright (C) 2023 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 coord;
@@ -6,7 +9,12 @@ layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D gradTabTexture;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 translationPoint;
vec2 focalToCenter;
float centerRadius;
diff --git a/src/quickshapes/shaders_ng/radialgradient.frag.qsb b/src/quickshapes/shaders_ng/radialgradient.frag.qsb
deleted file mode 100644
index ecd7f2926d..0000000000
--- a/src/quickshapes/shaders_ng/radialgradient.frag.qsb
+++ /dev/null
Binary files differ
diff --git a/src/quickshapes/shaders_ng/radialgradient.vert b/src/quickshapes/shaders_ng/radialgradient.vert
index 08f15c4f8c..16c8406b23 100644
--- a/src/quickshapes/shaders_ng/radialgradient.vert
+++ b/src/quickshapes/shaders_ng/radialgradient.vert
@@ -6,7 +6,12 @@ layout(location = 1) in vec4 vertexColor;
layout(location = 0) out vec2 coord;
layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 matrix[QSHADER_VIEW_COUNT];
+#else
mat4 matrix;
+#endif
+ mat4 gradientMatrix;
vec2 translationPoint;
vec2 focalToCenter;
float centerRadius;
@@ -14,10 +19,13 @@ layout(std140, binding = 0) uniform buf {
float opacity;
} ubuf;
-out gl_PerVertex { vec4 gl_Position; };
-
void main()
{
- coord = vertexCoord.xy - ubuf.translationPoint;
+ vec2 gradVertexCoord = (ubuf.gradientMatrix * vertexCoord).xy;
+ coord = gradVertexCoord - ubuf.translationPoint;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.matrix[gl_ViewIndex] * vertexCoord;
+#else
gl_Position = ubuf.matrix * vertexCoord;
+#endif
}
diff --git a/src/quickshapes/shaders_ng/radialgradient.vert.qsb b/src/quickshapes/shaders_ng/radialgradient.vert.qsb
deleted file mode 100644
index 68e0e21f02..0000000000
--- a/src/quickshapes/shaders_ng/radialgradient.vert.qsb
+++ /dev/null
Binary files differ
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/src/quickshapes/shaders_ng/wireframe.frag b/src/quickshapes/shaders_ng/wireframe.frag
new file mode 100644
index 0000000000..b37ffde6ff
--- /dev/null
+++ b/src/quickshapes/shaders_ng/wireframe.frag
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 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) out vec4 fragColor;
+layout(location = 0) in vec3 barycentric;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+} ubuf;
+
+void main()
+{
+ float f = min(barycentric.x, min(barycentric.y, barycentric.z));
+ float d = fwidth(f * 1.5);
+ float alpha = smoothstep(0.0, d, f);
+
+ //alpha = 1.0 - step(0.5, barycentric.x);
+ fragColor = vec4(1.0, 0.2, 1.0, 1.0) * (1.0 - alpha);
+}
diff --git a/src/quickshapes/shaders_ng/wireframe.vert b/src/quickshapes/shaders_ng/wireframe.vert
new file mode 100644
index 0000000000..69f8872d51
--- /dev/null
+++ b/src/quickshapes/shaders_ng/wireframe.vert
@@ -0,0 +1,23 @@
+#version 440
+
+layout(location = 0) in vec4 vertexCoord;
+layout(location = 1) in vec3 vertexBarycentric;
+layout(location = 0) out vec3 barycentric;
+
+layout(std140, binding = 0) uniform buf {
+#if QSHADER_VIEW_COUNT >= 2
+ mat4 qt_Matrix[QSHADER_VIEW_COUNT];
+#else
+ mat4 qt_Matrix;
+#endif
+} ubuf;
+
+void main()
+{
+ barycentric = vertexBarycentric;
+#if QSHADER_VIEW_COUNT >= 2
+ gl_Position = ubuf.qt_Matrix[gl_ViewIndex] * vertexCoord;
+#else
+ gl_Position = ubuf.qt_Matrix * vertexCoord;
+#endif
+}