aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2023-10-10 14:23:33 +0200
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2023-11-11 11:42:49 +0100
commitbde55ad574ac84440e2cdc9c1122a344bb1cb67a (patch)
treeabb8d486eec2761630df6437019e2a54cb7259c6
parent6df497920c696b44ab826c7b4cd775255c220511 (diff)
Introduce a CurveRendering backend for text
This moves the internals of the curve renderer out from Qt Quick Shapes and into a more centralized location in Qt Quick, so that we can use the same code to create a new text backend for rendering large scale text without artifacts. Change-Id: I3f7e6f7961c1bbe230fcb531c0ca028e038c1afd Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
-rw-r--r--src/quick/CMakeLists.txt143
-rw-r--r--src/quick/items/qquicktext.cpp8
-rw-r--r--src/quick/items/qquicktext_p.h3
-rw-r--r--src/quick/items/qquicktextedit.cpp8
-rw-r--r--src/quick/items/qquicktextedit_p.h3
-rw-r--r--src/quick/items/qquicktextinput.cpp8
-rw-r--r--src/quick/items/qquicktextinput_p.h3
-rw-r--r--src/quick/items/qquicktextutil_p.h2
-rw-r--r--src/quick/items/qquickwindow.cpp9
-rw-r--r--src/quick/items/qquickwindow.h3
-rw-r--r--src/quick/items/qsginternaltextnode.cpp11
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp4
-rw-r--r--src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h2
-rw-r--r--src/quick/scenegraph/qsgcontext.cpp10
-rw-r--r--src/quick/scenegraph/qsgcontext_p.h5
-rw-r--r--src/quick/scenegraph/qsgcurveabstractnode_p.h (renamed from src/quickshapes/qquickshapeabstractcurvenode_p.h)12
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode.cpp (renamed from src/quickshapes/qquickshapecurvenode.cpp)17
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.cpp (renamed from src/quickshapes/qquickshapecurvenode_p.cpp)79
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p.h (renamed from src/quickshapes/qquickshapecurvenode_p.h)82
-rw-r--r--src/quick/scenegraph/qsgcurvefillnode_p_p.h (renamed from src/quickshapes/qquickshapecurvenode_p_p.h)17
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas.cpp142
-rw-r--r--src/quick/scenegraph/qsgcurveglyphatlas_p.h69
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode.cpp165
-rw-r--r--src/quick/scenegraph/qsgcurveglyphnode_p.h69
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor.cpp1109
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor_p.h51
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode.cpp (renamed from src/quickshapes/qquickshapestrokenode.cpp)20
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.cpp (renamed from src/quickshapes/qquickshapestrokenode_p.cpp)31
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p.h (renamed from src/quickshapes/qquickshapestrokenode_p.h)31
-rw-r--r--src/quick/scenegraph/qsgcurvestrokenode_p_p.h (renamed from src/quickshapes/qquickshapestrokenode_p_p.h)29
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext.cpp7
-rw-r--r--src/quick/scenegraph/qsgdefaultcontext_p.h2
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext.cpp19
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext_p.h2
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.frag (renamed from src/quickshapes/shaders_ng/shapecurve.frag)0
-rw-r--r--src/quick/scenegraph/shaders_ng/shapecurve.vert (renamed from src/quickshapes/shaders_ng/shapecurve.vert)0
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.frag (renamed from src/quickshapes/shaders_ng/shapestroke.frag)4
-rw-r--r--src/quick/scenegraph/shaders_ng/shapestroke.vert (renamed from src/quickshapes/shaders_ng/shapestroke.vert)0
-rw-r--r--src/quick/scenegraph/util/qquadpath.cpp (renamed from src/quickshapes/qquadpath.cpp)6
-rw-r--r--src/quick/scenegraph/util/qquadpath_p.h (renamed from src/quickshapes/qquadpath_p.h)3
-rw-r--r--src/quick/scenegraph/util/qsggradientcache.cpp121
-rw-r--r--src/quick/scenegraph/util/qsggradientcache_p.h72
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.cpp8
-rw-r--r--src/quick/scenegraph/util/qsgtextnode.h3
-rw-r--r--src/quickshapes/CMakeLists.txt129
-rw-r--r--src/quickshapes/qquickshape.cpp106
-rw-r--r--src/quickshapes/qquickshape_p_p.h41
-rw-r--r--src/quickshapes/qquickshapecurverenderer.cpp1195
-rw-r--r--src/quickshapes/qquickshapecurverenderer_p.h16
-rw-r--r--src/quickshapes/qquickshapegenericrenderer.cpp28
-rw-r--r--src/quickshapes/qquickshapegenericrenderer_p.h5
-rw-r--r--tests/baseline/scenegraph/data/text/text_emoji.qml5
-rw-r--r--tests/baseline/scenegraph/data/text/text_emoji_hebrew.qml4
-rw-r--r--tests/manual/painterpathquickshape/TextShape.qml2
-rw-r--r--tests/manual/textrendering/CMakeLists.txt40
-rw-r--r--tests/manual/textrendering/main.cpp26
-rw-r--r--tests/manual/textrendering/main.qml103
57 files changed, 2507 insertions, 1585 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index c859008fa0..2c2604edab 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -134,13 +134,18 @@ qt_internal_add_qml_module(Quick
scenegraph/coreapi/qsgtexture.cpp scenegraph/coreapi/qsgtexture.h scenegraph/coreapi/qsgtexture_p.h
scenegraph/coreapi/qsgtexture_platform.h
scenegraph/qsgadaptationlayer.cpp scenegraph/qsgadaptationlayer_p.h
+ scenegraph/qsgcurveabstractnode_p.h
scenegraph/qsgbasicglyphnode.cpp scenegraph/qsgbasicglyphnode_p.h
scenegraph/qsgbasicinternalimagenode.cpp scenegraph/qsgbasicinternalimagenode_p.h
scenegraph/qsgbasicinternalrectanglenode.cpp scenegraph/qsgbasicinternalrectanglenode_p.h
scenegraph/qsgcontext.cpp scenegraph/qsgcontext_p.h
scenegraph/qsgcontextplugin.cpp scenegraph/qsgcontextplugin_p.h
+ scenegraph/qsgcurvefillnode.cpp scenegraph/qsgcurvefillnode_p.cpp scenegraph/qsgcurvefillnode_p.h scenegraph/qsgcurvefillnode_p_p.h
+ scenegraph/qsgcurvestrokenode.cpp scenegraph/qsgcurvestrokenode_p.cpp scenegraph/qsgcurvestrokenode_p.h scenegraph/qsgcurvestrokenode_p_p.h
scenegraph/qsgdefaultcontext.cpp scenegraph/qsgdefaultcontext_p.h
scenegraph/qsgdefaultglyphnode.cpp scenegraph/qsgdefaultglyphnode_p.cpp scenegraph/qsgdefaultglyphnode_p.h
+ scenegraph/qsgcurveglyphatlas.cpp scenegraph/qsgcurveglyphatlas_p.h
+ scenegraph/qsgcurveglyphnode.cpp scenegraph/qsgcurveglyphnode_p.h
scenegraph/qsgdefaultglyphnode_p_p.h
scenegraph/qsgdefaultinternalimagenode.cpp scenegraph/qsgdefaultinternalimagenode_p.h
scenegraph/qsgdefaultinternalrectanglenode.cpp scenegraph/qsgdefaultinternalrectanglenode_p.h
@@ -153,6 +158,7 @@ qt_internal_add_qml_module(Quick
scenegraph/qsgrhishadereffectnode.cpp scenegraph/qsgrhishadereffectnode_p.h
scenegraph/qsgrhisupport.cpp scenegraph/qsgrhisupport_p.h
scenegraph/qsgrhitextureglyphcache.cpp scenegraph/qsgrhitextureglyphcache_p.h
+ scenegraph/qsgcurveprocessor.cpp scenegraph/qsgcurveprocessor_p.h
scenegraph/util/qsgareaallocator.cpp scenegraph/util/qsgareaallocator_p.h
scenegraph/util/qsgdefaultimagenode.cpp scenegraph/util/qsgdefaultimagenode_p.h
scenegraph/util/qsgdefaultninepatchnode.cpp scenegraph/util/qsgdefaultninepatchnode_p.h
@@ -171,6 +177,8 @@ qt_internal_add_qml_module(Quick
scenegraph/util/qsgtextureprovider.cpp scenegraph/util/qsgtextureprovider.h
scenegraph/util/qsgtexturereader.cpp scenegraph/util/qsgtexturereader_p.h
scenegraph/util/qsgvertexcolormaterial.cpp scenegraph/util/qsgvertexcolormaterial.h
+ scenegraph/util/qquadpath.cpp scenegraph/util/qquadpath_p.h
+ scenegraph/util/qsggradientcache.cpp scenegraph/util/qsggradientcache_p.h
util/qminimalflatset_p.h
util/qquickanimation.cpp util/qquickanimation_p.h
util/qquickanimation_p_p.h
@@ -310,6 +318,141 @@ qt_internal_add_shaders(Quick "scenegraph_shaders"
"scenegraph/shaders_ng/visualization.vert"
)
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ "scenegraph/shaders_ng/shapestroke.frag"
+ "scenegraph/shaders_ng/shapestroke.vert"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_lg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "LINEARGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_lg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_lg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_lg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "LINEARGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_lg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_lg_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_rg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "RADIALGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_rg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_rg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_rg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "RADIALGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_rg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_rg_derivatives.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_cg"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "CONICALGRADIENT"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_cg.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_cg.vert.qsb"
+)
+
+qt_internal_add_shaders(Quick "scenegraph_curve_shaders_cg_derivatives"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ DEFINES
+ "CONICALGRADIENT"
+ "USE_DERIVATIVES"
+ PREFIX
+ "/qt-project.org"
+ FILES
+ "scenegraph/shaders_ng/shapecurve.frag"
+ "scenegraph/shaders_ng/shapecurve.vert"
+ OUTPUTS
+ "scenegraph/shaders_ng/shapecurve_cg_derivatives.frag.qsb"
+ "scenegraph/shaders_ng/shapecurve_cg_derivatives.vert.qsb"
+)
+
qt_internal_extend_target(Quick CONDITION QT_FEATURE_qml_network
LIBRARIES
Qt::Network
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index e69d878409..a3e5c94761 100644
--- a/src/quick/items/qquicktext.cpp
+++ b/src/quick/items/qquicktext.cpp
@@ -3119,12 +3119,20 @@ void QQuickText::setRenderTypeQuality(int renderTypeQuality)
\value Text.QtRendering Text is rendered using a scalable distance field for each glyph.
\value Text.NativeRendering Text is rendered using a platform-specific technique.
+ \value Text.CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware. (Introduced in Qt 6.7.0.)
Select \c Text.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickText::RenderType QQuickText::renderType() const
diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h
index 5304648fca..d827a0e5d7 100644
--- a/src/quick/items/qquicktext_p.h
+++ b/src/quick/items/qquicktext_p.h
@@ -109,7 +109,8 @@ public:
Q_ENUM(WrapMode)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index 7870e89538..e139b825f0 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -533,12 +533,20 @@ void QQuickTextEdit::setTextFormat(TextFormat format)
\value TextEdit.QtRendering Text is rendered using a scalable distance field for each glyph.
\value TextEdit.NativeRendering Text is rendered using a platform-specific technique.
+ \value TextEdit.CurveRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
Select \c TextEdit.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c TextEdit.QtRendering and \c TextEdit.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickTextEdit::RenderType QQuickTextEdit::renderType() const
diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h
index f64332d508..2a3d01353c 100644
--- a/src/quick/items/qquicktextedit_p.h
+++ b/src/quick/items/qquicktextedit_p.h
@@ -124,7 +124,8 @@ public:
Q_ENUM(SelectionMode)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp
index 2cce5378fb..f2042c70bb 100644
--- a/src/quick/items/qquicktextinput.cpp
+++ b/src/quick/items/qquicktextinput.cpp
@@ -136,12 +136,20 @@ void QQuickTextInput::setText(const QString &s)
\value TextInput.QtRendering Text is rendered using a scalable distance field for each glyph.
\value TextInput.NativeRendering Text is rendered using a platform-specific technique.
+ \value TextInput.CurveRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
Select \c TextInput.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c TextInput.QtRendering and \c TextInput.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickTextInput::RenderType QQuickTextInput::renderType() const
diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h
index 515c696b0c..d9c8328921 100644
--- a/src/quick/items/qquicktextinput_p.h
+++ b/src/quick/items/qquicktextinput_p.h
@@ -138,7 +138,8 @@ public:
Q_ENUM(CursorPosition)
enum RenderType { QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
Q_ENUM(RenderType)
diff --git a/src/quick/items/qquicktextutil_p.h b/src/quick/items/qquicktextutil_p.h
index 212c488c73..9f8e2ae393 100644
--- a/src/quick/items/qquicktextutil_p.h
+++ b/src/quick/items/qquicktextutil_p.h
@@ -99,6 +99,8 @@ typename T::RenderType QQuickTextUtil::textRenderType()
return T::QtRendering;
case QQuickWindow::NativeTextRendering:
return T::NativeRendering;
+ case QQuickWindow::CurveTextRendering:
+ return T::CurveRendering;
}
Q_UNREACHABLE_RETURN(T::QtRendering);
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 315381e4e6..4f371b98a3 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -2656,9 +2656,16 @@ QQmlIncubationController *QQuickWindow::incubationController() const
text. Using such features in combination with the NativeTextRendering
render type will lend poor and sometimes pixelated results.
- \value QtTextRendering Use Qt's own rasterization algorithm.
+ Both \c QtTextRendering and \c CurveTextRendering are hardware-accelerated techniques.
+ \c QtTextRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveTextRendering should be considered as an alternative in cases
+ where \c QtTextRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+ \value QtTextRendering Use Qt's own rasterization algorithm.
\value NativeTextRendering Use the operating system's native rasterizer for text.
+ \value CurveTextRendering Text is rendered using a curve rasterizer running directly on
+ the graphics hardware. (Introduced in Qt 6.7.0.)
*/
/*!
diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h
index 1a184e7c34..d93ee71806 100644
--- a/src/quick/items/qquickwindow.h
+++ b/src/quick/items/qquickwindow.h
@@ -82,7 +82,8 @@ public:
enum TextRenderType {
QtTextRendering,
- NativeTextRendering
+ NativeTextRendering,
+ CurveTextRendering
};
Q_ENUM(TextRenderType)
diff --git a/src/quick/items/qsginternaltextnode.cpp b/src/quick/items/qsginternaltextnode.cpp
index 1e3c2b468d..c9200e1e3d 100644
--- a/src/quick/items/qsginternaltextnode.cpp
+++ b/src/quick/items/qsginternaltextnode.cpp
@@ -10,7 +10,6 @@
#include <private/qquickclipnode_p.h>
#include <private/qquickitem_p.h>
#include <private/qquicktextdocument_p.h>
-#include <QtQuick/private/qsgcontext_p.h>
#include <QtCore/qpoint.h>
#include <qtextdocument.h>
@@ -60,10 +59,14 @@ QSGGlyphNode *QSGInternalTextNode::addGlyphs(const QPointF &position, const QGly
}
}
+ QSGTextNode::RenderType preferredRenderType =
+ preferNativeGlyphNode
+ ? QSGTextNode::NativeRendering
+ : m_renderType;
+
QSGGlyphNode *node = m_renderContext->sceneGraphContext()->createGlyphNode(m_renderContext,
- preferNativeGlyphNode,
+ preferredRenderType,
m_renderTypeQuality);
-
node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
node->setStyle(style);
node->setStyleColor(styleColor);
@@ -85,7 +88,7 @@ QSGGlyphNode *QSGInternalTextNode::addGlyphs(const QPointF &position, const QGly
if (style == QQuickText::Outline && color.alpha() > 0 && styleColor != color) {
QSGGlyphNode *fillNode = m_renderContext->sceneGraphContext()->createGlyphNode(m_renderContext,
- preferNativeGlyphNode,
+ preferredRenderType,
m_renderTypeQuality);
fillNode->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
fillNode->setStyle(QQuickText::Normal);
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
index 4fd0003e54..ef43aa1012 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp
@@ -75,10 +75,10 @@ QSGPainterNode *QSGSoftwareContext::createPainterNode(QQuickPaintedItem *item)
return new QSGSoftwarePainterNode(item);
}
-QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality)
+QSGGlyphNode *QSGSoftwareContext::createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality)
{
Q_UNUSED(rc);
- Q_UNUSED(preferNativeGlyphNode);
+ Q_UNUSED(renderType);
Q_UNUSED(renderTypeQuality);
return new QSGSoftwareGlyphNode();
}
diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
index 6b95ebb1ad..68f18b490a 100644
--- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
+++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext_p.h
@@ -55,7 +55,7 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp
index 4271c079ce..76c521dfd1 100644
--- a/src/quick/scenegraph/qsgcontext.cpp
+++ b/src/quick/scenegraph/qsgcontext.cpp
@@ -474,6 +474,16 @@ void QSGRenderContext::preprocess()
}
/*!
+ Factory function for curve atlases that can be used to provide geometry for the curve
+ renderer for a given font.
+*/
+QSGCurveGlyphAtlas *QSGRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ Q_UNUSED(font);
+ return nullptr;
+}
+
+/*!
Factory function for scene graph backends of the distance-field glyph cache.
*/
QSGDistanceFieldGlyphCache *QSGRenderContext::distanceFieldGlyphCache(const QRawFont &, int)
diff --git a/src/quick/scenegraph/qsgcontext_p.h b/src/quick/scenegraph/qsgcontext_p.h
index 0bc50a2c2f..6f124c52dd 100644
--- a/src/quick/scenegraph/qsgcontext_p.h
+++ b/src/quick/scenegraph/qsgcontext_p.h
@@ -27,6 +27,7 @@
#include <QtQuick/qsgnode.h>
#include <QtQuick/qsgrendererinterface.h>
+#include <QtQuick/qsgtextnode.h>
#include <QtCore/qpointer.h>
@@ -65,6 +66,7 @@ class QRhiRenderPassDescriptor;
class QRhiCommandBuffer;
class QQuickGraphicsConfiguration;
class QQuickItem;
+class QSGCurveGlyphAtlas;
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP)
Q_DECLARE_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION)
@@ -98,7 +100,7 @@ public:
virtual QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) = 0;
virtual QSGInternalTextNode *createInternalTextNode(QSGRenderContext *renderContext);
virtual QSGPainterNode *createPainterNode(QQuickPaintedItem *item) = 0;
- virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) = 0;
+ virtual QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) = 0;
virtual QSGLayer *createLayer(QSGRenderContext *renderContext) = 0;
virtual QSGGuiThreadShaderEffectManager *createGuiThreadShaderEffectManager();
virtual QSGShaderEffectNode *createShaderEffectNode(QSGRenderContext *renderContext);
@@ -165,6 +167,7 @@ public:
virtual void preprocess();
virtual void invalidateGlyphCaches();
virtual QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality);
+ virtual QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font);
QSGTexture *textureForFactory(QQuickTextureFactory *factory, QQuickWindow *window);
virtual QSGTexture *createTexture(const QImage &image, uint flags = CreateTexture_Alpha) const = 0;
diff --git a/src/quickshapes/qquickshapeabstractcurvenode_p.h b/src/quick/scenegraph/qsgcurveabstractnode_p.h
index 68a7c052e1..aadc17d3f0 100644
--- a/src/quickshapes/qquickshapeabstractcurvenode_p.h
+++ b/src/quick/scenegraph/qsgcurveabstractnode_p.h
@@ -1,13 +1,12 @@
// 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 QQUICKSHAPEABSTRACTCURVENODE_P_H
-#define QQUICKSHAPEABSTRACTCURVENODE_P_H
+#ifndef QSGCURVEABSTRACTNODE_P_H
+#define QSGCURVEABSTRACTNODE_P_H
+#include <QtGui/qcolor.h>
#include <QtQuick/qsgnode.h>
-#include "qquickshapegenericrenderer_p.h"
-
//
// W A R N I N G
// -------------
@@ -21,12 +20,13 @@
QT_BEGIN_NAMESPACE
-class QQuickShapeAbstractCurveNode : public QSGGeometryNode
+class QSGCurveAbstractNode : public QSGGeometryNode
{
public:
virtual void setColor(QColor col) = 0;
+ virtual void cookGeometry() = 0;
};
QT_END_NAMESPACE
-#endif // QQUICKSHAPEABSTRACTCURVENODE_P_H
+#endif // QSGCURVEABSTRACTNODE_P_H
diff --git a/src/quickshapes/qquickshapecurvenode.cpp b/src/quick/scenegraph/qsgcurvefillnode.cpp
index 11b5ecbaf4..9fa526bb0a 100644
--- a/src/quickshapes/qquickshapecurvenode.cpp
+++ b/src/quick/scenegraph/qsgcurvefillnode.cpp
@@ -1,15 +1,12 @@
// 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
-#include "qquickshapecurvenode_p.h"
-#include "qquickshapecurvenode_p_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvefillnode_p_p.h"
QT_BEGIN_NAMESPACE
-namespace {
-}
-
-QQuickShapeCurveNode::QQuickShapeCurveNode()
+QSGCurveFillNode::QSGCurveFillNode()
{
setFlag(OwnsGeometry, true);
setGeometry(new QSGGeometry(attributes(), 0, 0));
@@ -17,13 +14,13 @@ QQuickShapeCurveNode::QQuickShapeCurveNode()
updateMaterial();
}
-void QQuickShapeCurveNode::updateMaterial()
+void QSGCurveFillNode::updateMaterial()
{
- m_material.reset(new QQuickShapeCurveMaterial(this));
+ m_material.reset(new QSGCurveFillMaterial(this));
setMaterial(m_material.data());
}
-void QQuickShapeCurveNode::cookGeometry()
+void QSGCurveFillNode::cookGeometry()
{
QSGGeometry *g = geometry();
if (g->indexType() != QSGGeometry::UnsignedIntType) {
@@ -48,7 +45,7 @@ void QQuickShapeCurveNode::cookGeometry()
m_uncookedVertexes.clear();
}
-const QSGGeometry::AttributeSet &QQuickShapeCurveNode::attributes()
+const QSGGeometry::AttributeSet &QSGCurveFillNode::attributes()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
diff --git a/src/quickshapes/qquickshapecurvenode_p.cpp b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
index c19672d1eb..b7b9c877c1 100644
--- a/src/quickshapes/qquickshapecurvenode_p.cpp
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.cpp
@@ -1,19 +1,20 @@
// 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
-#include "qquickshapecurvenode_p_p.h"
-#include "qquickshapecurvenode_p.h"
+#include "qsgcurvefillnode_p_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "util/qsggradientcache_p.h"
-#include "qquickshapegenericrenderer_p.h"
+#include <private/qsgtexture_p.h>
QT_BEGIN_NAMESPACE
namespace {
- class QQuickShapeCurveMaterialShader : public QSGMaterialShader
+ class QSGCurveFillMaterialShader : public QSGMaterialShader
{
public:
- QQuickShapeCurveMaterialShader(QQuickAbstractPathRenderer::FillGradientType gradientType,
+ QSGCurveFillMaterialShader(QGradient::Type gradientType,
bool includeStroke,
bool useDerivatives);
@@ -22,17 +23,17 @@ namespace {
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};
- QQuickShapeCurveMaterialShader::QQuickShapeCurveMaterialShader(QQuickAbstractPathRenderer::FillGradientType gradientType,
+ QSGCurveFillMaterialShader::QSGCurveFillMaterialShader(QGradient::Type gradientType,
bool includeStroke,
bool useDerivatives)
{
- QString baseName = QStringLiteral(":/qt-project.org/shapes/shaders_ng/shapecurve");
+ QString baseName = QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapecurve");
- if (gradientType == QQuickAbstractPathRenderer::LinearGradient) {
+ if (gradientType == QGradient::LinearGradient) {
baseName += QStringLiteral("_lg");
- } else if (gradientType == QQuickAbstractPathRenderer::RadialGradient) {
+ } else if (gradientType == QGradient::RadialGradient) {
baseName += QStringLiteral("_rg");
- } else if (gradientType == QQuickAbstractPathRenderer::ConicalGradient) {
+ } else if (gradientType == QGradient::ConicalGradient) {
baseName += QStringLiteral("_cg");
}
@@ -46,23 +47,23 @@ namespace {
setShaderFileName(FragmentStage, baseName + QStringLiteral(".frag.qsb"));
}
- void QQuickShapeCurveMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
+ void QSGCurveFillMaterialShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
Q_UNUSED(oldMaterial);
- const QQuickShapeCurveMaterial *m = static_cast<QQuickShapeCurveMaterial *>(newMaterial);
- const QQuickShapeCurveNode *node = m->node();
- if (binding != 1 || node->gradientType() == QQuickAbstractPathRenderer::NoGradient)
+ const QSGCurveFillMaterial *m = static_cast<QSGCurveFillMaterial *>(newMaterial);
+ const QSGCurveFillNode *node = m->node();
+ if (binding != 1 || node->gradientType() == QGradient::NoGradient)
return;
- const QQuickShapeGradientCacheKey cacheKey(node->fillGradient().stops,
+ const QSGGradientCacheKey cacheKey(node->fillGradient().stops,
node->fillGradient().spread);
- QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
+ QSGTexture *t = QSGGradientCache::cacheForRhi(state.rhi())->get(cacheKey);
t->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = t;
}
- bool QQuickShapeCurveMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+ bool QSGCurveFillMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
bool changed = false;
QByteArray *buf = state.uniformData();
@@ -89,11 +90,11 @@ namespace {
}
offset += 4;
- QQuickShapeCurveMaterial *newMaterial = static_cast<QQuickShapeCurveMaterial *>(newEffect);
- QQuickShapeCurveMaterial *oldMaterial = static_cast<QQuickShapeCurveMaterial *>(oldEffect);
+ QSGCurveFillMaterial *newMaterial = static_cast<QSGCurveFillMaterial *>(newEffect);
+ QSGCurveFillMaterial *oldMaterial = static_cast<QSGCurveFillMaterial *>(oldEffect);
- QQuickShapeCurveNode *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
- QQuickShapeCurveNode *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+ QSGCurveFillNode *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ QSGCurveFillNode *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
if (newNode == nullptr)
return changed;
@@ -134,7 +135,7 @@ namespace {
offset += 16;
}
- if (newNode->gradientType() == QQuickAbstractPathRenderer::NoGradient) {
+ if (newNode->gradientType() == QGradient::NoGradient) {
Q_ASSERT(buf->size() >= offset + 16);
QVector4D newColor = QVector4D(newNode->color().redF(),
@@ -154,7 +155,7 @@ namespace {
}
offset += 16;
- } else if (newNode->gradientType() == QQuickAbstractPathRenderer::LinearGradient) {
+ } else if (newNode->gradientType() == QGradient::LinearGradient) {
Q_ASSERT(buf->size() >= offset + 8 + 8);
QVector2D newGradientStart = QVector2D(newNode->fillGradient().a);
@@ -179,7 +180,7 @@ namespace {
}
offset += 8;
- } else if (newNode->gradientType() == QQuickAbstractPathRenderer::RadialGradient) {
+ } else if (newNode->gradientType() == QGradient::RadialGradient) {
Q_ASSERT(buf->size() >= offset + 8 + 8 + 4 + 4);
QVector2D newFocalPoint = QVector2D(newNode->fillGradient().b);
@@ -225,7 +226,7 @@ namespace {
}
offset += 4;
- } else if (newNode->gradientType() == QQuickAbstractPathRenderer::ConicalGradient) {
+ } else if (newNode->gradientType() == QGradient::ConicalGradient) {
Q_ASSERT(buf->size() >= offset + 8 + 4);
QVector2D newFocalPoint = QVector2D(newNode->fillGradient().a);
@@ -254,30 +255,30 @@ namespace {
}
}
-QQuickShapeCurveMaterial::QQuickShapeCurveMaterial(QQuickShapeCurveNode *node)
+QSGCurveFillMaterial::QSGCurveFillMaterial(QSGCurveFillNode *node)
: m_node(node)
{
setFlag(Blending, true);
setFlag(RequiresDeterminant, true);
}
-int QQuickShapeCurveMaterial::compare(const QSGMaterial *other) const
+int QSGCurveFillMaterial::compare(const QSGMaterial *other) const
{
if (other->type() != type())
return (type() - other->type());
- const QQuickShapeCurveMaterial *otherMaterial =
- static_cast<const QQuickShapeCurveMaterial *>(other);
+ const QSGCurveFillMaterial *otherMaterial =
+ static_cast<const QSGCurveFillMaterial *>(other);
- QQuickShapeCurveNode *a = node();
- QQuickShapeCurveNode *b = otherMaterial->node();
+ QSGCurveFillNode *a = node();
+ QSGCurveFillNode *b = otherMaterial->node();
if (a == b)
return 0;
if (int d = a->strokeColor().rgba() - b->strokeColor().rgba())
return d;
- if (a->gradientType() == QQuickAbstractPathRenderer::NoGradient) {
+ if (a->gradientType() == QGradient::NoGradient) {
if (int d = a->color().red() - b->color().red())
return d;
if (int d = a->color().green() - b->color().green())
@@ -287,8 +288,8 @@ int QQuickShapeCurveMaterial::compare(const QSGMaterial *other) const
if (int d = a->color().alpha() - b->color().alpha())
return d;
} else {
- const QQuickAbstractPathRenderer::GradientDesc &ga = a->fillGradient();
- const QQuickAbstractPathRenderer::GradientDesc &gb = b->fillGradient();
+ const QSGGradientCache::GradientDesc &ga = a->fillGradient();
+ const QSGGradientCache::GradientDesc &gb = b->fillGradient();
if (int d = ga.a.x() - gb.a.x())
return d;
@@ -321,7 +322,7 @@ int QQuickShapeCurveMaterial::compare(const QSGMaterial *other) const
return 0;
}
-QSGMaterialType *QQuickShapeCurveMaterial::type() const
+QSGMaterialType *QSGCurveFillMaterial::type() const
{
static QSGMaterialType type[8];
uint index = node()->gradientType();
@@ -333,11 +334,11 @@ QSGMaterialType *QQuickShapeCurveMaterial::type() const
return &type[index];
}
-QSGMaterialShader *QQuickShapeCurveMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
+QSGMaterialShader *QSGCurveFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
{
- return new QQuickShapeCurveMaterialShader(node()->gradientType(),
- node()->hasStroke(),
- renderMode == QSGRendererInterface::RenderMode3D);
+ return new QSGCurveFillMaterialShader(node()->gradientType(),
+ node()->hasStroke(),
+ renderMode == QSGRendererInterface::RenderMode3D);
}
diff --git a/src/quickshapes/qquickshapecurvenode_p.h b/src/quick/scenegraph/qsgcurvefillnode_p.h
index 9a79e6d314..3ef4caa61c 100644
--- a/src/quickshapes/qquickshapecurvenode_p.h
+++ b/src/quick/scenegraph/qsgcurvefillnode_p.h
@@ -1,13 +1,16 @@
// 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 QQUICKSHAPECURVENODE_P_H
-#define QQUICKSHAPECURVENODE_P_H
+#ifndef QSGCURVEFILLNODE_P_H
+#define QSGCURVEFILLNODE_P_H
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/private/qtquickexports_p.h>
+#include <QtQuick/private/qsggradientcache_p.h>
#include <QtQuick/qsgnode.h>
-#include "qquickshapeabstractcurvenode_p.h"
-#include "qquickshapegenericrenderer_p.h"
+#include "qsgcurveabstractnode_p.h"
//
// W A R N I N G
@@ -22,10 +25,10 @@
QT_BEGIN_NAMESPACE
-class QQuickShapeCurveNode : public QQuickShapeAbstractCurveNode
+class Q_QUICK_PRIVATE_EXPORT QSGCurveFillNode : public QSGCurveAbstractNode
{
public:
- QQuickShapeCurveNode();
+ QSGCurveFillNode();
void setColor(QColor col) override
{
@@ -66,17 +69,17 @@ public:
return m_strokeWidth;
}
- void setFillGradient(const QQuickAbstractPathRenderer::GradientDesc &fillGradient)
+ void setFillGradient(const QSGGradientCache::GradientDesc &fillGradient)
{
m_fillGradient = fillGradient;
}
- QQuickAbstractPathRenderer::GradientDesc fillGradient() const
+ QSGGradientCache::GradientDesc fillGradient() const
{
return m_fillGradient;
}
- void setGradientType(QQuickAbstractPathRenderer::FillGradientType type)
+ void setGradientType(QGradient::Type type)
{
if (m_gradientType != type) {
m_gradientType = type;
@@ -84,6 +87,11 @@ public:
}
}
+ QGradient::Type gradientType() const
+ {
+ return m_gradientType;
+ }
+
float debug() const
{
return m_debug;
@@ -94,10 +102,6 @@ public:
m_debug = newDebug;
}
- QQuickAbstractPathRenderer::FillGradientType gradientType() const
- {
- return m_gradientType;
- }
bool hasStroke() const
{
@@ -139,25 +143,51 @@ public:
duvdy.x(), duvdy.y(),
n[2].x(), n[2].y()
});
-
}
void appendTriangle(const QVector2D &v1,
const QVector2D &v2,
const QVector2D &v3,
- std::function<QVector3D(QVector2D)> uvForPoint)
+ const QVector3D &uv1,
+ const QVector3D &uv2,
+ const QVector3D &uv3,
+ const QVector2D &n1,
+ const QVector2D &n2,
+ const QVector2D &n3,
+ const QVector2D &duvdx,
+ const QVector2D &duvdy)
{
- appendTriangle({v1, v2, v3}, {}, uvForPoint);
- }
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v1.x(), v1.y(),
+ uv1.x(), uv1.y(), uv1.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n1.x(), n1.y()
+ });
- void appendIndex(quint32 index)
- {
- m_uncookedIndexes.append(index);
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v2.x(), v2.y(),
+ uv2.x(), uv2.y(), uv2.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n2.x(), n2.y()
+ });
+
+ m_uncookedIndexes.append(m_uncookedVertexes.size());
+ m_uncookedVertexes.append( { v3.x(), v3.y(),
+ uv3.x(), uv3.y(), uv3.z(),
+ duvdx.x(), duvdx.y(),
+ duvdy.x(), duvdy.y(),
+ n3.x(), n3.y()
+ });
}
- void appendIndexes(QVector<quint32> indexes)
+ void appendTriangle(const QVector2D &v1,
+ const QVector2D &v2,
+ const QVector2D &v3,
+ std::function<QVector3D(QVector2D)> uvForPoint)
{
- m_uncookedIndexes.append(indexes);
+ appendTriangle({v1, v2, v3}, {}, uvForPoint);
}
QVector<quint32> uncookedIndexes() const
@@ -165,7 +195,7 @@ public:
return m_uncookedIndexes;
}
- void cookGeometry();
+ void cookGeometry() override;
private:
struct CurveNodeVertex
@@ -182,8 +212,8 @@ private:
QColor m_strokeColor = Qt::transparent;
float m_strokeWidth = 0.0f;
float m_debug = 0.0f;
- QQuickAbstractPathRenderer::GradientDesc m_fillGradient;
- QQuickAbstractPathRenderer::FillGradientType m_gradientType = QQuickAbstractPathRenderer::NoGradient;
+ QSGGradientCache::GradientDesc m_fillGradient;
+ QGradient::Type m_gradientType = QGradient::NoGradient;
QScopedPointer<QSGMaterial> m_material;
@@ -193,4 +223,4 @@ private:
QT_END_NAMESPACE
-#endif // QQUICKSHAPECURVENODE_P_H
+#endif // QSGCURVEFILLNODE_P_H
diff --git a/src/quickshapes/qquickshapecurvenode_p_p.h b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
index 5b04785e31..d52f227d85 100644
--- a/src/quickshapes/qquickshapecurvenode_p_p.h
+++ b/src/quick/scenegraph/qsgcurvefillnode_p_p.h
@@ -1,9 +1,10 @@
// 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 QQUICKSHAPECURVENODE_P_P_H
-#define QQUICKSHAPECURVENODE_P_P_H
+#ifndef QSGCURVEFILLNODE_P_P_H
+#define QSGCURVEFILLNODE_P_P_H
+#include <QtQuick/private/qtquickexports_p.h>
#include <QtQuick/qsgmaterial.h>
//
@@ -19,14 +20,14 @@
QT_BEGIN_NAMESPACE
-class QQuickShapeCurveNode;
-class QQuickShapeCurveMaterial : public QSGMaterial
+class QSGCurveFillNode;
+class Q_QUICK_PRIVATE_EXPORT QSGCurveFillMaterial : public QSGMaterial
{
public:
- QQuickShapeCurveMaterial(QQuickShapeCurveNode *node);
+ QSGCurveFillMaterial(QSGCurveFillNode *node);
int compare(const QSGMaterial *other) const override;
- QQuickShapeCurveNode *node() const
+ QSGCurveFillNode *node() const
{
return m_node;
}
@@ -35,9 +36,9 @@ private:
QSGMaterialType *type() const override;
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override;
- QQuickShapeCurveNode *m_node;
+ QSGCurveFillNode *m_node;
};
QT_END_NAMESPACE
-#endif // QQUICKSHAPECURVENODE_P_P_H
+#endif // QSGCURVEFILLNODE_P_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas.cpp b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
new file mode 100644
index 0000000000..410ce2dd26
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas.cpp
@@ -0,0 +1,142 @@
+// 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
+
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurveprocessor_p.h"
+#include "util/qquadpath_p.h"
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/qpainterpath.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphAtlas::QSGCurveGlyphAtlas(const QRawFont &font)
+ : m_font(font)
+{
+ // The font size used for the curve atlas currently affects the outlines, since we don't
+ // really support cosmetic outlines. Therefore we need to pick one which gives large enough
+ // triangles relative to glyph size that we can reuse the same triangles for any font size.
+ // When experimenting, 10 works for all font sizes we tested, so we currently default to this
+ // but allow overriding it.
+ static int curveGlyphAtlasFontSize = qEnvironmentVariableIntValue("QSGCURVEGLYPHATLAS_FONT_SIZE");
+ m_font.setPixelSize(curveGlyphAtlasFontSize > 0 ? qreal(curveGlyphAtlasFontSize) : 10.0);
+}
+
+QSGCurveGlyphAtlas::~QSGCurveGlyphAtlas()
+{
+}
+
+void QSGCurveGlyphAtlas::populate(const QList<glyph_t> &glyphs)
+{
+ for (glyph_t glyphIndex : glyphs) {
+ if (!m_glyphs.contains(glyphIndex)) {
+ QPainterPath path = m_font.pathForGlyph(glyphIndex);
+ QQuadPath quadPath = QQuadPath::fromPainterPath(path);
+ quadPath.setFillRule(Qt::WindingFill);
+
+ Glyph glyph;
+
+ QSGCurveProcessor::processStroke(quadPath, 2, 2, Qt::MiterJoin, Qt::FlatCap,
+ [&glyph](const std::array<QVector2D, 3> &s,
+ const std::array<QVector2D, 3> &p,
+ const std::array<QVector2D, 3> &n,
+ bool isLine) {
+ glyph.strokeVertices.append(s.at(0));
+ glyph.strokeVertices.append(s.at(1));
+ glyph.strokeVertices.append(s.at(2));
+
+ glyph.strokeUvs.append(p.at(0));
+ glyph.strokeUvs.append(p.at(1));
+ glyph.strokeUvs.append(p.at(2));
+
+ glyph.strokeNormals.append(n.at(0));
+ glyph.strokeNormals.append(n.at(1));
+ glyph.strokeNormals.append(n.at(2));
+
+ glyph.strokeElementIsLine.append(isLine);
+ });
+
+ quadPath = quadPath.subPathsClosed();
+ quadPath.addCurvatureData(); // ### Since the inside of glyphs is defined by order of
+ // vertices, this step could be simplified
+ QSGCurveProcessor::solveOverlaps(quadPath);
+
+ QSGCurveProcessor::processFill(quadPath,
+ Qt::WindingFill,
+ [&glyph](const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n,
+ QSGCurveProcessor::uvForPointCallback uvForPoint)
+ {
+ glyph.vertices.append(v.at(0));
+ glyph.vertices.append(v.at(1));
+ glyph.vertices.append(v.at(2));
+
+ QVector3D uv1 = uvForPoint(v.at(0));
+ glyph.uvs.append(uv1);
+ glyph.uvs.append(uvForPoint(v.at(1)));
+ glyph.uvs.append(uvForPoint(v.at(2)));
+
+ glyph.normals.append(n.at(0));
+ glyph.normals.append(n.at(1));
+ glyph.normals.append(n.at(2));
+
+ glyph.duvdx.append(QVector2D(uvForPoint(v.at(0) + QVector2D(1, 0))) - QVector2D(uv1));
+ glyph.duvdy.append(QVector2D(uvForPoint(v.at(0) + QVector2D(0, 1))) - QVector2D(uv1));
+ });
+
+ m_glyphs.insert(glyphIndex, glyph);
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const QVector2D v(position);
+ for (qsizetype i = glyph.strokeElementIsLine.size() - 1; i >= 0; --i) {
+ QVector2D v1 = glyph.strokeVertices.at(i * 3 + 0) + v;
+ QVector2D v2 = glyph.strokeVertices.at(i * 3 + 1) + v;
+ QVector2D v3 = glyph.strokeVertices.at(i * 3 + 2) + v;
+ if (glyph.strokeElementIsLine.at(i)) {
+ node->appendTriangle({ v1, v2, v3 },
+ std::array<QVector2D, 2>({ glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 2) + v }),
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+ } else {
+ node->appendTriangle({ v1, v2, v3 },
+ { glyph.strokeUvs.at(i * 3 + 0) + v, glyph.strokeUvs.at(i * 3 + 1) + v, glyph.strokeUvs.at(i * 3 + 2) + v },
+ { glyph.strokeNormals.at(i * 3 + 0), glyph.strokeNormals.at(i * 3 + 1), glyph.strokeNormals.at(i * 3 + 2) });
+
+ }
+ }
+}
+
+void QSGCurveGlyphAtlas::addGlyph(QSGCurveFillNode *node,
+ glyph_t glyphIndex,
+ const QPointF &position,
+ qreal pixelSize) const
+{
+ const Glyph &glyph = m_glyphs[glyphIndex];
+
+ const float scaleFactor = pixelSize / m_font.pixelSize();
+ const QVector2D v(position);
+ for (qsizetype i = 0; i < glyph.vertices.size() / 3; ++i) {
+ node->appendTriangle(scaleFactor * glyph.vertices.at(i * 3 + 0) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 1) + v,
+ scaleFactor * glyph.vertices.at(i * 3 + 2) + v,
+ glyph.uvs.at(i * 3 + 0),
+ glyph.uvs.at(i * 3 + 1),
+ glyph.uvs.at(i * 3 + 2),
+ glyph.normals.at(i * 3 + 0),
+ glyph.normals.at(i * 3 + 1),
+ glyph.normals.at(i * 3 + 2),
+ glyph.duvdx.at(i) / scaleFactor,
+ glyph.duvdy.at(i) / scaleFactor);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphatlas_p.h b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
new file mode 100644
index 0000000000..317abe11f8
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphatlas_p.h
@@ -0,0 +1,69 @@
+// 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 QSGCURVEGLYPHATLAS_P_H
+#define QSGCURVEGLYPHATLAS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qrawfont.h>
+#include <QtGui/private/qtextengine_p.h>
+#include <QtQuick/private/qtquickexports_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveFillNode;
+class QSGCurveStrokeNode;
+
+class Q_QUICK_PRIVATE_EXPORT QSGCurveGlyphAtlas
+{
+public:
+ QSGCurveGlyphAtlas(const QRawFont &font);
+ virtual ~QSGCurveGlyphAtlas();
+
+ void populate(const QList<glyph_t> &glyphs);
+ void addGlyph(QSGCurveFillNode *node,
+ glyph_t glyph,
+ const QPointF &position,
+ qreal pixelSize) const;
+ void addStroke(QSGCurveStrokeNode *node,
+ glyph_t glyph,
+ const QPointF &position) const;
+
+ qreal fontSize() const
+ {
+ return m_font.pixelSize();
+ }
+
+private:
+ struct Glyph
+ {
+ QList<QVector2D> vertices;
+ QList<QVector3D> uvs;
+ QList<QVector2D> normals;
+ QList<QVector2D> duvdx;
+ QList<QVector2D> duvdy;
+
+ QList<QVector2D> strokeVertices;
+ QList<QVector2D> strokeUvs;
+ QList<QVector2D> strokeNormals;
+ QList<bool> strokeElementIsLine;
+ };
+
+ QHash<glyph_t, Glyph> m_glyphs;
+ QRawFont m_font;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QSGCURVEGLYPHATLAS_P_H
diff --git a/src/quick/scenegraph/qsgcurveglyphnode.cpp b/src/quick/scenegraph/qsgcurveglyphnode.cpp
new file mode 100644
index 0000000000..4e20ce75b2
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode.cpp
@@ -0,0 +1,165 @@
+// 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
+
+#include "qsgcurveglyphnode_p.h"
+#include "qsgcurveglyphatlas_p.h"
+#include "qsgcurvefillnode_p.h"
+#include "qsgcurvestrokenode_p.h"
+
+#include <private/qsgcurveabstractnode_p.h>
+#include <private/qsgcontext_p.h>
+#include <private/qsgtexturematerial_p.h>
+
+#include <private/qrawfont_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+
+QSGCurveGlyphNode::QSGCurveGlyphNode(QSGRenderContext *context)
+ : m_context(context)
+ , m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
+ , m_dirtyGeometry(false)
+{
+ setFlag(UsePreprocess);
+
+ // #### To avoid asserts: we should probably merge this with QSGCurveFillNode
+ setGeometry(&m_geometry);
+ setMaterial(new QSGTextureMaterial);
+}
+
+QSGCurveGlyphNode::~QSGCurveGlyphNode()
+{
+}
+
+void QSGCurveGlyphNode::setPreferredAntialiasingMode(AntialiasingMode mode)
+{
+ Q_UNUSED(mode);
+}
+
+void QSGCurveGlyphNode::setColor(const QColor &color)
+{
+ m_color = color;
+ if (m_glyphNode != nullptr)
+ m_glyphNode->setColor(color);
+}
+
+void QSGCurveGlyphNode::setStyleColor(const QColor &styleColor)
+{
+ m_styleColor = styleColor;
+ if (m_styleNode != nullptr)
+ m_styleNode->setColor(styleColor);
+}
+
+void QSGCurveGlyphNode::setStyle(QQuickText::TextStyle style)
+{
+ if (m_style != style) {
+ m_style = style;
+ m_dirtyGeometry = true;
+ update();
+ }
+}
+
+void QSGCurveGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
+{
+ m_glyphs = glyphs;
+
+ QRawFont font = glyphs.rawFont();
+ m_fontSize = font.pixelSize();
+ m_position = QPointF(position.x(), position.y() - font.ascent());
+
+ m_curveGlyphAtlas = m_context->curveGlyphAtlas(font);
+
+ m_curveGlyphAtlas->populate(glyphs.glyphIndexes());
+
+ m_dirtyGeometry = true;
+
+#ifdef QSG_RUNTIME_DESCRIPTION
+ qsgnode_set_description(this, QString::number(glyphs.glyphIndexes().count())
+ + QStringLiteral(" curve glyphs: ")
+ + m_glyphs.rawFont().familyName()
+ + QStringLiteral(" ")
+ + QString::number(m_glyphs.rawFont().pixelSize()));
+#endif
+}
+
+void QSGCurveGlyphNode::update()
+{
+ markDirty(DirtyGeometry);
+}
+
+void QSGCurveGlyphNode::preprocess()
+{
+ if (m_dirtyGeometry)
+ updateGeometry();
+}
+
+void QSGCurveGlyphNode::updateGeometry()
+{
+ delete m_glyphNode;
+ m_glyphNode = nullptr;
+
+ delete m_styleNode;
+ m_styleNode = nullptr;
+
+ Q_ASSERT(m_curveGlyphAtlas != nullptr);
+
+ m_glyphNode = new QSGCurveFillNode;
+ m_glyphNode->setColor(m_color);
+
+ QPointF offset;
+
+ float fontScale = float(m_fontSize / m_curveGlyphAtlas->fontSize());
+ QSGCurveFillNode *raisedSunkenStyleNode = nullptr;
+ QSGCurveStrokeNode *outlineNode = nullptr;
+ if (m_style == QQuickText::Raised || m_style == QQuickText::Sunken) {
+ raisedSunkenStyleNode = new QSGCurveFillNode;
+ raisedSunkenStyleNode ->setColor(m_styleColor);
+
+ offset = m_style == QQuickText::Raised ? QPointF(0.0f, 1.0f) : QPointF(0.0f, -1.0f);
+ m_styleNode = raisedSunkenStyleNode;
+ } else if (m_style == QQuickText::Outline) {
+ outlineNode = new QSGCurveStrokeNode;
+ outlineNode->setColor(m_styleColor);
+ outlineNode->setStrokeWidth(2 / fontScale);
+ outlineNode->setLocalScale(fontScale);
+
+ m_styleNode = outlineNode;
+ }
+
+ const QVector<quint32> indexes = m_glyphs.glyphIndexes();
+ const QVector<QPointF> positions = m_glyphs.positions();
+ for (qsizetype i = 0; i < indexes.size(); ++i) {
+ if (i == 0)
+ m_baseLine = positions.at(i);
+ m_curveGlyphAtlas->addGlyph(m_glyphNode,
+ indexes.at(i),
+ m_position + positions.at(i),
+ m_fontSize);
+ if (raisedSunkenStyleNode != nullptr) {
+ m_curveGlyphAtlas->addGlyph(raisedSunkenStyleNode,
+ indexes.at(i),
+ m_position + positions.at(i) + offset,
+ m_fontSize);
+ }
+ if (outlineNode != nullptr) {
+ // Since the stroke node will scale everything by fontScale internally (the
+ // shader does not support pre-transforming the vertices), we have to also first
+ // do the inverse scale on the glyph position to get the correct position.
+ m_curveGlyphAtlas->addStroke(outlineNode,
+ indexes.at(i),
+ (m_position + positions.at(i)) / fontScale);
+ }
+ }
+
+ if (m_styleNode != nullptr) {
+ m_styleNode->cookGeometry();
+ appendChildNode(m_styleNode);
+ }
+
+ m_glyphNode->cookGeometry();
+ appendChildNode(m_glyphNode);
+
+ m_dirtyGeometry = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveglyphnode_p.h b/src/quick/scenegraph/qsgcurveglyphnode_p.h
new file mode 100644
index 0000000000..862fdbf70a
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveglyphnode_p.h
@@ -0,0 +1,69 @@
+// 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 QSGCURVEGLYPHNODE_P_H
+#define QSGCURVEGLYPHNODE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtquickexports_p.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgbasicglyphnode_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGCurveGlyphAtlas;
+class QSGCurveFillNode;
+class QSGCurveAbstractNode;
+
+class Q_QUICK_PRIVATE_EXPORT QSGCurveGlyphNode : public QSGGlyphNode
+{
+public:
+ QSGCurveGlyphNode(QSGRenderContext *context);
+ ~QSGCurveGlyphNode();
+ void setGlyphs(const QPointF &position, const QGlyphRun &glyphs) override;
+ void update() override;
+ void preprocess() override;
+ void setPreferredAntialiasingMode(AntialiasingMode) override;
+ void updateGeometry();
+ void setColor(const QColor &color) override;
+ void setStyle(QQuickText::TextStyle style) override;
+
+ void setStyleColor(const QColor &color) override;
+ QPointF baseLine() const override { return m_baseLine; }
+
+private:
+ QSGRenderContext *m_context;
+ QSGCurveGlyphAtlas *m_curveGlyphAtlas = nullptr;
+ QSGGeometry m_geometry;
+ QColor m_color = Qt::black;
+
+ struct GlyphInfo {
+ QVector<quint32> indexes;
+ QVector<QPointF> positions;
+ };
+
+ uint m_dirtyGeometry: 1;
+ qreal m_fontSize = 0.0f;
+ QGlyphRun m_glyphs;
+ QQuickText::TextStyle m_style;
+ QColor m_styleColor;
+ QPointF m_baseLine;
+ QPointF m_position;
+
+ QSGCurveFillNode *m_glyphNode = nullptr;
+ QSGCurveAbstractNode *m_styleNode = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/quick/scenegraph/qsgcurveprocessor.cpp b/src/quick/scenegraph/qsgcurveprocessor.cpp
new file mode 100644
index 0000000000..805cdf3a46
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor.cpp
@@ -0,0 +1,1109 @@
+// 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
+
+#include "qsgcurveprocessor_p.h"
+
+#include <QtGui/private/qtriangulator_p.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcSGCurveProcessor, "qt.quick.curveprocessor");
+
+namespace {
+// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
+// v1 maps to [1,0], v2 maps to [0,1]. p is the point to be mapped to uv in this space (i.e. vector from p0)
+static inline QVector2D 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 {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
+static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
+{
+ QVector2D v1 = 2 * (p1 - p0);
+ QVector2D v2 = p2 - v1 - p0;
+ return uvForPoint(v1, v2, p - p0);
+}
+
+static QVector3D elementUvForPoint(const QQuadPath::Element& e, QVector2D p)
+{
+ auto uv = curveUv(e.startPoint(), e.controlPoint(), e.endPoint(), p);
+ if (e.isLine())
+ return { uv.x(), uv.y(), 0.0f };
+ else
+ return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
+}
+
+static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
+{
+ auto v = b - a;
+ return {v.y(), -v.x()};
+}
+
+// The sign of the return value indicates which side of the line defined by a and n the point p falls
+static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
+{
+ float dot = QVector2D::dotProduct(p - a, n);
+ return dot;
+};
+
+template<typename Func>
+void iteratePath(const QQuadPath &path, int index, Func &&lambda)
+{
+ const auto &element = path.elementAt(index);
+ if (element.childCount() == 0) {
+ lambda(element, index);
+ } else {
+ for (int i = 0; i < element.childCount(); ++i)
+ iteratePath(path, element.indexOfChild(i), lambda);
+ }
+}
+
+static inline float determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
+{
+ return p1.x() * (p2.y() - p3.y())
+ + p2.x() * (p3.y() - p1.y())
+ + p3.x() * (p1.y() - p2.y());
+}
+
+/*
+ Clever triangle overlap algorithm. Stack Overflow says:
+
+ You can prove that the two triangles do not collide by finding an edge (out of the total 6
+ edges that make up the two triangles) that acts as a separating line where all the vertices
+ of one triangle lie on one side and the vertices of the other triangle lie on the other side.
+ If you can find such an edge then it means that the triangles do not intersect otherwise the
+ triangles are colliding.
+*/
+using TrianglePoints = std::array<QVector2D, 3>;
+using LinePoints = std::array<QVector2D, 2>;
+
+// The sign of the determinant tells the winding order: positive means counter-clockwise
+
+static inline double determinant(const TrianglePoints &p)
+{
+ return determinant(p[0], p[1], p[2]);
+}
+
+// Fix the triangle so that the determinant is positive
+static void fixWinding(TrianglePoints &p)
+{
+ double det = determinant(p);
+ if (det < 0.0) {
+ qSwap(p[0], p[1]);
+ }
+}
+
+// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
+// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
+// a positive determinant].
+bool checkEdge(QVector2D &p1, QVector2D &p2, QVector2D &p, float epsilon)
+{
+ return determinant(p1, p2, p) <= epsilon;
+}
+
+
+bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2, float epsilon = 1.0/32)
+{
+ // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
+ fixWinding(triangle1);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
+ checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
+ return false;
+ }
+
+ // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
+ fixWinding(triangle2);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+
+ if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
+ checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line, float epsilon = 1.0/32)
+{
+ // See if all vertices of the triangle are on the same side of the line
+ bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
+ auto s2 = determinant(line[0], line[1], triangle[1]) < 0;
+ auto s3 = determinant(line[0], line[1], triangle[2]) < 0;
+ // If all determinants have the same sign, then there is no overlap
+ if (s1 == s2 && s2 == s3) {
+ return false;
+ }
+ // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
+ fixWinding(triangle);
+ for (int i = 0; i < 3; i++) {
+ int ni = (i + 1) % 3;
+ if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
+ checkEdge(triangle[i], triangle[ni], line[1], epsilon))
+ return false;
+ }
+
+ return true;
+}
+
+// We could slightly optimize this if we did fixWinding in advance
+bool checkTriangleContains (QVector2D pt, QVector2D v1, QVector2D v2, QVector2D v3, float epsilon = 1.0/32)
+{
+ float d1, d2, d3;
+ d1 = determinant(pt, v1, v2);
+ d2 = determinant(pt, v2, v3);
+ d3 = determinant(pt, v3, v1);
+
+ bool allNegative = d1 < -epsilon && d2 < -epsilon && d3 < -epsilon;
+ bool allPositive = d1 > epsilon && d2 > epsilon && d3 > epsilon;
+
+ return allNegative || allPositive;
+}
+
+// e1 is always a concave curve. e2 can be curve or line
+static bool isOverlap(const QQuadPath &path, int e1, int e2)
+{
+ const QQuadPath::Element &element1 = path.elementAt(e1);
+ const QQuadPath::Element &element2 = path.elementAt(e2);
+
+ TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
+
+ if (element2.isLine()) {
+ LinePoints line{ element2.startPoint(), element2.endPoint() };
+ return checkLineTriangleOverlap(t1, line);
+ } else {
+ TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
+ return checkTriangleOverlap(t1, t2);
+ }
+
+ return false;
+}
+
+static bool isOverlap(const QQuadPath &path, int index, const QVector2D &vertex)
+{
+ const QQuadPath::Element &elem = path.elementAt(index);
+ return checkTriangleContains(vertex, elem.startPoint(), elem.controlPoint(), elem.endPoint());
+}
+
+struct TriangleData
+{
+ TrianglePoints points;
+ int pathElementIndex;
+ TrianglePoints normals;
+};
+
+// Returns a vector that is normal to baseLine, pointing to the right
+QVector2D normalVector(QVector2D baseLine)
+{
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+ return normal;
+}
+
+// Returns a vector that is normal to the path and pointing to the right. If endSide is false
+// the vector is normal to the start point, otherwise to the end point
+QVector2D normalVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine())
+ return normalVector(element.endPoint() - element.startPoint());
+ else if (!endSide)
+ return normalVector(element.controlPoint() - element.startPoint());
+ else
+ return normalVector(element.endPoint() - element.controlPoint());
+}
+
+// Returns a vector that is parallel to the path. If endSide is false
+// the vector starts at the start point and points forward,
+// otherwise it starts at the end point and points backward
+QVector2D tangentVector(const QQuadPath::Element &element, bool endSide = false)
+{
+ if (element.isLine()) {
+ if (!endSide)
+ return element.endPoint() - element.startPoint();
+ else
+ return element.startPoint() - element.endPoint();
+ } else {
+ if (!endSide)
+ return element.controlPoint() - element.startPoint();
+ else
+ return element.controlPoint() - element.endPoint();
+ }
+}
+
+// Really simplistic O(n^2) triangulator - only intended for five points
+QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, const QList<QVector2D> &normals, int elementIndex)
+{
+ int count = pts.size();
+ Q_ASSERT(count >= 3);
+ Q_ASSERT(normals.size() == count);
+
+ // First we find the convex hull: it's always in positive determinant winding order
+ QList<int> hull;
+ float det1 = determinant(pts[0], pts[1], pts[2]);
+ if (det1 > 0)
+ hull << 0 << 1 << 2;
+ else
+ hull << 2 << 1 << 0;
+ auto connectableInHull = [&](int idx) -> QList<int> {
+ QList<int> r;
+ const int n = hull.size();
+ const auto &pt = pts[idx];
+ for (int i = 0; i < n; ++i) {
+ const auto &i1 = hull.at(i);
+ const auto &i2 = hull.at((i+1) % n);
+ if (determinant(pts[i1], pts[i2], pt) < 0.0f)
+ r << i;
+ }
+ return r;
+ };
+ for (int i = 3; i < count; ++i) {
+ auto visible = connectableInHull(i);
+ if (visible.isEmpty())
+ continue;
+ int visCount = visible.count();
+ int hullCount = hull.count();
+ // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
+ // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
+ int boundaryStart = visible[0];
+ for (int j = 0; j < visCount - 1; ++j) {
+ if ((visible[j] + 1) % hullCount != visible[j+1]) {
+ boundaryStart = visible[j + 1];
+ break;
+ }
+ }
+ // Finally replace the points that are now inside the hull
+ // We insert the new point after boundaryStart, and before boundaryStart + visCount (modulo...)
+ // and remove the points in between
+ int pointsToKeep = hullCount - visCount + 1;
+ QList<int> newHull;
+ newHull << i;
+ for (int j = 0; j < pointsToKeep; ++j) {
+ newHull << hull.at((j + boundaryStart + visCount) % hullCount);
+ }
+ hull = newHull;
+ }
+
+ // Now that we have a convex hull, we can trivially triangulate it
+ QList<TriangleData> ret;
+ for (int i = 1; i < hull.size() - 1; ++i) {
+ int i0 = hull[0];
+ int i1 = hull[i];
+ int i2 = hull[i+1];
+ ret.append({{pts[i0], pts[i1], pts[i2]}, elementIndex, {normals[i0], normals[i1], normals[i2]}});
+ }
+ return ret;
+}
+
+static bool needsSplit(const QQuadPath::Element &el)
+{
+ const auto v1 = el.controlPoint() - el.startPoint();
+ const auto v2 = el.endPoint() - el.controlPoint();
+ float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
+ return cos < 0.9;
+}
+static void splitElementIfNecessary(QQuadPath &path, int index)
+{
+ auto &e = path.elementAt(index);
+ if (e.isLine())
+ return;
+ if (e.childCount() == 0) {
+ if (needsSplit(e))
+ path.splitElementAt(index);
+ } else {
+ for (int i = 0; i < e.childCount(); ++i)
+ splitElementIfNecessary(path, e.indexOfChild(i));
+ }
+}
+
+static QQuadPath subdivide(const QQuadPath &path, int subdivisions)
+{
+ QQuadPath newPath = path;
+
+ for (int i = 0; i < subdivisions; ++i)
+ for (int j = 0; j < newPath.elementCount(); j++)
+ splitElementIfNecessary(newPath, j);
+ return newPath;
+}
+
+static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
+{
+ const bool bevelJoin = joinStyle == Qt::BevelJoin;
+ const bool roundJoin = joinStyle == Qt::RoundJoin;
+ const bool miterJoin = !bevelJoin && !roundJoin;
+
+ const bool roundCap = capStyle == Qt::RoundCap;
+ const bool squareCap = capStyle == Qt::SquareCap;
+ // We can't use the simple miter for miter joins, since the shader currently only supports round joins
+ const bool simpleMiter = joinStyle == Qt::RoundJoin;
+
+ Q_ASSERT(miterLimit > 0 || !miterJoin);
+ float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
+
+ const float penFactor = penWidth / 2;
+
+ // Returns {inner1, inner2, outer1, outer2, outerMiter}
+ // where foo1 is for the end of element1 and foo2 is for the start of element2
+ // and inner1 == inner2 unless we had to give up finding a decent point
+ auto calculateJoin = [&](const QQuadPath::Element *element1, const QQuadPath::Element *element2,
+ bool &outerBisectorWithinMiterLimit, bool &innerIsRight, bool &giveUp) -> std::array<QVector2D, 5>
+ {
+ outerBisectorWithinMiterLimit = true;
+ innerIsRight = true;
+ giveUp = false;
+ if (!element1) {
+ Q_ASSERT(element2);
+ QVector2D n = normalVector(*element2).normalized();
+ return {n, n, -n, -n, -n};
+ }
+ if (!element2) {
+ Q_ASSERT(element1);
+ QVector2D n = normalVector(*element1, true).normalized();
+ return {n, n, -n, -n, -n};
+ }
+
+ Q_ASSERT(element1->endPoint() == element2->startPoint());
+
+ const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
+ const auto p2 = element1->endPoint();
+ const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
+
+ const auto v1 = (p1 - p2).normalized();
+ const auto v2 = (p3 - p2).normalized();
+ const auto b = (v1 + v2);
+
+ constexpr float epsilon = 1.0f / 32.0f;
+ bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
+
+ if (smoothJoin) {
+ // v1 and v2 are almost parallel and pointing in opposite directions
+ // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
+ QVector2D n1(-v1.y(), v1.x());
+ QVector2D n2(-v2.y(), v2.x());
+ QVector2D n = (n2 - n1).normalized();
+ return {n, n, -n, -n, -n};
+ }
+ // Calculate the length of the bisector, so it will cover the entire miter.
+ // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
+ // dot product of two unit vectors is the cosine of the angle between them
+ // The length of the miter is w/sin(x/2) where x is the angle between the two elements
+
+ const auto bisector = b.normalized();
+ float cos2x = QVector2D::dotProduct(v1, v2);
+ cos2x = qMin(1.0f, cos2x); // Allow for float inaccuracy
+ float sine = sqrt((1.0f - cos2x) / 2);
+ innerIsRight = determinant(p1, p2, p3) > 0;
+ sine = qMax(sine, 0.01f); // Avoid divide by zero
+ float length = penFactor / sine;
+
+ // Check if bisector is longer than one of the lines it's trying to bisect
+
+ auto tooLong = [](QVector2D p1, QVector2D p2, QVector2D n, float length, float margin) -> bool {
+ auto v = p2 - p1;
+ // It's too long if the projection onto the bisector is longer than the bisector
+ // and the projection onto the normal to the bisector is shorter
+ // than the pen margin (that projection is just v - proj)
+ // (we're adding a 10% safety margin to make room for AA -- not exact)
+ auto projLen = QVector2D::dotProduct(v, n);
+ return projLen * 0.9f < length && (v - n * projLen).length() * 0.9 < margin;
+ };
+
+
+ // The angle bisector of the tangent lines is not correct for curved lines. We could fix this by calculating
+ // the exact intersection point, but for now just give up and use the normals.
+
+ giveUp = !element1->isLine() || !element2->isLine()
+ || tooLong(p1, p2, bisector, length, penFactor)
+ || tooLong(p3, p2, bisector, length, penFactor);
+ outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
+ bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
+ const QVector2D bn = bisector / sine;
+
+ if (simpleJoin)
+ return {bn, bn, -bn, -bn, -bn}; // We only have one inner and one outer point TODO: change inner point when conflict/curve
+ const QVector2D n1 = normalVector(*element1, true).normalized();
+ const QVector2D n2 = normalVector(*element2).normalized();
+ if (giveUp) {
+ if (innerIsRight)
+ return {n1, n2, -n1, -n2, -bn};
+ else
+ return {-n1, -n2, n1, n2, -bn};
+
+ } else {
+ if (innerIsRight)
+ return {bn, bn, -n1, -n2, -bn};
+ else
+ return {bn, bn, n1, n2, -bn};
+ }
+ };
+
+ QList<TriangleData> ret;
+
+ auto triangulateCurve = [&](int idx, const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, const QVector2D &p4,
+ const QVector2D &n1, const QVector2D &n2, const QVector2D &n3, const QVector2D &n4)
+ {
+ const auto &element = path.elementAt(idx);
+ const auto &s = element.startPoint();
+ const auto &c = element.controlPoint();
+ const auto &e = element.endPoint();
+ // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
+ bool controlPointOnRight = determinant(s, c, e) > 0;
+ QVector2D startNormal = normalVector(element).normalized();
+ QVector2D endNormal = normalVector(element, true).normalized();
+ QVector2D controlPointNormal = (startNormal + endNormal).normalized();
+ if (controlPointOnRight)
+ controlPointNormal = -controlPointNormal;
+ QVector2D p5 = c + controlPointNormal * penFactor; // This is too simplistic
+ TrianglePoints t1{p1, p2, p5};
+ TrianglePoints t2{p3, p4, p5};
+ bool simpleCase = !checkTriangleOverlap(t1, t2);
+
+ if (simpleCase) {
+ ret.append({{p1, p2, p5}, idx, {n1, n2, controlPointNormal}});
+ ret.append({{p3, p4, p5}, idx, {n3, n4, controlPointNormal}});
+ if (controlPointOnRight) {
+ ret.append({{p1, p3, p5}, idx, {n1, n3, controlPointNormal}});
+ } else {
+ ret.append({{p2, p4, p5}, idx, {n2, n4, controlPointNormal}});
+ }
+ } else {
+ ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
+ }
+ };
+
+ // Each element is calculated independently, so we don't have to special-case closed sub-paths.
+ // Take care so the end points of one element are precisely equal to the start points of the next.
+ // Any additional triangles needed for joining are added at the end of the current element.
+
+ int count = path.elementCount();
+ int subStart = 0;
+ while (subStart < count) {
+ int subEnd = subStart;
+ for (int i = subStart + 1; i < count; ++i) {
+ const auto &e = path.elementAt(i);
+ if (e.isSubpathStart()) {
+ subEnd = i - 1;
+ break;
+ }
+ if (i == count - 1) {
+ subEnd = i;
+ break;
+ }
+ }
+ bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
+ const int subCount = subEnd - subStart + 1;
+
+ auto addIdx = [&](int idx, int delta) -> int {
+ int subIdx = idx - subStart;
+ if (closed)
+ subIdx = (subIdx + subCount + delta) % subCount;
+ else
+ subIdx += delta;
+ return subStart + subIdx;
+ };
+ auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
+ int subIdx = idx - subStart;
+ if (closed) {
+ subIdx = (subIdx + subCount + delta) % subCount;
+ return &path.elementAt(subStart + subIdx);
+ }
+ subIdx += delta;
+ if (subIdx >= 0 && subIdx < subCount)
+ return &path.elementAt(subStart + subIdx);
+ return nullptr;
+ };
+
+ for (int i = subStart; i <= subEnd; ++i) {
+ const auto &element = path.elementAt(i);
+ const auto *nextElement = elementAt(i, +1);
+ const auto *prevElement = elementAt(i, -1);
+
+ const auto &s = element.startPoint();
+ const auto &e = element.endPoint();
+
+ bool startInnerIsRight;
+ bool startBisectorWithinMiterLimit; // Not used
+ bool giveUpOnStartJoin; // Not used
+ auto startJoin = calculateJoin(prevElement, &element,
+ startBisectorWithinMiterLimit, startInnerIsRight,
+ giveUpOnStartJoin);
+ const QVector2D &startInner = startJoin[1];
+ const QVector2D &startOuter = startJoin[3];
+
+ bool endInnerIsRight;
+ bool endBisectorWithinMiterLimit;
+ bool giveUpOnEndJoin;
+ auto endJoin = calculateJoin(&element, nextElement,
+ endBisectorWithinMiterLimit, endInnerIsRight,
+ giveUpOnEndJoin);
+ QVector2D endInner = endJoin[0];
+ QVector2D endOuter = endJoin[2];
+ QVector2D nextOuter = endJoin[3];
+ QVector2D outerB = endJoin[4];
+
+ QVector2D p1, p2, p3, p4;
+ QVector2D n1, n2, n3, n4;
+
+ if (startInnerIsRight) {
+ n1 = startInner;
+ n2 = startOuter;
+ } else {
+ n1 = startOuter;
+ n2 = startInner;
+ }
+
+ p1 = s + n1 * penFactor;
+ p2 = s + n2 * penFactor;
+
+ // repeat logic above for the other end:
+ if (endInnerIsRight) {
+ n3 = endInner;
+ n4 = endOuter;
+ } else {
+ n3 = endOuter;
+ n4 = endInner;
+ }
+
+ p3 = e + n3 * penFactor;
+ p4 = e + n4 * penFactor;
+
+ // End caps
+
+ if (!prevElement) {
+ QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
+ if (roundCap) {
+ p1 += capSpace;
+ p2 += capSpace;
+ } else if (squareCap) {
+ QVector2D c1 = p1 + capSpace;
+ QVector2D c2 = p2 + capSpace;
+ ret.append({{p1, s, c1}, -1, {}});
+ ret.append({{c1, s, c2}, -1, {}});
+ ret.append({{p2, s, c2}, -1, {}});
+ }
+ }
+ if (!nextElement) {
+ QVector2D capSpace = tangentVector(element, true).normalized() * -penFactor;
+ if (roundCap) {
+ p3 += capSpace;
+ p4 += capSpace;
+ } else if (squareCap) {
+ QVector2D c3 = p3 + capSpace;
+ QVector2D c4 = p4 + capSpace;
+ ret.append({{p3, e, c3}, -1, {}});
+ ret.append({{c3, e, c4}, -1, {}});
+ ret.append({{p4, e, c4}, -1, {}});
+ }
+ }
+
+ if (element.isLine()) {
+ ret.append({{p1, p2, p3}, i, {n1, n2, n3}});
+ ret.append({{p2, p3, p4}, i, {n2, n3, n4}});
+ } else {
+ triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
+ }
+
+ bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
+ if (!trivialJoin && nextElement) {
+ // inside of join (opposite of bevel) is defined by
+ // triangle s, e, next.e
+ bool innerOnRight = endInnerIsRight;
+
+ const auto outer1 = e + endOuter * penFactor;
+ const auto outer2 = e + nextOuter * penFactor;
+ //const auto inner = e + endInner * penFactor;
+
+ if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
+ ret.append({{outer1, e, outer2}, -1, {}});
+ } else if (roundJoin) {
+ ret.append({{outer1, e, outer2}, i, {}});
+ QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
+ if (!innerOnRight)
+ nn = -nn;
+ ret.append({{outer1, outer1 + nn, outer2}, i, {}});
+ ret.append({{outer1 + nn, outer2, outer2 + nn}, i, {}});
+
+ } else if (miterJoin) {
+ QVector2D outer = e + outerB * penFactor;
+ ret.append({{outer1, e, outer}, -2, {}});
+ ret.append({{outer, e, outer2}, -2, {}});
+ }
+
+ if (!giveUpOnEndJoin) {
+ QVector2D inner = e + endInner * penFactor;
+ ret.append({{inner, e, outer1}, i, {endInner, {}, endOuter}});
+ // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
+ int nextIdx = addIdx(i, +1);
+ ret.append({{inner, e, outer2}, nextIdx, {endInner, {}, nextOuter}});
+ }
+ }
+ }
+ subStart = subEnd + 1;
+ }
+ return ret;
+}
+
+// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
+// elements
+static void handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
+{
+ if (!isOverlap(path, e1, e2)) {
+ return;
+ }
+
+ if (recursionLevel > 8) {
+ qCDebug(lcSGCurveProcessor) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
+ return;
+ }
+
+ if (path.elementAt(e1).childCount() > 1) {
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else if (path.elementAt(e2).childCount() > 1) {
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ handleOverlap(path, e1, e21, recursionLevel + 1);
+ handleOverlap(path, e1, e22, recursionLevel + 1);
+ } else {
+ path.splitElementAt(e1);
+ auto e11 = path.indexOfChildAt(e1, 0);
+ auto e12 = path.indexOfChildAt(e1, 1);
+ bool overlap1 = isOverlap(path, e11, e2);
+ bool overlap2 = isOverlap(path, e12, e2);
+ if (!overlap1 && !overlap2)
+ return; // no more overlap: success!
+
+ // We need to split more:
+ if (path.elementAt(e2).isLine()) {
+ // Splitting a line won't help, so we just split e1 further
+ if (overlap1)
+ handleOverlap(path, e11, e2, recursionLevel + 1);
+ if (overlap2)
+ handleOverlap(path, e12, e2, recursionLevel + 1);
+ } else {
+ // See if splitting e2 works:
+ path.splitElementAt(e2);
+ auto e21 = path.indexOfChildAt(e2, 0);
+ auto e22 = path.indexOfChildAt(e2, 1);
+ if (overlap1) {
+ handleOverlap(path, e11, e21, recursionLevel + 1);
+ handleOverlap(path, e11, e22, recursionLevel + 1);
+ }
+ if (overlap2) {
+ handleOverlap(path, e12, e21, recursionLevel + 1);
+ handleOverlap(path, e12, e22, recursionLevel + 1);
+ }
+ }
+ }
+}
+
+// Test if element contains a start point of another element
+static void handleOverlap(QQuadPath &path, int e1, const QVector2D vertex, int recursionLevel = 0)
+{
+ // First of all: Ignore the next element: it trivially overlaps (maybe not necessary: we do check for strict containment)
+ if (vertex == path.elementAt(e1).endPoint() || !isOverlap(path, e1, vertex))
+ return;
+ if (recursionLevel > 8) {
+ qDebug() << "Vertex overlap: recursion level" << recursionLevel << "aborting!";
+ return;
+ }
+
+ // Don't split if we're already split
+ if (path.elementAt(e1).childCount() == 0)
+ path.splitElementAt(e1);
+
+ handleOverlap(path, path.indexOfChildAt(e1, 0), vertex, recursionLevel + 1);
+ handleOverlap(path, path.indexOfChildAt(e1, 1), vertex, recursionLevel + 1);
+}
+
+}
+
+void QSGCurveProcessor::solveOverlaps(QQuadPath &path)
+{
+ for (int i = 0; i < path.elementCount(); i++) {
+ auto &element = path.elementAt(i);
+ // only concave curve overlap is problematic, as long as we don't allow self-intersecting curves
+ if (element.isLine() || element.isConvex())
+ continue;
+
+ for (int j = 0; j < path.elementCount(); j++) {
+ if (i == j)
+ continue; // Would be silly to test overlap with self
+ auto &other = path.elementAt(j);
+ if (!other.isConvex() && !other.isLine() && j < i)
+ continue; // We have already tested this combination, so no need to test again
+ handleOverlap(path, i, j);
+ }
+ }
+
+ static const int handleConcaveJoint = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_CONCAVE_JOINT");
+ if (handleConcaveJoint) {
+ // Note that the joint between two non-concave elements can also be concave, so we have to
+ // test all convex elements to see if there is a vertex in any of them. We could do it the other way
+ // by identifying concave joints, but then we would have to know which side is the inside
+ // TODO: optimization potential! Maybe do that at the same time as we identify concave curves?
+
+ // We do this in a separate loop, since the triangle/triangle test above is more expensive, and
+ // if we did this first, there would be more triangles to test
+ for (int i = 0; i < path.elementCount(); i++) {
+ auto &element = path.elementAt(i);
+ if (!element.isConvex())
+ continue;
+
+ for (int j = 0; j < path.elementCount(); j++) {
+ // We only need to check one point per element, since all subpaths are closed
+ // Could do smartness to skip elements that cannot overlap, but let's do it the easy way first
+ if (i == j)
+ continue;
+ const auto &other = path.elementAt(j);
+ handleOverlap(path, i, other.startPoint());
+ }
+ }
+ }
+}
+
+void QSGCurveProcessor::processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions)
+{
+ auto thePath = subdivide(strokePath, subdivisions).flattened(); // TODO: don't flatten, but handle it in the triangulator
+ auto triangles = customTriangulator2(thePath, penWidth, joinStyle, capStyle, miterLimit);
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element, const TriangleData &t){
+ addTriangle(t.points,
+ { element.startPoint(), element.controlPoint(), element.endPoint() },
+ t.normals,
+ element.isLine());
+ };
+
+ auto addBevelTriangle = [&](const TrianglePoints &p)
+ {
+ QVector2D fp1 = p[0];
+ QVector2D fp2 = p[2];
+
+ // That describes a path that passes through those points. We want the stroke
+ // edge, so we need to shift everything down by the stroke offset
+
+ QVector2D nn = calcNormalVector(p[0], p[2]);
+ if (determinant(p) < 0)
+ nn = -nn;
+ float delta = penWidth / 2;
+
+ QVector2D offset = nn.normalized() * delta;
+ fp1 += offset;
+ fp2 += offset;
+
+ TrianglePoints n;
+ // p1 is inside, so n[1] is {0,0}
+ n[0] = (p[0] - p[1]).normalized();
+ n[2] = (p[2] - p[1]).normalized();
+
+ addTriangle(p, { fp1, QVector2D(0.0f, 0.0f), fp2 }, n, true);
+ };
+
+ for (const auto &triangle : triangles) {
+ if (triangle.pathElementIndex < 0) {
+ addBevelTriangle(triangle.points);
+ continue;
+ }
+ const auto &element = thePath.elementAt(triangle.pathElementIndex);
+ addCurveTriangle(element, triangle);
+ }
+}
+
+void QSGCurveProcessor::processFill(const QQuadPath &fillPath,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle)
+{
+ QPainterPath internalHull;
+ internalHull.setFillRule(fillRule);
+
+ QHash<QPair<float, float>, int> linePointHash;
+ QHash<QPair<float, float>, int> concaveControlPointHash;
+ QHash<QPair<float, float>, int> convexPointHash;
+
+ auto toRoundedPair = [](const QPointF &p) -> QPair<float, float> {
+ return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
+ };
+
+ auto toRoundedVec2D = [](const QPointF &p) -> QVector2D {
+ return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
+ };
+
+ auto roundVec2D = [](const QVector2D &p) -> QVector2D {
+ return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
+ };
+
+ auto addCurveTriangle = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangle({ sp, cp, ep },
+ {},
+ [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto addCurveTriangleWithNormals = [&](const QQuadPath::Element &element,
+ const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n) {
+ addTriangle(v, n, [&element](QVector2D v) { return elementUvForPoint(element, v); });
+ };
+
+ auto outsideNormal = [](const QVector2D &startPoint,
+ const QVector2D &endPoint,
+ const QVector2D &insidePoint) {
+
+ QVector2D baseLine = endPoint - startPoint;
+ QVector2D insideVector = insidePoint - startPoint;
+ QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
+
+ bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
+
+ return swap ? normal : -normal;
+ };
+
+ auto addTriangleForLine = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+
+ // Add triangles on the outer side to make room for AA
+ const QVector2D normal = outsideNormal(sp, ep, cp);
+ constexpr QVector2D null;
+ addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
+ addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
+ };
+
+ auto addTriangleForConcave = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addTriangleForLine(element, sp, ep, cp);
+ };
+
+ auto addTriangleForConvex = [&](const QQuadPath::Element &element,
+ const QVector2D &sp,
+ const QVector2D &ep,
+ const QVector2D &cp) {
+ addCurveTriangle(element, sp, ep, cp);
+ // Add two triangles on the outer side to get some more AA
+
+ constexpr QVector2D null;
+ // First triangle on the line sp-cp, replacing ep
+ {
+ const QVector2D normal = outsideNormal(sp, cp, ep);
+ addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
+ }
+
+ // Second triangle on the line ep-cp, replacing sp
+ {
+ const QVector2D normal = outsideNormal(ep, cp, sp);
+ addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
+ }
+ };
+
+ auto addFillTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3) {
+ constexpr QVector3D uv(0.0, 1.0, -1.0);
+ addTriangle({ p1, p2, p3 },
+ {},
+ [&uv](QVector2D) { return uv; });
+ };
+
+ for (int i = 0; i < fillPath.elementCount(); ++i) {
+ iteratePath(fillPath, i, [&](const QQuadPath::Element &element, int index) {
+ QPointF sp(element.startPoint().toPointF()); //### to much conversion to and from pointF
+ QPointF cp(element.controlPoint().toPointF());
+ QPointF ep(element.endPoint().toPointF());
+ if (element.isSubpathStart())
+ internalHull.moveTo(sp);
+ if (element.isLine()) {
+ internalHull.lineTo(ep);
+ linePointHash.insert(toRoundedPair(sp), index);
+ } else {
+ if (element.isConvex()) {
+ internalHull.lineTo(ep);
+ addTriangleForConvex(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
+ convexPointHash.insert(toRoundedPair(sp), index);
+ } else {
+ internalHull.lineTo(cp);
+ internalHull.lineTo(ep);
+ addTriangleForConcave(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
+ concaveControlPointHash.insert(toRoundedPair(cp), index);
+ }
+ }
+ });
+ }
+
+ auto makeHashable = [](const QVector2D &p) -> QPair<float, float> {
+ return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
+ };
+ // Points in p are already rounded do 1/32
+ // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
+ // (Does not handle ambiguous vertices that are on multiple unrelated lines/curves)
+ auto onSameSideOfLine = [](const QVector2D &p1,
+ const QVector2D &p2,
+ const QVector2D &linePoint,
+ const QVector2D &lineNormal) {
+ float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
+ float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
+ return side1 * side2 >= 0;
+ };
+
+ auto pointInSafeSpace = [&](const QVector2D &p, const QQuadPath::Element &element) -> bool {
+ const QVector2D a = element.startPoint();
+ const QVector2D b = element.endPoint();
+ const QVector2D c = element.controlPoint();
+ // There are "safe" areas of the curve also across the baseline: the curve can never cross:
+ // line1: the line through A and B'
+ // line2: the line through B and A'
+ // Where A' = A "mirrored" through C and B' = B "mirrored" through C
+ const QVector2D n1 = calcNormalVector(a, c + (c - b));
+ const QVector2D n2 = calcNormalVector(b, c + (c - a));
+ bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
+ bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
+ return safeSideOf1 && safeSideOf2;
+ };
+
+ auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
+ int lineElementIndex = -1;
+ int concaveElementIndex = -1;
+ int convexElementIndex = -1;
+
+ bool foundElement = false;
+ int si = -1;
+ int ei = -1;
+ for (int i = 0; i < 3; ++i) {
+ if (auto found = linePointHash.constFind(makeHashable(p[i])); found != linePointHash.constEnd()) {
+ // check if this triangle is on a line, i.e. if one point is the sp and another is the ep of the same path element
+ const auto &element = fillPath.elementAt(*found);
+ for (int j = 0; j < 3; ++j) {
+ if (i != j && roundVec2D(element.endPoint()) == p[j]) {
+ if (foundElement)
+ return false; // More than one edge on path: must split
+ lineElementIndex = *found;
+ si = i;
+ ei = j;
+ foundElement = true;
+ }
+ }
+ } else if (auto found = concaveControlPointHash.constFind(makeHashable(p[i])); found != concaveControlPointHash.constEnd()) {
+ // check if this triangle is on the tangent line of a concave curve,
+ // i.e if one point is the cp, and the other is sp or ep
+ // TODO: clean up duplicated code (almost the same as the lineElement path above)
+ const auto &element = fillPath.elementAt(*found);
+ for (int j = 0; j < 3; ++j) {
+ if (i == j)
+ continue;
+ if (roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j]) {
+ if (foundElement)
+ return false; // More than one edge on path: must split
+ concaveElementIndex = *found;
+ // The tangent line is p[i] - p[j]
+ si = i;
+ ei = j;
+ foundElement = true;
+ }
+ }
+ } else if (auto found = convexPointHash.constFind(makeHashable(p[i])); found != convexPointHash.constEnd()) {
+ // check if this triangle is on a curve, i.e. if one point is the sp and another is the ep of the same path element
+ const auto &element = fillPath.elementAt(*found);
+ for (int j = 0; j < 3; ++j) {
+ if (i != j && roundVec2D(element.endPoint()) == p[j]) {
+ if (foundElement)
+ return false; // More than one edge on path: must split
+ convexElementIndex = *found;
+ si = i;
+ ei = j;
+ foundElement = true;
+ }
+ }
+ }
+ }
+ if (lineElementIndex != -1) {
+ int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
+ addTriangleForLine(fillPath.elementAt(lineElementIndex), p[si], p[ei], p[ci]);
+ } else if (concaveElementIndex != -1) {
+ addCurveTriangle(fillPath.elementAt(concaveElementIndex), p[0], p[1], p[2]);
+ } else if (convexElementIndex != -1) {
+ int oi = (6 - si - ei) % 3;
+ const auto &otherPoint = p[oi];
+ const auto &element = fillPath.elementAt(convexElementIndex);
+ // We have to test whether the triangle can cross the line
+ // TODO: use the toplevel element's safe space
+ bool safeSpace = pointInSafeSpace(otherPoint, element);
+ if (safeSpace) {
+ addCurveTriangle(element, p[0], p[1], p[2]);
+ } else {
+ // Find a point inside the triangle that's also in the safe space
+ QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
+ // We should calculate the point directly, but just do a lazy implementation for now:
+ for (int i = 0; i < 7; ++i) {
+ safeSpace = pointInSafeSpace(newPoint, element);
+ if (safeSpace)
+ break;
+ newPoint = (p[si] + p[ei] + newPoint) / 3;
+ }
+ if (safeSpace) {
+ // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
+ // Curve triangle is (sp, ep, np)
+ addCurveTriangle(element, p[si], p[ei], newPoint);
+ // The other two are (sp, op, np) and (ep, op, np)
+ addFillTriangle(p[si], p[oi], newPoint);
+ addFillTriangle(p[ei], p[oi], newPoint);
+ } else {
+ // fallback to fill if we can't find a point in safe space
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ }
+
+ } else {
+ addFillTriangle(p[0], p[1], p[2]);
+ }
+ return true;
+ };
+
+ QTriangleSet triangles = qTriangulate(internalHull);
+
+ const quint32 *idxTable = static_cast<const quint32 *>(triangles.indices.data());
+ for (int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
+ const quint32 *idx = &idxTable[triangle * 3];
+
+ QVector2D p[3];
+ for (int i = 0; i < 3; ++i) {
+ p[i] = toRoundedVec2D(QPointF(triangles.vertices.at(idx[i] * 2),
+ triangles.vertices.at(idx[i] * 2 + 1)));
+ }
+ if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
+ continue; // Skip degenerate triangles
+ bool needsSplit = !handleTriangle(p);
+ if (needsSplit) {
+ QVector2D c = (p[0] + p[1] + p[2]) / 3;
+ for (int i = 0; i < 3; ++i) {
+ qSwap(c, p[i]);
+ handleTriangle(p);
+ qSwap(c, p[i]);
+ }
+ }
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/qsgcurveprocessor_p.h b/src/quick/scenegraph/qsgcurveprocessor_p.h
new file mode 100644
index 0000000000..271f15207c
--- /dev/null
+++ b/src/quick/scenegraph/qsgcurveprocessor_p.h
@@ -0,0 +1,51 @@
+// 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 QSGCURVEPROCESSOR_P_H
+#define QSGCURVEPROCESSOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qtquickexports_p.h>
+#include "util/qquadpath_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QSGCurveProcessor
+{
+public:
+ typedef std::function<QVector3D(QVector2D)> uvForPointCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ uvForPointCallback)> addTriangleCallback;
+ typedef std::function<void(const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ const std::array<QVector2D, 3> &,
+ bool)> addStrokeTriangleCallback;
+
+
+ static void processFill(const QQuadPath &path,
+ Qt::FillRule fillRule,
+ addTriangleCallback addTriangle);
+ static void processStroke(const QQuadPath &strokePath,
+ float miterLimit,
+ float penWidth,
+ Qt::PenJoinStyle joinStyle,
+ Qt::PenCapStyle capStyle,
+ addStrokeTriangleCallback addTriangle,
+ int subdivisions = 3);
+ static void solveOverlaps(QQuadPath &path);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGCURVEPROCESSOR_P_H
diff --git a/src/quickshapes/qquickshapestrokenode.cpp b/src/quick/scenegraph/qsgcurvestrokenode.cpp
index 2b07cd5bca..2bfaff1b4e 100644
--- a/src/quickshapes/qquickshapestrokenode.cpp
+++ b/src/quick/scenegraph/qsgcurvestrokenode.cpp
@@ -1,27 +1,27 @@
// 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
-#include "qquickshapestrokenode_p.h"
-#include "qquickshapestrokenode_p_p.h"
+#include "qsgcurvestrokenode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
QT_BEGIN_NAMESPACE
-QQuickShapeStrokeNode::QQuickShapeStrokeNode()
+QSGCurveStrokeNode::QSGCurveStrokeNode()
{
setFlag(OwnsGeometry, true);
setGeometry(new QSGGeometry(attributes(), 0, 0));
updateMaterial();
}
-void QQuickShapeStrokeNode::QQuickShapeStrokeNode::updateMaterial()
+void QSGCurveStrokeNode::QSGCurveStrokeNode::updateMaterial()
{
- m_material.reset(new QQuickShapeStrokeMaterial(this));
+ m_material.reset(new QSGCurveStrokeMaterial(this));
setMaterial(m_material.data());
}
// Take the start, control and end point of a curve and return the points A, B, C
// representing the curve as Q(s) = A*s*s + B*s + C
-std::array<QVector2D, 3> QQuickShapeStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
+std::array<QVector2D, 3> QSGCurveStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
{
QVector2D a = p[0] - 2*p[1] + p[2];
QVector2D b = 2*p[1] - 2*p[0];
@@ -31,7 +31,7 @@ std::array<QVector2D, 3> QQuickShapeStrokeNode::curveABC(const std::array<QVecto
}
// Curve from p[0] to p[2] with control point p[1]
-void QQuickShapeStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
const std::array<QVector2D, 3> &p,
const std::array<QVector2D, 3> &n)
{
@@ -48,7 +48,7 @@ void QQuickShapeStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
}
// Straight line from p0 to p1
-void QQuickShapeStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
+void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
const std::array<QVector2D, 2> &p,
const std::array<QVector2D, 3> &n)
{
@@ -71,7 +71,7 @@ void QQuickShapeStrokeNode::appendTriangle(const std::array<QVector2D, 3> &v,
m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
}
-void QQuickShapeStrokeNode::cookGeometry()
+void QSGCurveStrokeNode::cookGeometry()
{
QSGGeometry *g = geometry();
if (g->indexType() != QSGGeometry::UnsignedIntType) {
@@ -96,7 +96,7 @@ void QQuickShapeStrokeNode::cookGeometry()
m_uncookedVertexes.clear();
}
-const QSGGeometry::AttributeSet &QQuickShapeStrokeNode::attributes()
+const QSGGeometry::AttributeSet &QSGCurveStrokeNode::attributes()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), //vertexCoord
diff --git a/src/quickshapes/qquickshapestrokenode_p.cpp b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
index d5eb901c66..c53a2c054b 100644
--- a/src/quickshapes/qquickshapestrokenode_p.cpp
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.cpp
@@ -1,24 +1,30 @@
// 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
-#include "qquickshapestrokenode_p_p.h"
-#include "qquickshapestrokenode_p.h"
-
-#include "qquickshapegenericrenderer_p.h"
+#include "qsgcurvestrokenode_p_p.h"
+#include "qsgcurvestrokenode_p.h"
QT_BEGIN_NAMESPACE
-bool QQuickShapeStrokeMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
+bool QSGCurveStrokeMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
bool changed = false;
QByteArray *buf = state.uniformData();
Q_ASSERT(buf->size() >= 64);
+ auto *newMaterial = static_cast<QSGCurveStrokeMaterial *>(newEffect);
+ auto *oldMaterial = static_cast<QSGCurveStrokeMaterial *>(oldEffect);
+
+ auto *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
+ auto *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
+
if (state.isMatrixDirty()) {
- const QMatrix4x4 m = state.combinedMatrix();
+ QMatrix4x4 m = state.combinedMatrix();
+ float localScale = newNode != nullptr ? newNode->localScale() : 1.0f;
+ m.scale(localScale);
memcpy(buf->data(), m.constData(), 64);
- float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio();
+ float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio() * localScale;
memcpy(buf->data()+64, &matrixScale, 4);
changed = true;
}
@@ -30,13 +36,6 @@ bool QQuickShapeStrokeMaterialShader::updateUniformData(RenderState &state, QSGM
}
int offset = 64+16;
-
- auto *newMaterial = static_cast<QQuickShapeStrokeMaterial *>(newEffect);
- auto *oldMaterial = static_cast<QQuickShapeStrokeMaterial *>(oldEffect);
-
- auto *newNode = newMaterial != nullptr ? newMaterial->node() : nullptr;
- auto *oldNode = oldMaterial != nullptr ? oldMaterial->node() : nullptr;
-
if (newNode == nullptr)
return changed;
@@ -73,11 +72,11 @@ bool QQuickShapeStrokeMaterialShader::updateUniformData(RenderState &state, QSGM
return changed;
}
-int QQuickShapeStrokeMaterial::compare(const QSGMaterial *other) const
+int QSGCurveStrokeMaterial::compare(const QSGMaterial *other) const
{
int typeDif = type() - other->type();
if (!typeDif) {
- auto *othernode = static_cast<const QQuickShapeStrokeMaterial*>(other)->node();
+ auto *othernode = static_cast<const QSGCurveStrokeMaterial*>(other)->node();
if (node()->color() != othernode->color())
return node()->color().rgb() < othernode->color().rgb() ? -1 : 1;
if (node()->strokeWidth() != othernode->strokeWidth())
diff --git a/src/quickshapes/qquickshapestrokenode_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p.h
index 5c2713f052..d765d2258c 100644
--- a/src/quickshapes/qquickshapestrokenode_p.h
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p.h
@@ -1,14 +1,14 @@
// 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 QQUICKSHAPESTROKENODE_P_H
-#define QQUICKSHAPESTROKENODE_P_H
+#ifndef QSGCURVESTROKENODE_P_H
+#define QSGCURVESTROKENODE_P_H
+#include <QtQuick/private/qtquickexports_p.h>
#include <QtQuick/qsgnode.h>
-#include "qquickshapeabstractcurvenode_p.h"
-#include "qquickshapegenericrenderer_p.h"
-#include "qquickshapestrokenode_p_p.h"
+#include "qsgcurveabstractnode_p.h"
+#include "qsgcurvestrokenode_p_p.h"
//
// W A R N I N G
@@ -23,10 +23,10 @@
QT_BEGIN_NAMESPACE
-class QQuickShapeStrokeNode : public QQuickShapeAbstractCurveNode
+class Q_QUICK_PRIVATE_EXPORT QSGCurveStrokeNode : public QSGCurveAbstractNode
{
public:
- QQuickShapeStrokeNode();
+ QSGCurveStrokeNode();
void setColor(QColor col) override
{
@@ -55,7 +55,7 @@ public:
const std::array<QVector2D, 2> &p, // line points
const std::array<QVector2D, 3> &n); // vertex normals
- void cookGeometry();
+ void cookGeometry() override;
static const QSGGeometry::AttributeSet &attributes();
@@ -74,6 +74,16 @@ public:
m_debug = newDebug;
}
+ void setLocalScale(float scale)
+ {
+ m_localScale = scale;
+ }
+
+ float localScale() const
+ {
+ return m_localScale;
+ }
+
private:
struct StrokeVertex
@@ -92,9 +102,10 @@ private:
QColor m_color;
float m_strokeWidth = 0.0f;
float m_debug = 0.0f;
+ float m_localScale = 1.0f;
protected:
- QScopedPointer<QQuickShapeStrokeMaterial> m_material;
+ QScopedPointer<QSGCurveStrokeMaterial> m_material;
QVector<StrokeVertex> m_uncookedVertexes;
QVector<quint32> m_uncookedIndexes;
@@ -102,4 +113,4 @@ protected:
QT_END_NAMESPACE
-#endif // QQUICKSHAPESTROKENODE_P_H
+#endif // QSGCURVESTROKENODE_P_H
diff --git a/src/quickshapes/qquickshapestrokenode_p_p.h b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
index 97fcad3cd8..9597a6eb48 100644
--- a/src/quickshapes/qquickshapestrokenode_p_p.h
+++ b/src/quick/scenegraph/qsgcurvestrokenode_p_p.h
@@ -1,9 +1,10 @@
// 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 QQUICKSHAPESTROKENODE_P_P_H
-#define QQUICKSHAPESTROKENODE_P_P_H
+#ifndef QSGCURVESTROKENODE_P_P_H
+#define QSGCURVESTROKENODE_P_P_H
+#include <QtQuick/private/qtquickexports_p.h>
#include <QtQuick/qsgmaterial.h>
//
@@ -19,28 +20,28 @@
QT_BEGIN_NAMESPACE
-class QQuickShapeStrokeNode;
-class QQuickShapeStrokeMaterial;
+class QSGCurveStrokeNode;
+class QSGCurveStrokeMaterial;
-class QQuickShapeStrokeMaterialShader : public QSGMaterialShader
+class Q_QUICK_PRIVATE_EXPORT QSGCurveStrokeMaterialShader : public QSGMaterialShader
{
public:
- QQuickShapeStrokeMaterialShader()
+ QSGCurveStrokeMaterialShader()
{
setShaderFileName(VertexStage,
- QStringLiteral(":/qt-project.org/shapes/shaders_ng/shapestroke.vert.qsb"));
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.vert.qsb"));
setShaderFileName(FragmentStage,
- QStringLiteral(":/qt-project.org/shapes/shaders_ng/shapestroke.frag.qsb"));
+ QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/shapestroke.frag.qsb"));
}
bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
};
-class QQuickShapeStrokeMaterial : public QSGMaterial
+class Q_QUICK_PRIVATE_EXPORT QSGCurveStrokeMaterial : public QSGMaterial
{
public:
- QQuickShapeStrokeMaterial(QQuickShapeStrokeNode *node)
+ QSGCurveStrokeMaterial(QSGCurveStrokeNode *node)
: m_node(node)
{
setFlag(Blending, true);
@@ -48,7 +49,7 @@ public:
int compare(const QSGMaterial *other) const override;
- QQuickShapeStrokeNode *node() const
+ QSGCurveStrokeNode *node() const
{
return m_node;
}
@@ -61,12 +62,12 @@ protected:
}
QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
{
- return new QQuickShapeStrokeMaterialShader;
+ return new QSGCurveStrokeMaterialShader;
}
- QQuickShapeStrokeNode *m_node;
+ QSGCurveStrokeNode *m_node;
};
QT_END_NAMESPACE
-#endif // QQUICKSHAPESTROKENODE_P_P_H
+#endif // QSGCURVESTROKENODE_P_P_H
diff --git a/src/quick/scenegraph/qsgdefaultcontext.cpp b/src/quick/scenegraph/qsgdefaultcontext.cpp
index f37e0661b7..379debfe82 100644
--- a/src/quick/scenegraph/qsgdefaultcontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultcontext.cpp
@@ -7,6 +7,7 @@
#include <QtQuick/private/qsgdefaultinternalimagenode_p.h>
#include <QtQuick/private/qsgdefaultpainternode_p.h>
#include <QtQuick/private/qsgdefaultglyphnode_p.h>
+#include <QtQuick/private/qsgcurveglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p.h>
#include <QtQuick/private/qsgdistancefieldglyphnode_p_p.h>
#include <QtQuick/private/qsgrhisupport_p.h>
@@ -146,10 +147,12 @@ QSGPainterNode *QSGDefaultContext::createPainterNode(QQuickPaintedItem *item)
}
QSGGlyphNode *QSGDefaultContext::createGlyphNode(QSGRenderContext *rc,
- bool preferNativeGlyphNode,
+ QSGTextNode::RenderType renderType,
int renderTypeQuality)
{
- if (m_distanceFieldDisabled || preferNativeGlyphNode) {
+ if (renderType == QSGTextNode::CurveRendering) {
+ return new QSGCurveGlyphNode(rc);
+ } else if (m_distanceFieldDisabled || renderType == QSGTextNode::NativeRendering) {
return new QSGDefaultGlyphNode(rc);
} else {
QSGDistanceFieldGlyphNode *node = new QSGDistanceFieldGlyphNode(rc);
diff --git a/src/quick/scenegraph/qsgdefaultcontext_p.h b/src/quick/scenegraph/qsgdefaultcontext_p.h
index 8dd507bd9a..9250224f50 100644
--- a/src/quick/scenegraph/qsgdefaultcontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultcontext_p.h
@@ -33,7 +33,7 @@ public:
QSGInternalRectangleNode *createInternalRectangleNode() override;
QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override;
QSGPainterNode *createPainterNode(QQuickPaintedItem *item) override;
- QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, bool preferNativeGlyphNode, int renderTypeQuality) override;
+ QSGGlyphNode *createGlyphNode(QSGRenderContext *rc, QSGTextNode::RenderType renderType, int renderTypeQuality) override;
QSGLayer *createLayer(QSGRenderContext *renderContext) override;
QSurfaceFormat defaultSurfaceFormat() const override;
QSGRendererInterface *rendererInterface(QSGRenderContext *renderContext) override;
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
index 73f4bec27f..dc3262f2da 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp
+++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qsgdefaultrendercontext_p.h"
+#include "qsgcurveglyphatlas_p.h"
#include <QtGui/QGuiApplication>
@@ -71,6 +72,9 @@ void QSGDefaultRenderContext::invalidateGlyphCaches()
}
}
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
{
auto it = m_fontEnginesToClean.begin();
while (it != m_fontEnginesToClean.end()) {
@@ -131,6 +135,9 @@ void QSGDefaultRenderContext::invalidate()
}
m_fontEnginesToClean.clear();
+ qDeleteAll(m_curveGlyphAtlases);
+ m_curveGlyphAtlases.clear();
+
qDeleteAll(m_glyphCaches);
m_glyphCaches.clear();
@@ -254,6 +261,18 @@ void QSGDefaultRenderContext::preprocess()
}
}
+QSGCurveGlyphAtlas *QSGDefaultRenderContext::curveGlyphAtlas(const QRawFont &font)
+{
+ QString key = fontKey(font, 0);
+ QSGCurveGlyphAtlas *atlas = m_curveGlyphAtlases.value(key, nullptr);
+ if (atlas == nullptr) {
+ atlas = new QSGCurveGlyphAtlas(font);
+ m_curveGlyphAtlases.insert(key, atlas);
+ }
+
+ return atlas;
+}
+
QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality)
{
QString key = fontKey(font, renderTypeQuality);
diff --git a/src/quick/scenegraph/qsgdefaultrendercontext_p.h b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
index 06c6adfc64..1743ea35b7 100644
--- a/src/quick/scenegraph/qsgdefaultrendercontext_p.h
+++ b/src/quick/scenegraph/qsgdefaultrendercontext_p.h
@@ -70,6 +70,7 @@ public:
void preprocess() override;
void invalidateGlyphCaches() override;
QSGDistanceFieldGlyphCache *distanceFieldGlyphCache(const QRawFont &font, int renderTypeQuality) override;
+ QSGCurveGlyphAtlas *curveGlyphAtlas(const QRawFont &font) override;
QSGTexture *createTexture(const QImage &image, uint flags) const override;
QSGRenderer *createRenderer(QSGRendererInterface::RenderMode renderMode = QSGRendererInterface::RenderMode2D) override;
@@ -119,6 +120,7 @@ protected:
bool m_useDepthBufferFor2D;
QRhiResourceUpdateBatch *m_glyphCacheResourceUpdates;
QSet<QRhiTexture *> m_pendingGlyphCacheTextures;
+ QHash<QString, QSGCurveGlyphAtlas *> m_curveGlyphAtlases;
};
QT_END_NAMESPACE
diff --git a/src/quickshapes/shaders_ng/shapecurve.frag b/src/quick/scenegraph/shaders_ng/shapecurve.frag
index 2badca31a7..2badca31a7 100644
--- a/src/quickshapes/shaders_ng/shapecurve.frag
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.frag
diff --git a/src/quickshapes/shaders_ng/shapecurve.vert b/src/quick/scenegraph/shaders_ng/shapecurve.vert
index 64e32b5517..64e32b5517 100644
--- a/src/quickshapes/shaders_ng/shapecurve.vert
+++ b/src/quick/scenegraph/shaders_ng/shapecurve.vert
diff --git a/src/quickshapes/shaders_ng/shapestroke.frag b/src/quick/scenegraph/shaders_ng/shapestroke.frag
index 846500c44c..84435ba6f9 100644
--- a/src/quickshapes/shaders_ng/shapestroke.frag
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.frag
@@ -1,6 +1,3 @@
-// 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 vec4 P;
@@ -130,5 +127,4 @@ void main()
fragColor = vec4(ubuf.strokeColor.rgb, 1.0) *ubuf.strokeColor.a * fillCoverage * ubuf.opacity
+ ubuf.debug * vec4(0.0, 0.5, 1.0, 1.0) * (1.0 - fillCoverage) * ubuf.opacity;
-
}
diff --git a/src/quickshapes/shaders_ng/shapestroke.vert b/src/quick/scenegraph/shaders_ng/shapestroke.vert
index 9b22eb4eeb..9b22eb4eeb 100644
--- a/src/quickshapes/shaders_ng/shapestroke.vert
+++ b/src/quick/scenegraph/shaders_ng/shapestroke.vert
diff --git a/src/quickshapes/qquadpath.cpp b/src/quick/scenegraph/util/qquadpath.cpp
index 7c3be8469d..c78c650db9 100644
--- a/src/quickshapes/qquadpath.cpp
+++ b/src/quick/scenegraph/util/qquadpath.cpp
@@ -2,13 +2,15 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquadpath_p.h"
-#include "qquickshapecurverenderer_p_p.h"
#include <QtGui/private/qbezier_p.h>
#include <QtMath>
+#include <QtCore/QLoggingCategory>
#include <QtCore/QVarLengthArray>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcSGCurveProcessor);
+
static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
{
static bool init = false;
@@ -755,7 +757,7 @@ void QQuadPath::splitElementAt(int index)
#ifndef QT_NO_DEBUG
if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
- qCDebug(lcShapeCurveRenderer) << "Splitting has resulted in ~null quad";
+ qCDebug(lcSGCurveProcessor) << "Splitting has resulted in ~null quad";
#endif
}
diff --git a/src/quickshapes/qquadpath_p.h b/src/quick/scenegraph/util/qquadpath_p.h
index e6b038c09c..74589e3ab9 100644
--- a/src/quickshapes/qquadpath_p.h
+++ b/src/quick/scenegraph/util/qquadpath_p.h
@@ -20,10 +20,11 @@
#include <QtCore/qdebug.h>
#include <QtGui/qvector2d.h>
#include <QtGui/qpainterpath.h>
+#include <QtQuick/private/qtquickexports_p.h>
QT_BEGIN_NAMESPACE
-class QQuadPath
+class Q_QUICK_PRIVATE_EXPORT QQuadPath
{
public:
class Element
diff --git a/src/quick/scenegraph/util/qsggradientcache.cpp b/src/quick/scenegraph/util/qsggradientcache.cpp
new file mode 100644
index 0000000000..ae88cd2e3e
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache.cpp
@@ -0,0 +1,121 @@
+// 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
+
+#include "qsggradientcache_p.h"
+
+#include <QtGui/private/qdrawhelper_p.h>
+#include <QtGui/rhi/qrhi.h>
+
+#include <QtQuick/qsgtexture.h>
+#include <QtQuick/private/qsgplaintexture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+static void generateGradientColorTable(const QSGGradientCacheKey &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;
+}
+
+QSGGradientCache::~QSGGradientCache()
+{
+ qDeleteAll(m_textures);
+}
+
+QSGGradientCache *QSGGradientCache::cacheForRhi(QRhi *rhi)
+{
+ static QHash<QRhi *, QSGGradientCache *> caches;
+ auto it = caches.constFind(rhi);
+ if (it != caches.constEnd())
+ return *it;
+
+ QSGGradientCache *cache = new QSGGradientCache;
+ rhi->addCleanupCallback([cache](QRhi *rhi) {
+ caches.remove(rhi);
+ delete cache;
+ });
+ caches.insert(rhi, cache);
+ return cache;
+}
+
+QSGTexture *QSGGradientCache::get(const QSGGradientCacheKey &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 QGradient::PadSpread:
+ tx->setHorizontalWrapMode(QSGTexture::ClampToEdge);
+ tx->setVerticalWrapMode(QSGTexture::ClampToEdge);
+ break;
+ case QGradient::RepeatSpread:
+ tx->setHorizontalWrapMode(QSGTexture::Repeat);
+ tx->setVerticalWrapMode(QSGTexture::Repeat);
+ break;
+ case QGradient::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
diff --git a/src/quick/scenegraph/util/qsggradientcache_p.h b/src/quick/scenegraph/util/qsggradientcache_p.h
new file mode 100644
index 0000000000..ade19fcf85
--- /dev/null
+++ b/src/quick/scenegraph/util/qsggradientcache_p.h
@@ -0,0 +1,72 @@
+// 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 QSGGRADIENTCACHE_P_H
+#define QSGGRADIENTCACHE_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 <QtCore/qhash.h>
+#include <QtGui/qbrush.h>
+
+#include <QtQuick/private/qtquickexports_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSGTexture;
+class QSGPlainTexture;
+class QRhi;
+
+struct Q_QUICK_PRIVATE_EXPORT QSGGradientCacheKey
+{
+ QSGGradientCacheKey(const QGradientStops &stops, QGradient::Spread spread)
+ : stops(stops), spread(spread)
+ { }
+ QGradientStops stops;
+ QGradient::Spread spread;
+ bool operator==(const QSGGradientCacheKey &other) const
+ {
+ return spread == other.spread && stops == other.stops;
+ }
+};
+
+inline size_t qHash(const QSGGradientCacheKey &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 Q_QUICK_PRIVATE_EXPORT QSGGradientCache
+{
+public:
+ struct GradientDesc { // can fully describe a linear/radial/conical gradient
+ QGradientStops stops;
+ QGradient::Spread spread = QGradient::PadSpread;
+ 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)
+ };
+
+ ~QSGGradientCache();
+ static QSGGradientCache *cacheForRhi(QRhi *rhi);
+ QSGTexture *get(const QSGGradientCacheKey &grad);
+
+private:
+ QHash<QSGGradientCacheKey, QSGPlainTexture *> m_textures;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSGGRADIENTCACHE_P_H
diff --git a/src/quick/scenegraph/util/qsgtextnode.cpp b/src/quick/scenegraph/util/qsgtextnode.cpp
index c04666cf0d..3de7f8a703 100644
--- a/src/quick/scenegraph/util/qsgtextnode.cpp
+++ b/src/quick/scenegraph/util/qsgtextnode.cpp
@@ -44,12 +44,20 @@ QT_BEGIN_NAMESPACE
\value QtRendering Text is rendered using a scalable distance field for each glyph.
\value NativeRendering Text is rendered using a platform-specific technique.
+ \value CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware.
Select \c NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
\sa setRenderType(), setRenderTypeQuality()
*/
diff --git a/src/quick/scenegraph/util/qsgtextnode.h b/src/quick/scenegraph/util/qsgtextnode.h
index 4df98f0af2..92f8385ce4 100644
--- a/src/quick/scenegraph/util/qsgtextnode.h
+++ b/src/quick/scenegraph/util/qsgtextnode.h
@@ -25,7 +25,8 @@ public:
enum RenderType
{
QtRendering,
- NativeRendering
+ NativeRendering,
+ CurveRendering
};
~QSGTextNode() override = default;
diff --git a/src/quickshapes/CMakeLists.txt b/src/quickshapes/CMakeLists.txt
index c1f7886c93..27bf3d4e5b 100644
--- a/src/quickshapes/CMakeLists.txt
+++ b/src/quickshapes/CMakeLists.txt
@@ -21,10 +21,6 @@ qt_internal_add_qml_module(QuickShapesPrivate
qquickshapegenericrenderer.cpp qquickshapegenericrenderer_p.h
qquickshapesglobal.h qquickshapesglobal_p.h
qquickshapecurverenderer.cpp qquickshapecurverenderer_p.h qquickshapecurverenderer_p_p.h
- qquickshapecurvenode.cpp qquickshapecurvenode_p.h qquickshapecurvenode_p_p.h qquickshapecurvenode_p.cpp
- qquickshapeabstractcurvenode_p.h
- qquickshapestrokenode.cpp qquickshapestrokenode_p.h qquickshapestrokenode_p_p.h qquickshapestrokenode_p.cpp
- qquadpath_p.h qquadpath.cpp
qquickshapesoftwarerenderer.cpp qquickshapesoftwarerenderer_p.h
PUBLIC_LIBRARIES
Qt::Core
@@ -56,132 +52,7 @@ qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders"
"shaders_ng/radialgradient.frag"
"shaders_ng/conicalgradient.vert"
"shaders_ng/conicalgradient.frag"
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- "shaders_ng/shapestroke.frag"
- "shaders_ng/shapestroke.vert"
"shaders_ng/wireframe.frag"
"shaders_ng/wireframe.vert"
)
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_derivatives"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES "USE_DERIVATIVES"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_derivatives.frag.qsb"
- "shaders_ng/shapecurve_derivatives.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_lg"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "LINEARGRADIENT"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_lg.frag.qsb"
- "shaders_ng/shapecurve_lg.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_lg_derivatives"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "LINEARGRADIENT"
- "USE_DERIVATIVES"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_lg_derivatives.frag.qsb"
- "shaders_ng/shapecurve_lg_derivatives.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_rg"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "RADIALGRADIENT"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_rg.frag.qsb"
- "shaders_ng/shapecurve_rg.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_rg_derivatives"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "RADIALGRADIENT"
- "USE_DERIVATIVES"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_rg_derivatives.frag.qsb"
- "shaders_ng/shapecurve_rg_derivatives.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_cg"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "CONICALGRADIENT"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_cg.frag.qsb"
- "shaders_ng/shapecurve_cg.vert.qsb"
-)
-
-qt_internal_add_shaders(QuickShapesPrivate "qtquickshapes_shaders_cg_derivatives"
- SILENT
- BATCHABLE
- PRECOMPILE
- OPTIMIZED
- DEFINES
- "CONICALGRADIENT"
- "USE_DERIVATIVES"
- PREFIX
- "/qt-project.org/shapes"
- FILES
- "shaders_ng/shapecurve.frag"
- "shaders_ng/shapecurve.vert"
- OUTPUTS
- "shaders_ng/shapecurve_cg_derivatives.frag.qsb"
- "shaders_ng/shapecurve_cg_derivatives.vert.qsb"
-)
-
diff --git a/src/quickshapes/qquickshape.cpp b/src/quickshapes/qquickshape.cpp
index 67029bba03..431b9b5593 100644
--- a/src/quickshapes/qquickshape.cpp
+++ b/src/quickshapes/qquickshape.cpp
@@ -1727,112 +1727,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_p.h b/src/quickshapes/qquickshape_p_p.h
index 0cdfbb19a7..2e9dd6510c 100644
--- a/src/quickshapes/qquickshape_p_p.h
+++ b/src/quickshapes/qquickshape_p_p.h
@@ -37,16 +37,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 = QQuickShapeGradient::PadSpread;
- 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() { }
@@ -164,38 +155,6 @@ public:
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
index aefee42c23..436a6713c0 100644
--- a/src/quickshapes/qquickshapecurverenderer.cpp
+++ b/src/quickshapes/qquickshapecurverenderer.cpp
@@ -3,8 +3,6 @@
#include "qquickshapecurverenderer_p.h"
#include "qquickshapecurverenderer_p_p.h"
-#include "qquickshapecurvenode_p.h"
-#include "qquickshapestrokenode_p.h"
#include <QtGui/qvector2d.h>
#include <QtGui/qvector4d.h>
@@ -12,6 +10,10 @@
#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>
#include <QThread>
@@ -76,7 +78,7 @@ protected:
};
-class QQuickShapeWireFrameNode : public QQuickShapeAbstractCurveNode
+class QQuickShapeWireFrameNode : public QSGCurveAbstractNode
{
public:
struct WireFrameVertex
@@ -112,6 +114,11 @@ public:
return attrs;
}
+ void cookGeometry() override
+ {
+ // Intentionally empty
+ }
+
protected:
QScopedPointer<QQuickShapeWireFrameMaterial> m_material;
};
@@ -209,21 +216,21 @@ void QQuickShapeCurveRenderer::setStrokeStyle(int index,
void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
{
PathData &pd(m_paths[index]);
- pd.gradientType = NoGradient;
+ pd.gradientType = QGradient::NoGradient;
if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) {
- pd.gradientType = LinearGradient;
+ pd.gradientType = QGradient::LinearGradient;
pd.gradient.stops = gradient->gradientStops();
- pd.gradient.spread = gradient->spread();
+ 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 = RadialGradient;
+ 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 = ConicalGradient;
+ pd.gradientType = QGradient::ConicalGradient;
pd.gradient.a = QPointF(g->centerX(), g->centerY());
pd.gradient.v0 = g->angle();
} else
@@ -235,9 +242,9 @@ void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *g
}
}
- if (pd.gradientType != NoGradient) {
+ if (pd.gradientType != QGradient::NoGradient) {
pd.gradient.stops = gradient->gradientStops();
- pd.gradient.spread = gradient->spread();
+ pd.gradient.spread = QGradient::Spread(gradient->spread());
}
pd.m_dirty |= FillDirty;
@@ -284,7 +291,7 @@ void QQuickShapeCurveRenderer::updateNode()
pathData.fillPath = pathData.path.subPathsClosed();
pathData.fillPath.addCurvatureData();
if (doOverlapSolving)
- solveOverlaps(pathData.fillPath);
+ QSGCurveProcessor::solveOverlaps(pathData.fillPath);
}
pathData.fillNodes = addFillNodes(pathData, &pathData.fillDebugNodes);
dirtyFlags |= StrokeDirty;
@@ -323,74 +330,10 @@ void QQuickShapeCurveRenderer::updateNode()
}
}
-namespace {
-// Input coordinate space is pre-mapped so that (0, 0) maps to [0, 0] in uv space.
-// v1 maps to [1,0], v2 maps to [0,1]. p is the point to be mapped to uv in this space (i.e. vector from p0)
-static inline QVector2D 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 {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
-static inline QVector2D curveUv(QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
-{
- QVector2D v1 = 2 * (p1 - p0);
- QVector2D v2 = p2 - v1 - p0;
- return uvForPoint(v1, v2, p - p0);
-}
-
-static QVector3D elementUvForPoint(const QQuadPath::Element& e, QVector2D p)
-{
- auto uv = curveUv(e.startPoint(), e.controlPoint(), e.endPoint(), p);
- if (e.isLine())
- return { uv.x(), uv.y(), 0.0f };
- else
- return { uv.x(), uv.y(), e.isConvex() ? -1.0f : 1.0f };
-}
-
-static inline QVector2D calcNormalVector(QVector2D a, QVector2D b)
-{
- auto v = b - a;
- return {v.y(), -v.x()};
-}
-
-// The sign of the return value indicates which side of the line defined by a and n the point p falls
-static inline float testSideOfLineByNormal(QVector2D a, QVector2D n, QVector2D p)
-{
- float dot = QVector2D::dotProduct(p - a, n);
- return dot;
-};
-
-template<typename Func>
-void iteratePath(const QQuadPath &path, int index, Func &&lambda)
-{
- const auto &element = path.elementAt(index);
- if (element.childCount() == 0) {
- lambda(element, index);
- } else {
- for (int i = 0; i < element.childCount(); ++i)
- iteratePath(path, element.indexOfChild(i), lambda);
- }
-}
-
-static inline float determinant(const QVector2D &p1, const QVector2D &p2, const QVector2D &p3)
-{
- return p1.x() * (p2.y() - p3.y())
- + p2.x() * (p3.y() - p1.y())
- + p3.x() * (p1.y() - p2.y());
-}
-}
-
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const PathData &pathData,
NodeList *debugNodes)
{
- auto *node = new QQuickShapeCurveNode;
+ auto *node = new QSGCurveFillNode;
node->setGradientType(pathData.gradientType);
NodeList ret;
@@ -401,284 +344,21 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const
bool visualizeDebug = debugVisualization() & DebugCurves;
const float dbg = visualizeDebug ? 0.5f : 0.0f;
node->setDebug(dbg);
- QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
-
- QHash<QPair<float, float>, int> linePointHash;
- QHash<QPair<float, float>, int> concaveControlPointHash;
- QHash<QPair<float, float>, int> convexPointHash;
-
- auto toRoundedPair = [](const QPointF &p) -> QPair<float, float> {
- return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
- };
-
- auto toRoundedVec2D = [](const QPointF &p) -> QVector2D {
- return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
- };
-
- auto roundVec2D = [](const QVector2D &p) -> QVector2D {
- return { qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f };
- };
-
- auto addCurveTriangle = [&](const QQuadPath::Element &element,
- const QVector2D &sp,
- const QVector2D &ep,
- const QVector2D &cp) {
- node->appendTriangle(sp, cp, ep,
- [&element](QVector2D v) { return elementUvForPoint(element, v); });
-
- wfVertices.append({sp.x(), sp.y(), 1.0f, 0.0f, 0.0f}); // 0
- wfVertices.append({cp.x(), cp.y(), 0.0f, 1.0f, 0.0f}); // 1
- wfVertices.append({ep.x(), ep.y(), 0.0f, 0.0f, 1.0f}); // 2
- };
-
- auto addCurveTriangleWithNormals = [&](const QQuadPath::Element &element,
- const std::array<QVector2D, 3> &v,
- const std::array<QVector2D, 3> &n) {
- node->appendTriangle(v, n, [&element](QVector2D v) { return elementUvForPoint(element, v); });
- wfVertices.append({v[0].x(), v[0].y(), 1.0f, 0.0f, 0.0f}); // 0
- wfVertices.append({v[1].x(), v[1].y(), 0.0f, 1.0f, 0.0f}); // 1
- wfVertices.append({v[2].x(), v[2].y(), 0.0f, 0.0f, 1.0f}); // 2
- };
-
- auto outsideNormal = [](const QVector2D &startPoint,
- const QVector2D &endPoint,
- const QVector2D &insidePoint) {
-
- QVector2D baseLine = endPoint - startPoint;
- QVector2D insideVector = insidePoint - startPoint;
- QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
-
- bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
-
- return swap ? normal : -normal;
- };
-
- auto addTriangleForLine = [&](const QQuadPath::Element &element,
- const QVector2D &sp,
- const QVector2D &ep,
- const QVector2D &cp) {
- addCurveTriangle(element, sp, ep, cp);
-
- // Add triangles on the outer side to make room for AA
- const QVector2D normal = outsideNormal(sp, ep, cp);
- constexpr QVector2D null;
- addCurveTriangleWithNormals(element, {sp, sp, ep}, {null, normal, null});
- addCurveTriangleWithNormals(element, {sp, ep, ep}, {normal, normal, null});
- };
-
- auto addTriangleForConcave = [&](const QQuadPath::Element &element,
- const QVector2D &sp,
- const QVector2D &ep,
- const QVector2D &cp) {
- addTriangleForLine(element, sp, ep, cp);
- };
-
- auto addTriangleForConvex = [&](const QQuadPath::Element &element,
- const QVector2D &sp,
- const QVector2D &ep,
- const QVector2D &cp) {
- addCurveTriangle(element, sp, ep, cp);
- // Add two triangles on the outer side to get some more AA
-
- constexpr QVector2D null;
- // First triangle on the line sp-cp, replacing ep
- {
- const QVector2D normal = outsideNormal(sp, cp, ep);
- addCurveTriangleWithNormals(element, {sp, sp, cp}, {null, normal, null});
- }
-
- // Second triangle on the line ep-cp, replacing sp
- {
- const QVector2D normal = outsideNormal(ep, cp, sp);
- addCurveTriangleWithNormals(element, {ep, ep, cp}, {null, normal, null});
- }
- };
-
- auto addFillTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3) {
- constexpr QVector3D uv(0.0, 1.0, -1.0);
- node->appendTriangle(p1, p2, p3, [&uv](QVector2D) { return uv; } );
-
- wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); // 0
- wfVertices.append({p3.x(), p3.y(), 0.0f, 1.0f, 0.0f}); // 1
- wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f}); // 2
- };
-
- for (int i = 0; i < pathData.fillPath.elementCount(); ++i) {
- iteratePath(pathData.fillPath, i, [&](const QQuadPath::Element &element, int index) {
- QPointF sp(element.startPoint().toPointF()); //### to much conversion to and from pointF
- QPointF cp(element.controlPoint().toPointF());
- QPointF ep(element.endPoint().toPointF());
- if (element.isSubpathStart())
- internalHull.moveTo(sp);
- if (element.isLine()) {
- internalHull.lineTo(ep);
- linePointHash.insert(toRoundedPair(sp), index);
- } else {
- if (element.isConvex()) {
- internalHull.lineTo(ep);
- addTriangleForConvex(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
- convexPointHash.insert(toRoundedPair(sp), index);
- } else {
- internalHull.lineTo(cp);
- internalHull.lineTo(ep);
- addTriangleForConcave(element, toRoundedVec2D(sp), toRoundedVec2D(ep), toRoundedVec2D(cp));
- concaveControlPointHash.insert(toRoundedPair(cp), index);
- }
- }
- });
- }
-
- auto makeHashable = [](const QVector2D &p) -> QPair<float, float> {
- return qMakePair(qRound(p.x() * 32.0f) / 32.0f, qRound(p.y() * 32.0f) / 32.0f);
- };
- // Points in p are already rounded do 1/32
- // Returns false if the triangle needs to be split. Adds the triangle to the graphics buffers and returns true otherwise.
- // (Does not handle ambiguous vertices that are on multiple unrelated lines/curves)
- auto onSameSideOfLine = [](const QVector2D &p1,
- const QVector2D &p2,
- const QVector2D &linePoint,
- const QVector2D &lineNormal) {
- float side1 = testSideOfLineByNormal(linePoint, lineNormal, p1);
- float side2 = testSideOfLineByNormal(linePoint, lineNormal, p2);
- return side1 * side2 >= 0;
- };
- auto pointInSafeSpace = [&](const QVector2D &p, const QQuadPath::Element &element) -> bool {
- const QVector2D a = element.startPoint();
- const QVector2D b = element.endPoint();
- const QVector2D c = element.controlPoint();
- // There are "safe" areas of the curve also across the baseline: the curve can never cross:
- // line1: the line through A and B'
- // line2: the line through B and A'
- // Where A' = A "mirrored" through C and B' = B "mirrored" through C
- const QVector2D n1 = calcNormalVector(a, c + (c - b));
- const QVector2D n2 = calcNormalVector(b, c + (c - a));
- bool safeSideOf1 = onSameSideOfLine(p, c, a, n1);
- bool safeSideOf2 = onSameSideOfLine(p, c, b, n2);
- return safeSideOf1 && safeSideOf2;
- };
-
- auto handleTriangle = [&](const QVector2D (&p)[3]) -> bool {
- int lineElementIndex = -1;
- int concaveElementIndex = -1;
- int convexElementIndex = -1;
-
- bool foundElement = false;
- int si = -1;
- int ei = -1;
- for (int i = 0; i < 3; ++i) {
- if (auto found = linePointHash.constFind(makeHashable(p[i])); found != linePointHash.constEnd()) {
- // check if this triangle is on a line, i.e. if one point is the sp and another is the ep of the same path element
- const auto &element = pathData.fillPath.elementAt(*found);
- for (int j = 0; j < 3; ++j) {
- if (i != j && roundVec2D(element.endPoint()) == p[j]) {
- if (foundElement)
- return false; // More than one edge on path: must split
- lineElementIndex = *found;
- si = i;
- ei = j;
- foundElement = true;
- }
- }
- } else if (auto found = concaveControlPointHash.constFind(makeHashable(p[i])); found != concaveControlPointHash.constEnd()) {
- // check if this triangle is on the tangent line of a concave curve,
- // i.e if one point is the cp, and the other is sp or ep
- // TODO: clean up duplicated code (almost the same as the lineElement path above)
- const auto &element = pathData.fillPath.elementAt(*found);
- for (int j = 0; j < 3; ++j) {
- if (i == j)
- continue;
- if (roundVec2D(element.startPoint()) == p[j] || roundVec2D(element.endPoint()) == p[j]) {
- if (foundElement)
- return false; // More than one edge on path: must split
- concaveElementIndex = *found;
- // The tangent line is p[i] - p[j]
- si = i;
- ei = j;
- foundElement = true;
- }
- }
- } else if (auto found = convexPointHash.constFind(makeHashable(p[i])); found != convexPointHash.constEnd()) {
- // check if this triangle is on a curve, i.e. if one point is the sp and another is the ep of the same path element
- const auto &element = pathData.fillPath.elementAt(*found);
- for (int j = 0; j < 3; ++j) {
- if (i != j && roundVec2D(element.endPoint()) == p[j]) {
- if (foundElement)
- return false; // More than one edge on path: must split
- convexElementIndex = *found;
- si = i;
- ei = j;
- foundElement = true;
- }
- }
- }
- }
- if (lineElementIndex != -1) {
- int ci = (6 - si - ei) % 3; // 1+2+3 is 6, so missing number is 6-n1-n2
- addTriangleForLine(pathData.fillPath.elementAt(lineElementIndex), p[si], p[ei], p[ci]);
- } else if (concaveElementIndex != -1) {
- addCurveTriangle(pathData.fillPath.elementAt(concaveElementIndex), p[0], p[1], p[2]);
- } else if (convexElementIndex != -1) {
- int oi = (6 - si - ei) % 3;
- const auto &otherPoint = p[oi];
- const auto &element = pathData.fillPath.elementAt(convexElementIndex);
- // We have to test whether the triangle can cross the line
- // TODO: use the toplevel element's safe space
- bool safeSpace = pointInSafeSpace(otherPoint, element);
- if (safeSpace) {
- addCurveTriangle(element, p[0], p[1], p[2]);
- } else {
- // Find a point inside the triangle that's also in the safe space
- QVector2D newPoint = (p[0] + p[1] + p[2]) / 3;
- // We should calculate the point directly, but just do a lazy implementation for now:
- for (int i = 0; i < 7; ++i) {
- safeSpace = pointInSafeSpace(newPoint, element);
- if (safeSpace)
- break;
- newPoint = (p[si] + p[ei] + newPoint) / 3;
- }
- if (safeSpace) {
- // Split triangle. We know the original triangle is only on one path element, so the other triangles are both fill.
- // Curve triangle is (sp, ep, np)
- addCurveTriangle(element, p[si], p[ei], newPoint);
- // The other two are (sp, op, np) and (ep, op, np)
- addFillTriangle(p[si], p[oi], newPoint);
- addFillTriangle(p[ei], p[oi], newPoint);
- } else {
- // fallback to fill if we can't find a point in safe space
- addFillTriangle(p[0], p[1], p[2]);
- }
- }
-
- } else {
- addFillTriangle(p[0], p[1], p[2]);
- }
- return true;
- };
-
- QTriangleSet triangles = qTriangulate(internalHull);
+ QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
- const quint32 *idxTable = static_cast<const quint32 *>(triangles.indices.data());
- for (int triangle = 0; triangle < triangles.indices.size() / 3; ++triangle) {
- const quint32 *idx = &idxTable[triangle * 3];
+ QSGCurveProcessor::processFill(pathData.fillPath,
+ pathData.fillRule,
+ [&wfVertices, &node](const std::array<QVector2D, 3> &v,
+ const std::array<QVector2D, 3> &n,
+ QSGCurveProcessor::uvForPointCallback uvForPoint)
+ {
+ node->appendTriangle(v, n, uvForPoint);
- QVector2D p[3];
- for (int i = 0; i < 3; ++i) {
- p[i] = toRoundedVec2D(QPointF(triangles.vertices.at(idx[i] * 2),
- triangles.vertices.at(idx[i] * 2 + 1)));
- }
- if (qFuzzyIsNull(determinant(p[0], p[1], p[2])))
- continue; // Skip degenerate triangles
- bool needsSplit = !handleTriangle(p);
- if (needsSplit) {
- QVector2D c = (p[0] + p[1] + p[2]) / 3;
- for (int i = 0; i < 3; ++i) {
- qSwap(c, p[i]);
- handleTriangle(p);
- qSwap(c, p[i]);
- }
- }
- }
+ 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) {
@@ -727,9 +407,28 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStr
QPen pen = pathData.pen;
stroker.process(vp, pen, {}, {});
- auto *node = new QQuickShapeCurveNode;
+ 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;
@@ -748,7 +447,7 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStr
return;
}
- auto uvForPoint = [&p1, &p2, &p3](QVector2D p) {
+ auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
auto uv = curveUv(p1, p2, p3, p);
return QVector3D(uv.x(), uv.y(), 0.0f); // Line
};
@@ -843,663 +542,44 @@ void QQuickShapeCurveRenderer::deleteAndClear(NodeList *nodeList)
nodeList->clear();
}
-namespace {
-
-/*
- Clever triangle overlap algorithm. Stack Overflow says:
-
- You can prove that the two triangles do not collide by finding an edge (out of the total 6
- edges that make up the two triangles) that acts as a separating line where all the vertices
- of one triangle lie on one side and the vertices of the other triangle lie on the other side.
- If you can find such an edge then it means that the triangles do not intersect otherwise the
- triangles are colliding.
-*/
-using TrianglePoints = std::array<QVector2D, 3>;
-using LinePoints = std::array<QVector2D, 2>;
-
-// The sign of the determinant tells the winding order: positive means counter-clockwise
-
-static inline double determinant(const TrianglePoints &p)
-{
- return determinant(p[0], p[1], p[2]);
-}
-
-// Fix the triangle so that the determinant is positive
-static void fixWinding(TrianglePoints &p)
-{
- double det = determinant(p);
- if (det < 0.0) {
- qSwap(p[0], p[1]);
- }
-}
-
-// Return true if the determinant is negative, i.e. if the winding order is opposite of the triangle p1,p2,p3.
-// This means that p is strictly on the other side of p1-p2 relative to p3 [where p1,p2,p3 is a triangle with
-// a positive determinant].
-bool checkEdge(QVector2D &p1, QVector2D &p2, QVector2D &p, float epsilon)
-{
- return determinant(p1, p2, p) <= epsilon;
-}
-
-
-bool checkTriangleOverlap(TrianglePoints &triangle1, TrianglePoints &triangle2, float epsilon = 1.0/32)
-{
- // See if there is an edge of triangle1 such that all vertices in triangle2 are on the opposite side
- fixWinding(triangle1);
- for (int i = 0; i < 3; i++) {
- int ni = (i + 1) % 3;
- if (checkEdge(triangle1[i], triangle1[ni], triangle2[0], epsilon) &&
- checkEdge(triangle1[i], triangle1[ni], triangle2[1], epsilon) &&
- checkEdge(triangle1[i], triangle1[ni], triangle2[2], epsilon))
- return false;
- }
-
- // See if there is an edge of triangle2 such that all vertices in triangle1 are on the opposite side
- fixWinding(triangle2);
- for (int i = 0; i < 3; i++) {
- int ni = (i + 1) % 3;
-
- if (checkEdge(triangle2[i], triangle2[ni], triangle1[0], epsilon) &&
- checkEdge(triangle2[i], triangle2[ni], triangle1[1], epsilon) &&
- checkEdge(triangle2[i], triangle2[ni], triangle1[2], epsilon))
- return false;
- }
-
- return true;
-}
-
-bool checkLineTriangleOverlap(TrianglePoints &triangle, LinePoints &line, float epsilon = 1.0/32)
-{
- // See if all vertices of the triangle are on the same side of the line
- bool s1 = determinant(line[0], line[1], triangle[0]) < 0;
- auto s2 = determinant(line[0], line[1], triangle[1]) < 0;
- auto s3 = determinant(line[0], line[1], triangle[2]) < 0;
- // If all determinants have the same sign, then there is no overlap
- if (s1 == s2 && s2 == s3) {
- return false;
- }
- // See if there is an edge of triangle1 such that both vertices in line are on the opposite side
- fixWinding(triangle);
- for (int i = 0; i < 3; i++) {
- int ni = (i + 1) % 3;
- if (checkEdge(triangle[i], triangle[ni], line[0], epsilon) &&
- checkEdge(triangle[i], triangle[ni], line[1], epsilon))
- return false;
- }
-
- return true;
-}
-
-// We could slightly optimize this if we did fixWinding in advance
-bool checkTriangleContains (QVector2D pt, QVector2D v1, QVector2D v2, QVector2D v3, float epsilon = 1.0/32)
-{
- float d1, d2, d3;
- d1 = determinant(pt, v1, v2);
- d2 = determinant(pt, v2, v3);
- d3 = determinant(pt, v3, v1);
-
- bool allNegative = d1 < -epsilon && d2 < -epsilon && d3 < -epsilon;
- bool allPositive = d1 > epsilon && d2 > epsilon && d3 > epsilon;
-
- return allNegative || allPositive;
-}
-
-// e1 is always a concave curve. e2 can be curve or line
-static bool isOverlap(const QQuadPath &path, int e1, int e2)
-{
- const QQuadPath::Element &element1 = path.elementAt(e1);
- const QQuadPath::Element &element2 = path.elementAt(e2);
-
- TrianglePoints t1{ element1.startPoint(), element1.controlPoint(), element1.endPoint() };
-
- if (element2.isLine()) {
- LinePoints line{ element2.startPoint(), element2.endPoint() };
- return checkLineTriangleOverlap(t1, line);
- } else {
- TrianglePoints t2{ element2.startPoint(), element2.controlPoint(), element2.endPoint() };
- return checkTriangleOverlap(t1, t2);
- }
-
- return false;
-}
-
-static bool isOverlap(const QQuadPath &path, int index, const QVector2D &vertex)
-{
- const QQuadPath::Element &elem = path.elementAt(index);
- return checkTriangleContains(vertex, elem.startPoint(), elem.controlPoint(), elem.endPoint());
-}
-
-struct TriangleData
-{
- TrianglePoints points;
- int pathElementIndex;
- TrianglePoints normals;
-};
-
-// Returns a vector that is normal to baseLine, pointing to the right
-QVector2D normalVector(QVector2D baseLine)
-{
- QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
- return normal;
-}
-
-// Returns a vector that is normal to the path and pointing to the right. If endSide is false
-// the vector is normal to the start point, otherwise to the end point
-QVector2D normalVector(const QQuadPath::Element &element, bool endSide = false)
-{
- if (element.isLine())
- return normalVector(element.endPoint() - element.startPoint());
- else if (!endSide)
- return normalVector(element.controlPoint() - element.startPoint());
- else
- return normalVector(element.endPoint() - element.controlPoint());
-}
-
-// Returns a vector that is parallel to the path. If endSide is false
-// the vector starts at the start point and points forward,
-// otherwise it starts at the end point and points backward
-QVector2D tangentVector(const QQuadPath::Element &element, bool endSide = false)
-{
- if (element.isLine()) {
- if (!endSide)
- return element.endPoint() - element.startPoint();
- else
- return element.startPoint() - element.endPoint();
- } else {
- if (!endSide)
- return element.controlPoint() - element.startPoint();
- else
- return element.controlPoint() - element.endPoint();
- }
-}
-
-// Really simplistic O(n^2) triangulator - only intended for five points
-QList<TriangleData> simplePointTriangulator(const QList<QVector2D> &pts, const QList<QVector2D> &normals, int elementIndex)
-{
- int count = pts.size();
- Q_ASSERT(count >= 3);
- Q_ASSERT(normals.size() == count);
-
- // First we find the convex hull: it's always in positive determinant winding order
- QList<int> hull;
- float det1 = determinant(pts[0], pts[1], pts[2]);
- if (det1 > 0)
- hull << 0 << 1 << 2;
- else
- hull << 2 << 1 << 0;
- auto connectableInHull = [&](int idx) -> QList<int> {
- QList<int> r;
- const int n = hull.size();
- const auto &pt = pts[idx];
- for (int i = 0; i < n; ++i) {
- const auto &i1 = hull.at(i);
- const auto &i2 = hull.at((i+1) % n);
- if (determinant(pts[i1], pts[i2], pt) < 0.0f)
- r << i;
- }
- return r;
- };
- for (int i = 3; i < count; ++i) {
- auto visible = connectableInHull(i);
- if (visible.isEmpty())
- continue;
- int visCount = visible.count();
- int hullCount = hull.count();
- // Find where the visible part of the hull starts. (This is the part we need to triangulate to,
- // and the part we're going to replace. "visible" contains the start point of the line segments that are visible from p.
- int boundaryStart = visible[0];
- for (int j = 0; j < visCount - 1; ++j) {
- if ((visible[j] + 1) % hullCount != visible[j+1]) {
- boundaryStart = visible[j + 1];
- break;
- }
- }
- // Finally replace the points that are now inside the hull
- // We insert the new point after boundaryStart, and before boundaryStart + visCount (modulo...)
- // and remove the points in between
- int pointsToKeep = hullCount - visCount + 1;
- QList<int> newHull;
- newHull << i;
- for (int j = 0; j < pointsToKeep; ++j) {
- newHull << hull.at((j + boundaryStart + visCount) % hullCount);
- }
- hull = newHull;
- }
-
- // Now that we have a convex hull, we can trivially triangulate it
- QList<TriangleData> ret;
- for (int i = 1; i < hull.size() - 1; ++i) {
- int i0 = hull[0];
- int i1 = hull[i];
- int i2 = hull[i+1];
- ret.append({{pts[i0], pts[i1], pts[i2]}, elementIndex, {normals[i0], normals[i1], normals[i2]}});
- }
- return ret;
-}
-
-static bool needsSplit(const QQuadPath::Element &el, float penWidth)
-{
- Q_UNUSED(penWidth)
- const auto v1 = el.controlPoint() - el.startPoint();
- const auto v2 = el.endPoint() - el.controlPoint();
- float cos = QVector2D::dotProduct(v1, v2) / (v1.length() * v2.length());
- return cos < 0.9;
-}
-static void splitElementIfNecessary(QQuadPath &path, int index, float penWidth)
-{
- auto &e = path.elementAt(index);
- if (e.isLine())
- return;
- if (e.childCount() == 0) {
- if (needsSplit(e, penWidth))
- path.splitElementAt(index);
- } else {
- for (int i = 0; i < e.childCount(); ++i)
- splitElementIfNecessary(path, e.indexOfChild(i), penWidth);
- }
-}
-
-static QQuadPath subdivide(const QQuadPath &path, int subdivisions, float penWidth)
-{
- QQuadPath newPath = path;
-
- for (int i = 0; i < subdivisions; ++i)
- for (int j = 0; j < newPath.elementCount(); j++)
- splitElementIfNecessary(newPath, j, penWidth);
- return newPath;
-}
-
-static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penWidth, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, float miterLimit)
-{
- const bool bevelJoin = joinStyle == Qt::BevelJoin;
- const bool roundJoin = joinStyle == Qt::RoundJoin;
- const bool miterJoin = !bevelJoin && !roundJoin;
-
- const bool roundCap = capStyle == Qt::RoundCap;
- const bool squareCap = capStyle == Qt::SquareCap;
- // We can't use the simple miter for miter joins, since the shader currently only supports round joins
- const bool simpleMiter = joinStyle == Qt::RoundJoin;
-
- Q_ASSERT(miterLimit > 0 || !miterJoin);
- float inverseMiterLimit = miterJoin ? 1.0f / miterLimit : 1.0;
-
- const float penFactor = penWidth / 2;
-
- // Returns {inner1, inner2, outer1, outer2, outerMiter}
- // where foo1 is for the end of element1 and foo2 is for the start of element2
- // and inner1 == inner2 unless we had to give up finding a decent point
- auto calculateJoin = [&](const QQuadPath::Element *element1, const QQuadPath::Element *element2,
- bool &outerBisectorWithinMiterLimit, bool &innerIsRight, bool &giveUp) -> std::array<QVector2D, 5>
- {
- outerBisectorWithinMiterLimit = true;
- innerIsRight = true;
- giveUp = false;
- if (!element1) {
- Q_ASSERT(element2);
- QVector2D n = normalVector(*element2).normalized();
- return {n, n, -n, -n, -n};
- }
- if (!element2) {
- Q_ASSERT(element1);
- QVector2D n = normalVector(*element1, true).normalized();
- return {n, n, -n, -n, -n};
- }
-
- Q_ASSERT(element1->endPoint() == element2->startPoint());
-
- const auto p1 = element1->isLine() ? element1->startPoint() : element1->controlPoint();
- const auto p2 = element1->endPoint();
- const auto p3 = element2->isLine() ? element2->endPoint() : element2->controlPoint();
-
- const auto v1 = (p1 - p2).normalized();
- const auto v2 = (p3 - p2).normalized();
- const auto b = (v1 + v2);
-
- constexpr float epsilon = 1.0f / 32.0f;
- bool smoothJoin = qAbs(b.x()) < epsilon && qAbs(b.y()) < epsilon;
-
- if (smoothJoin) {
- // v1 and v2 are almost parallel and pointing in opposite directions
- // angle bisector formula will give an almost null vector: use normal of bisector of normals instead
- QVector2D n1(-v1.y(), v1.x());
- QVector2D n2(-v2.y(), v2.x());
- QVector2D n = (n2 - n1).normalized();
- return {n, n, -n, -n, -n};
- }
- // Calculate the length of the bisector, so it will cover the entire miter.
- // Using the identity sin(x/2) == sqrt((1 - cos(x)) / 2), and the fact that the
- // dot product of two unit vectors is the cosine of the angle between them
- // The length of the miter is w/sin(x/2) where x is the angle between the two elements
-
- const auto bisector = b.normalized();
- float cos2x = QVector2D::dotProduct(v1, v2);
- cos2x = qMin(1.0f, cos2x); // Allow for float inaccuracy
- float sine = sqrt((1.0f - cos2x) / 2);
- innerIsRight = determinant(p1, p2, p3) > 0;
- sine = qMax(sine, 0.01f); // Avoid divide by zero
- float length = penFactor / sine;
-
- // Check if bisector is longer than one of the lines it's trying to bisect
-
- auto tooLong = [](QVector2D p1, QVector2D p2, QVector2D n, float length, float margin) -> bool {
- auto v = p2 - p1;
- // It's too long if the projection onto the bisector is longer than the bisector
- // and the projection onto the normal to the bisector is shorter
- // than the pen margin (that projection is just v - proj)
- // (we're adding a 10% safety margin to make room for AA -- not exact)
- auto projLen = QVector2D::dotProduct(v, n);
- return projLen * 0.9f < length && (v - n * projLen).length() * 0.9 < margin;
- };
-
-
- // The angle bisector of the tangent lines is not correct for curved lines. We could fix this by calculating
- // the exact intersection point, but for now just give up and use the normals.
-
- giveUp = !element1->isLine() || !element2->isLine()
- || tooLong(p1, p2, bisector, length, penFactor)
- || tooLong(p3, p2, bisector, length, penFactor);
- outerBisectorWithinMiterLimit = sine >= inverseMiterLimit / 2.0f;
- bool simpleJoin = simpleMiter && outerBisectorWithinMiterLimit && !giveUp;
- const QVector2D bn = bisector / sine;
-
- if (simpleJoin)
- return {bn, bn, -bn, -bn, -bn}; // We only have one inner and one outer point TODO: change inner point when conflict/curve
- const QVector2D n1 = normalVector(*element1, true).normalized();
- const QVector2D n2 = normalVector(*element2).normalized();
- if (giveUp) {
- if (innerIsRight)
- return {n1, n2, -n1, -n2, -bn};
- else
- return {-n1, -n2, n1, n2, -bn};
-
- } else {
- if (innerIsRight)
- return {bn, bn, -n1, -n2, -bn};
- else
- return {bn, bn, n1, n2, -bn};
- }
- };
-
- QList<TriangleData> ret;
-
- auto triangulateCurve = [&](int idx, const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, const QVector2D &p4,
- const QVector2D &n1, const QVector2D &n2, const QVector2D &n3, const QVector2D &n4)
- {
- const auto &element = path.elementAt(idx);
- const auto &s = element.startPoint();
- const auto &c = element.controlPoint();
- const auto &e = element.endPoint();
- // TODO: Don't flatten the path in addCurveStrokeNodes, but iterate over the children here instead
- bool controlPointOnRight = determinant(s, c, e) > 0;
- QVector2D startNormal = normalVector(element).normalized();
- QVector2D endNormal = normalVector(element, true).normalized();
- QVector2D controlPointNormal = (startNormal + endNormal).normalized();
- if (controlPointOnRight)
- controlPointNormal = -controlPointNormal;
- QVector2D p5 = c + controlPointNormal * penFactor; // This is too simplistic
- TrianglePoints t1{p1, p2, p5};
- TrianglePoints t2{p3, p4, p5};
- bool simpleCase = !checkTriangleOverlap(t1, t2);
-
- if (simpleCase) {
- ret.append({{p1, p2, p5}, idx, {n1, n2, controlPointNormal}});
- ret.append({{p3, p4, p5}, idx, {n3, n4, controlPointNormal}});
- if (controlPointOnRight) {
- ret.append({{p1, p3, p5}, idx, {n1, n3, controlPointNormal}});
- } else {
- ret.append({{p2, p4, p5}, idx, {n2, n4, controlPointNormal}});
- }
- } else {
- ret.append(simplePointTriangulator({p1, p2, p5, p3, p4}, {n1, n2, controlPointNormal, n3, n4}, idx));
- }
- };
-
- // Each element is calculated independently, so we don't have to special-case closed sub-paths.
- // Take care so the end points of one element are precisely equal to the start points of the next.
- // Any additional triangles needed for joining are added at the end of the current element.
-
- int count = path.elementCount();
- int subStart = 0;
- while (subStart < count) {
- int subEnd = subStart;
- for (int i = subStart + 1; i < count; ++i) {
- const auto &e = path.elementAt(i);
- if (e.isSubpathStart()) {
- subEnd = i - 1;
- break;
- }
- if (i == count - 1) {
- subEnd = i;
- break;
- }
- }
- bool closed = path.elementAt(subStart).startPoint() == path.elementAt(subEnd).endPoint();
- const int subCount = subEnd - subStart + 1;
-
- auto addIdx = [&](int idx, int delta) -> int {
- int subIdx = idx - subStart;
- if (closed)
- subIdx = (subIdx + subCount + delta) % subCount;
- else
- subIdx += delta;
- return subStart + subIdx;
- };
- auto elementAt = [&](int idx, int delta) -> const QQuadPath::Element * {
- int subIdx = idx - subStart;
- if (closed) {
- subIdx = (subIdx + subCount + delta) % subCount;
- return &path.elementAt(subStart + subIdx);
- }
- subIdx += delta;
- if (subIdx >= 0 && subIdx < subCount)
- return &path.elementAt(subStart + subIdx);
- return nullptr;
- };
-
- for (int i = subStart; i <= subEnd; ++i) {
- const auto &element = path.elementAt(i);
- const auto *nextElement = elementAt(i, +1);
- const auto *prevElement = elementAt(i, -1);
-
- const auto &s = element.startPoint();
- const auto &e = element.endPoint();
-
- bool startInnerIsRight;
- bool startBisectorWithinMiterLimit; // Not used
- bool giveUpOnStartJoin; // Not used
- auto startJoin = calculateJoin(prevElement, &element,
- startBisectorWithinMiterLimit, startInnerIsRight,
- giveUpOnStartJoin);
- const QVector2D &startInner = startJoin[1];
- const QVector2D &startOuter = startJoin[3];
-
- bool endInnerIsRight;
- bool endBisectorWithinMiterLimit;
- bool giveUpOnEndJoin;
- auto endJoin = calculateJoin(&element, nextElement,
- endBisectorWithinMiterLimit, endInnerIsRight,
- giveUpOnEndJoin);
- QVector2D endInner = endJoin[0];
- QVector2D endOuter = endJoin[2];
- QVector2D nextOuter = endJoin[3];
- QVector2D outerB = endJoin[4];
-
- QVector2D p1, p2, p3, p4;
- QVector2D n1, n2, n3, n4;
-
- if (startInnerIsRight) {
- n1 = startInner;
- n2 = startOuter;
- } else {
- n1 = startOuter;
- n2 = startInner;
- }
-
- p1 = s + n1 * penFactor;
- p2 = s + n2 * penFactor;
-
- // repeat logic above for the other end:
- if (endInnerIsRight) {
- n3 = endInner;
- n4 = endOuter;
- } else {
- n3 = endOuter;
- n4 = endInner;
- }
-
- p3 = e + n3 * penFactor;
- p4 = e + n4 * penFactor;
-
- // End caps
-
- if (!prevElement) {
- QVector2D capSpace = tangentVector(element).normalized() * -penFactor;
- if (roundCap) {
- p1 += capSpace;
- p2 += capSpace;
- } else if (squareCap) {
- QVector2D c1 = p1 + capSpace;
- QVector2D c2 = p2 + capSpace;
- ret.append({{p1, s, c1}, -1, {}});
- ret.append({{c1, s, c2}, -1, {}});
- ret.append({{p2, s, c2}, -1, {}});
- }
- }
- if (!nextElement) {
- QVector2D capSpace = tangentVector(element, true).normalized() * -penFactor;
- if (roundCap) {
- p3 += capSpace;
- p4 += capSpace;
- } else if (squareCap) {
- QVector2D c3 = p3 + capSpace;
- QVector2D c4 = p4 + capSpace;
- ret.append({{p3, e, c3}, -1, {}});
- ret.append({{c3, e, c4}, -1, {}});
- ret.append({{p4, e, c4}, -1, {}});
- }
- }
-
- if (element.isLine()) {
- ret.append({{p1, p2, p3}, i, {n1, n2, n3}});
- ret.append({{p2, p3, p4}, i, {n2, n3, n4}});
- } else {
- triangulateCurve(i, p1, p2, p3, p4, n1, n2, n3, n4);
- }
-
- bool trivialJoin = simpleMiter && endBisectorWithinMiterLimit && !giveUpOnEndJoin;
- if (!trivialJoin && nextElement) {
- // inside of join (opposite of bevel) is defined by
- // triangle s, e, next.e
- bool innerOnRight = endInnerIsRight;
-
- const auto outer1 = e + endOuter * penFactor;
- const auto outer2 = e + nextOuter * penFactor;
- //const auto inner = e + endInner * penFactor;
-
- if (bevelJoin || (miterJoin && !endBisectorWithinMiterLimit)) {
- ret.append({{outer1, e, outer2}, -1, {}});
- } else if (roundJoin) {
- ret.append({{outer1, e, outer2}, i, {}});
- QVector2D nn = calcNormalVector(outer1, outer2).normalized() * penFactor;
- if (!innerOnRight)
- nn = -nn;
- ret.append({{outer1, outer1 + nn, outer2}, i, {}});
- ret.append({{outer1 + nn, outer2, outer2 + nn}, i, {}});
-
- } else if (miterJoin) {
- QVector2D outer = e + outerB * penFactor;
- ret.append({{outer1, e, outer}, -2, {}});
- ret.append({{outer, e, outer2}, -2, {}});
- }
-
- if (!giveUpOnEndJoin) {
- QVector2D inner = e + endInner * penFactor;
- ret.append({{inner, e, outer1}, i, {endInner, {}, endOuter}});
- // The remaining triangle ought to be done by nextElement, but we don't have start join logic there (yet)
- int nextIdx = addIdx(i, +1);
- ret.append({{inner, e, outer2}, nextIdx, {endInner, {}, nextOuter}});
- }
- }
- }
- subStart = subEnd + 1;
- }
- return ret;
-}
-
-};
-
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData, NodeList *debugNodes)
{
NodeList ret;
const QColor &color = pathData.pen.color();
const bool debug = debugVisualization() & DebugCurves;
- auto *node = new QQuickShapeStrokeNode;
+ auto *node = new QSGCurveStrokeNode;
node->setDebug(0.2f * debug);
QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
- float miterLimit = pathData.pen.miterLimit();
- float penWidth = pathData.pen.widthF();
+ const float miterLimit = pathData.pen.miterLimit();
+ const float penWidth = pathData.pen.widthF();
static const int subdivisions = qEnvironmentVariable("QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral("3")).toInt();
- auto thePath = subdivide(pathData.strokePath, subdivisions, penWidth).flattened(); // TODO: don't flatten, but handle it in the triangulator
- auto triangles = customTriangulator2(thePath, penWidth, pathData.pen.joinStyle(), pathData.pen.capStyle(), miterLimit);
-
- auto addCurveTriangle = [&](const QQuadPath::Element &element, const TriangleData &t){
- const QVector2D &p0 = t.points[0];
- const QVector2D &p1 = t.points[1];
- const QVector2D &p2 = t.points[2];
- if (element.isLine()) {
- node->appendTriangle(t.points,
- LinePoints{element.startPoint(), element.endPoint()},
- t.normals);
- } else {
- node->appendTriangle(t.points,
- { element.startPoint(), element.controlPoint(), element.endPoint()},
- t.normals);
- }
- 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});
- };
-
- auto addBevelTriangle = [&](const TrianglePoints &p)
- {
- QVector2D fp1 = p[0];
- QVector2D fp2 = p[2];
-
- // That describes a path that passes through those points. We want the stroke
- // edge, so we need to shift everything down by the stroke offset
-
- QVector2D nn = calcNormalVector(p[0], p[2]);
- if (determinant(p) < 0)
- nn = -nn;
- float delta = penWidth / 2;
-
- QVector2D offset = nn.normalized() * delta;
- fp1 += offset;
- fp2 += offset;
-
- TrianglePoints n;
- // p1 is inside, so n[1] is {0,0}
- n[0] = (p[0] - p[1]).normalized();
- n[2] = (p[2] - p[1]).normalized();
- node->appendTriangle(p, LinePoints{fp1, fp2}, n);
-
- wfVertices.append({p[0].x(), p[0].y(), 1.0f, 0.0f, 0.0f});
- wfVertices.append({p[1].x(), p[1].y(), 0.0f, 1.0f, 0.0f});
- wfVertices.append({p[2].x(), p[2].y(), 0.0f, 0.0f, 1.0f});
- };
-
- for (const auto &triangle : triangles) {
- if (triangle.pathElementIndex < 0) {
- addBevelTriangle(triangle.points);
- continue;
- }
- const auto &element = thePath.elementAt(triangle.pathElementIndex);
- addCurveTriangle(element, triangle);
- }
+ 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
@@ -1534,123 +614,4 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes
return ret;
}
-// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
-// elements
-static void handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
-{
- if (!isOverlap(path, e1, e2)) {
- return;
- }
-
- if (recursionLevel > 8) {
- qCDebug(lcShapeCurveRenderer) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
- return;
- }
-
- if (path.elementAt(e1).childCount() > 1) {
- auto e11 = path.indexOfChildAt(e1, 0);
- auto e12 = path.indexOfChildAt(e1, 1);
- handleOverlap(path, e11, e2, recursionLevel + 1);
- handleOverlap(path, e12, e2, recursionLevel + 1);
- } else if (path.elementAt(e2).childCount() > 1) {
- auto e21 = path.indexOfChildAt(e2, 0);
- auto e22 = path.indexOfChildAt(e2, 1);
- handleOverlap(path, e1, e21, recursionLevel + 1);
- handleOverlap(path, e1, e22, recursionLevel + 1);
- } else {
- path.splitElementAt(e1);
- auto e11 = path.indexOfChildAt(e1, 0);
- auto e12 = path.indexOfChildAt(e1, 1);
- bool overlap1 = isOverlap(path, e11, e2);
- bool overlap2 = isOverlap(path, e12, e2);
- if (!overlap1 && !overlap2)
- return; // no more overlap: success!
-
- // We need to split more:
- if (path.elementAt(e2).isLine()) {
- // Splitting a line won't help, so we just split e1 further
- if (overlap1)
- handleOverlap(path, e11, e2, recursionLevel + 1);
- if (overlap2)
- handleOverlap(path, e12, e2, recursionLevel + 1);
- } else {
- // See if splitting e2 works:
- path.splitElementAt(e2);
- auto e21 = path.indexOfChildAt(e2, 0);
- auto e22 = path.indexOfChildAt(e2, 1);
- if (overlap1) {
- handleOverlap(path, e11, e21, recursionLevel + 1);
- handleOverlap(path, e11, e22, recursionLevel + 1);
- }
- if (overlap2) {
- handleOverlap(path, e12, e21, recursionLevel + 1);
- handleOverlap(path, e12, e22, recursionLevel + 1);
- }
- }
- }
-}
-
-// Test if element contains a start point of another element
-static void handleOverlap(QQuadPath &path, int e1, const QVector2D vertex, int recursionLevel = 0)
-{
- // First of all: Ignore the next element: it trivially overlaps (maybe not necessary: we do check for strict containment)
- if (vertex == path.elementAt(e1).endPoint() || !isOverlap(path, e1, vertex))
- return;
- if (recursionLevel > 8) {
- qCDebug(lcShapeCurveRenderer) << "Vertex overlap: recursion level" << recursionLevel << "aborting!";
- return;
- }
-
- // Don't split if we're already split
- if (path.elementAt(e1).childCount() == 0)
- path.splitElementAt(e1);
-
- handleOverlap(path, path.indexOfChildAt(e1, 0), vertex, recursionLevel + 1);
- handleOverlap(path, path.indexOfChildAt(e1, 1), vertex, recursionLevel + 1);
-}
-
-void QQuickShapeCurveRenderer::solveOverlaps(QQuadPath &path)
-{
- for (int i = 0; i < path.elementCount(); i++) {
- auto &element = path.elementAt(i);
- // only concave curve overlap is problematic, as long as we don't allow self-intersecting curves
- if (element.isLine() || element.isConvex())
- continue;
-
- for (int j = 0; j < path.elementCount(); j++) {
- if (i == j)
- continue; // Would be silly to test overlap with self
- auto &other = path.elementAt(j);
- if (!other.isConvex() && !other.isLine() && j < i)
- continue; // We have already tested this combination, so no need to test again
- handleOverlap(path, i, j);
- }
- }
-
- static const int handleConcaveJoint = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_CONCAVE_JOINT");
- if (handleConcaveJoint) {
- // Note that the joint between two non-concave elements can also be concave, so we have to
- // test all convex elements to see if there is a vertex in any of them. We could do it the other way
- // by identifying concave joints, but then we would have to know which side is the inside
- // TODO: optimization potential! Maybe do that at the same time as we identify concave curves?
-
- // We do this in a separate loop, since the triangle/triangle test above is more expensive, and
- // if we did this first, there would be more triangles to test
- for (int i = 0; i < path.elementCount(); i++) {
- auto &element = path.elementAt(i);
- if (!element.isConvex())
- continue;
-
- for (int j = 0; j < path.elementCount(); j++) {
- // We only need to check one point per element, since all subpaths are closed
- // Could do smartness to skip elements that cannot overlap, but let's do it the easy way first
- if (i == j)
- continue;
- const auto &other = path.elementAt(j);
- handleOverlap(path, i, other.startPoint());
- }
- }
- }
-}
-
QT_END_NAMESPACE
diff --git a/src/quickshapes/qquickshapecurverenderer_p.h b/src/quickshapes/qquickshapecurverenderer_p.h
index ae3689d5d6..1b8e7065ab 100644
--- a/src/quickshapes/qquickshapecurverenderer_p.h
+++ b/src/quickshapes/qquickshapecurverenderer_p.h
@@ -17,8 +17,9 @@
#include <QtQuickShapes/private/qquickshapesglobal_p.h>
#include <QtQuickShapes/private/qquickshape_p_p.h>
-#include <QtQuickShapes/private/qquadpath_p.h>
-#include <QtQuickShapes/private/qquickshapeabstractcurvenode_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>
@@ -27,6 +28,7 @@
#include <QtCore/qrunnable.h>
#include <QtGui/private/qtriangulator_p.h>
+#include <QtQuick/private/qsgcurvefillnode_p.h>
QT_BEGIN_NAMESPACE
@@ -57,7 +59,7 @@ public:
void setRootNode(QSGNode *node);
- using NodeList = QVector<QQuickShapeAbstractCurveNode *>;
+ using NodeList = QVector<QSGCurveAbstractNode *>;
enum DirtyFlag
{
@@ -79,15 +81,15 @@ public:
private:
struct PathData {
- bool isFillVisible() const { return fillColor.alpha() > 0 || gradientType != NoGradient; }
+ bool isFillVisible() const { return fillColor.alpha() > 0 || gradientType != QGradient::NoGradient; }
bool isStrokeVisible() const
{
return validPenWidth && pen.color().alpha() > 0 && pen.style() != Qt::NoPen;
}
- FillGradientType gradientType = NoGradient;
- GradientDesc gradient;
+ QGradient::Type gradientType = QGradient::NoGradient;
+ QSGGradientCache::GradientDesc gradient;
QPainterPath originalPath;
QQuadPath path;
QQuadPath fillPath;
@@ -111,8 +113,6 @@ private:
NodeList addTriangulatingStrokerNodes(const PathData &pathData, NodeList *debugNodes);
NodeList addCurveStrokeNodes(const PathData &pathData, NodeList *debugNodes);
- void solveOverlaps(QQuadPath &path);
-
QSGNode *m_rootNode;
QVector<PathData> m_paths;
static int debugVisualizationFlags;
diff --git a/src/quickshapes/qquickshapegenericrenderer.cpp b/src/quickshapes/qquickshapegenericrenderer.cpp
index a7f830ad01..896d85a623 100644
--- a/src/quickshapes/qquickshapegenericrenderer.cpp
+++ b/src/quickshapes/qquickshapegenericrenderer.cpp
@@ -7,6 +7,8 @@
#include <rhi/qrhi.h>
#include <QSGVertexColorMaterial>
+#include <QtQuick/private/qsggradientcache_p.h>
+
#if QT_CONFIG(thread)
#include <QThreadPool>
#endif
@@ -176,7 +178,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());
@@ -754,8 +756,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 +779,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;
@@ -883,8 +885,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 +908,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;
@@ -1001,8 +1003,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 +1026,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;
diff --git a/src/quickshapes/qquickshapegenericrenderer_p.h b/src/quickshapes/qquickshapegenericrenderer_p.h
index a5ec01329d..1455c02424 100644
--- a/src/quickshapes/qquickshapegenericrenderer_p.h
+++ b/src/quickshapes/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>
@@ -101,7 +102,7 @@ private:
Qt::FillRule fillRule;
QPainterPath path;
FillGradientType fillGradientActive;
- GradientDesc fillGradient;
+ QSGGradientCache::GradientDesc fillGradient;
VertexContainerType fillVertices;
IndexContainerType fillIndices;
QSGGeometry::Type indexType;
@@ -188,7 +189,7 @@ public:
void activateMaterial(QQuickWindow *window, Material m);
// shadow data for custom materials
- QQuickAbstractPathRenderer::GradientDesc m_fillGradient;
+ QSGGradientCache::GradientDesc m_fillGradient;
private:
QScopedPointer<QSGMaterial> m_material;
diff --git a/tests/baseline/scenegraph/data/text/text_emoji.qml b/tests/baseline/scenegraph/data/text/text_emoji.qml
index d50f6d6715..c9b9a33715 100644
--- a/tests/baseline/scenegraph/data/text/text_emoji.qml
+++ b/tests/baseline/scenegraph/data/text/text_emoji.qml
@@ -2,14 +2,13 @@ import QtQuick 2.0
Item {
width: 320
- height: 480
-
+ height: 680
Component {
id: component
Column {
property variant listModel: model
Repeater {
- model: [Text.NativeRendering, Text.QtRendering]
+ model: [Text.NativeRendering, Text.QtRendering, Text.CurveRendering]
Rectangle {
width: text.implicitWidth
height: text.implicitHeight
diff --git a/tests/baseline/scenegraph/data/text/text_emoji_hebrew.qml b/tests/baseline/scenegraph/data/text/text_emoji_hebrew.qml
index b67d584a12..4062690cd0 100644
--- a/tests/baseline/scenegraph/data/text/text_emoji_hebrew.qml
+++ b/tests/baseline/scenegraph/data/text/text_emoji_hebrew.qml
@@ -2,14 +2,14 @@ import QtQuick 2.0
Item {
width: 320
- height: 480
+ height: 680
Component {
id: component
Column {
property variant listModel: model
Repeater {
- model: [Text.NativeRendering, Text.QtRendering]
+ model: [Text.NativeRendering, Text.QtRendering, Text.CurveRendering]
Rectangle {
width: text.implicitWidth
height: text.implicitHeight
diff --git a/tests/manual/painterpathquickshape/TextShape.qml b/tests/manual/painterpathquickshape/TextShape.qml
index fa595d3390..f9cabf1ecd 100644
--- a/tests/manual/painterpathquickshape/TextShape.qml
+++ b/tests/manual/painterpathquickshape/TextShape.qml
@@ -7,7 +7,7 @@ import QtQuick.Controls
import QtQuick.Dialogs
ControlledShape {
- fillRule: ShapePath.OddEvenFill
+ fillRule: ShapePath.WindingFill
delegate: [
PathText {
text: "foobar"
diff --git a/tests/manual/textrendering/CMakeLists.txt b/tests/manual/textrendering/CMakeLists.txt
new file mode 100644
index 0000000000..9987010192
--- /dev/null
+++ b/tests/manual/textrendering/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ project(textrendering LANGUAGES C CXX ASM)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+find_package(Qt6 COMPONENTS ShaderTools)
+
+qt_internal_add_manual_test(textrendering
+ GUI
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickPrivate
+)
+
+
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_internal_add_resource(textrendering "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
+
+qt_add_qml_module(textrendering
+ VERSION 1.0
+ URI TextRendering
+ RESOURCE_PREFIX /
+)
diff --git a/tests/manual/textrendering/main.cpp b/tests/manual/textrendering/main.cpp
new file mode 100644
index 0000000000..0b2490f5df
--- /dev/null
+++ b/tests/manual/textrendering/main.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QFontDatabase>
+
+int main(int argc, char *argv[])
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+#endif
+ QGuiApplication app(argc, argv);
+ app.setOrganizationName("QtProject");
+
+ QQmlApplicationEngine engine;
+ const QUrl url(QStringLiteral("qrc:/main.qml"));
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+ &app, [url](QObject *obj, const QUrl &objUrl) {
+ if (!obj && url == objUrl)
+ QCoreApplication::exit(-1);
+ }, Qt::QueuedConnection);
+ engine.load(url);
+
+ return app.exec();
+}
diff --git a/tests/manual/textrendering/main.qml b/tests/manual/textrendering/main.qml
new file mode 100644
index 0000000000..5d3a6489dd
--- /dev/null
+++ b/tests/manual/textrendering/main.qml
@@ -0,0 +1,103 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Shapes
+
+Window {
+ id: theWindow
+ width: 1024
+ height: 768
+ visible: true
+ title: qsTr("Text Rendering")
+ color: "white"
+
+ Text {
+ id: dummyTextRendering
+ scale: scaleSlider.value
+ anchors.centerIn: parent
+ text: dummyText.text
+ font.pixelSize: fontSize.value
+ renderType: renderTypeCombo.currentIndex
+ style: styleCombo.currentIndex
+ styleColor: "indianred"
+ color: "black"
+ visible: renderTypeCombo.currentIndex <= 2
+ }
+
+ Shape {
+ id: dummyShapeRendering
+ anchors.centerIn: parent
+ scale: scaleSlider.value
+ visible: !dummyTextRendering.visible
+ width: boundingRect.width
+ height: boundingRect.height
+ preferredRendererType: shapesRendererCombo.currentIndex === 0 ? Shape.GeometryRenderer : Shape.CurveRenderer
+
+ ShapePath {
+ id: shapePath
+ fillColor: "black"
+ strokeColor: styleCombo.currentIndex === 1 ? "indianred" : "transparent"
+ strokeStyle: ShapePath.SolidLine
+ strokeWidth: 1
+ fillRule: ShapePath.WindingFill
+ PathText {
+ text: dummyText.text
+ font.pixelSize: fontSize.value
+ }
+ }
+ }
+
+ RowLayout {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ TextField {
+ id: dummyText
+ text: "Foobar"
+ }
+
+ Label {
+ text: "Scale:"
+ }
+
+ Slider {
+ id: scaleSlider
+ from: 0.5
+ to: 10
+ value: 1
+ }
+
+ Label {
+ text: "Font size:"
+ }
+
+ Slider {
+ id: fontSize
+ from: 1
+ to: 1000
+ value: 100
+ Layout.fillWidth: true
+ }
+
+ ComboBox {
+ id: styleCombo
+ model: [ "Normal", "Outline", "Raised", "Sunken" ]
+ }
+
+ ComboBox {
+ id: renderTypeCombo
+ model: [ "QtRendering", "NativeRendering", "CurveRendering", "Qt Quick Shapes" ]
+ }
+
+ ComboBox {
+ id: shapesRendererCombo
+ model: [ "GeometryRenderer", "CurveRenderer" ]
+ visible: renderTypeCombo.currentIndex > 2
+ }
+ }
+}