aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Olav Tvete <paul.tvete@qt.io>2023-11-23 12:59:41 +0100
committerPaul Olav Tvete <paul.tvete@qt.io>2024-01-17 15:36:27 +0100
commit96bd963cb7af37fd3b20ce3a1c9fd50939bed6fc (patch)
treeb130df39d127039ca1fc5b1688c3913827b6f7e1
parente7d8ebb25be6a3f11c7cf3f42660804c58650e19 (diff)
Use shape hints in the curve renderer
We don't need to do expensive operations if we already know that the path is well behaved. Task-number: QTBUG-112340 Pick-to: 6.7 Change-Id: Ic386b7f293045c28294f56ad433bdae2b3b6b0e5 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor.cpp12
-rw-r--r--src/quick/scenegraph/qsgcurveprocessor_p.h4
-rw-r--r--src/quick/scenegraph/util/qquadpath.cpp33
-rw-r--r--src/quick/scenegraph/util/qquadpath_p.h46
-rw-r--r--src/quickshapes/qquickshapecurverenderer.cpp11
-rw-r--r--src/quickshapes/qquickshapecurverenderer_p.h1
6 files changed, 87 insertions, 20 deletions
diff --git a/src/quick/scenegraph/qsgcurveprocessor.cpp b/src/quick/scenegraph/qsgcurveprocessor.cpp
index 29e223d49f..a58c087fa3 100644
--- a/src/quick/scenegraph/qsgcurveprocessor.cpp
+++ b/src/quick/scenegraph/qsgcurveprocessor.cpp
@@ -884,8 +884,10 @@ static void handleOverlap(QQuadPath &path, int e1, const QVector2D vertex, int r
}
-void QSGCurveProcessor::solveOverlaps(QQuadPath &path)
+void QSGCurveProcessor::solveOverlaps(QQuadPath &path, OverlapSolveMode mode)
{
+ if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
+ return;
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
@@ -902,7 +904,7 @@ void QSGCurveProcessor::solveOverlaps(QQuadPath &path)
}
}
- static const int handleConcaveJoint = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_CONCAVE_JOINT");
+ const bool handleConcaveJoint = mode == FullOverlapSolve;
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
@@ -926,6 +928,7 @@ void QSGCurveProcessor::solveOverlaps(QQuadPath &path)
}
}
}
+ path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
}
// A fast algorithm to find path elements that might overlap. We will only check the overlap of the
@@ -996,6 +999,9 @@ QList<QPair<int, int>> QSGCurveProcessor::findOverlappingCandidates(const QQuadP
// Returns true if the path was changed
bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool alwaysReorder)
{
+ if (path.testHint(QQuadPath::PathNonIntersecting))
+ return false;
+
struct IntersectionData { int e1; int e2; float t1; float t2; bool in1 = false, in2 = false, out1 = false, out2 = false; };
QList<IntersectionData> intersections;
@@ -1330,6 +1336,7 @@ bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool alwaysReorder)
qCDebug(lcSGCurveIntersectionSolver) << "All subpaths handled. Looking for unhandled intersections.";
if (intersections.isEmpty()) {
qCDebug(lcSGCurveIntersectionSolver) << "All intersections handled. I am done.";
+ fixedPath.setPathHints(path.pathHints() | QQuadPath::PathNonIntersecting);
path = fixedPath;
return true;
}
@@ -1396,6 +1403,7 @@ bool QSGCurveProcessor::solveIntersections(QQuadPath &path, bool alwaysReorder)
// Check the totalIterations as a sanity check. Should never be triggered.
qWarning() << "Could not solve intersections of path. This should not happen. Returning the path unchanged.";
+
return false;
}
diff --git a/src/quick/scenegraph/qsgcurveprocessor_p.h b/src/quick/scenegraph/qsgcurveprocessor_p.h
index d9ca9da5bb..18e08dcbae 100644
--- a/src/quick/scenegraph/qsgcurveprocessor_p.h
+++ b/src/quick/scenegraph/qsgcurveprocessor_p.h
@@ -32,6 +32,7 @@ public:
const std::array<QVector2D, 3> &,
bool)> addStrokeTriangleCallback;
+ enum OverlapSolveMode { FullOverlapSolve, SkipConcaveJoinsSolve };
static void processFill(const QQuadPath &path,
Qt::FillRule fillRule,
@@ -43,10 +44,9 @@ public:
Qt::PenCapStyle capStyle,
addStrokeTriangleCallback addTriangle,
int subdivisions = 3);
- static void solveOverlaps(QQuadPath &path);
+ static void solveOverlaps(QQuadPath &path, OverlapSolveMode mode = SkipConcaveJoinsSolve);
static QList<QPair<int, int>> findOverlappingCandidates(const QQuadPath &path);
static bool solveIntersections(QQuadPath &path, bool alwaysReorder = true);
-
};
QT_END_NAMESPACE
diff --git a/src/quick/scenegraph/util/qquadpath.cpp b/src/quick/scenegraph/util/qquadpath.cpp
index 70fca33e1c..188d53cd6a 100644
--- a/src/quick/scenegraph/util/qquadpath.cpp
+++ b/src/quick/scenegraph/util/qquadpath.cpp
@@ -474,12 +474,14 @@ QQuadPath::Element::CurvatureFlags QQuadPath::coordinateOrderOfElement(const QQu
return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
}
-QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path)
+QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path, PathHints hints)
{
QQuadPath res;
res.reserve(path.elementCount());
res.setFillRule(path.fillRule());
+ const bool isQuadratic = hints & PathQuadratic;
+
QPolygonF quads;
QPointF sp;
for (int i = 0; i < path.elementCount(); ++i) {
@@ -497,12 +499,18 @@ QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path)
QPointF cp1 = ep;
QPointF cp2(path.elementAt(++i));
ep = path.elementAt(++i);
- QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
- qt_toQuadratics(b, &quads);
- for (int i = 1; i < quads.size(); i += 2) {
- QVector2D cp(quads[i]);
- QVector2D ep(quads[i + 1]);
- res.quadTo(cp, ep);
+ if (isQuadratic) {
+ const qreal f = 3.0 / 2.0;
+ const QPointF cp = sp + f * (cp1 - sp);
+ res.quadTo(QVector2D(cp), QVector2D(ep));
+ } else {
+ QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
+ qt_toQuadratics(b, &quads);
+ for (int i = 1; i < quads.size(); i += 2) {
+ QVector2D cp(quads[i]);
+ QVector2D ep(quads[i + 1]);
+ res.quadTo(cp, ep);
+ }
}
break;
}
@@ -513,6 +521,7 @@ QQuadPath QQuadPath::fromPainterPath(const QPainterPath &path)
sp = ep;
}
+ res.setPathHints(hints | PathQuadratic);
return res;
}
@@ -527,12 +536,16 @@ void QQuadPath::addCurvatureData()
// can easily detect curvature of all subsequent elements in the subpath.
static bool checkAnomaly = qEnvironmentVariableIntValue("QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
+ const bool pathHasFillOnRight = testHint(PathFillOnRight);
Element::CurvatureFlags flags = Element::CurvatureUndetermined;
for (QQuadPath::Element &element : m_elements) {
Q_ASSERT(element.childCount() == 0);
if (element.isSubpathStart()) {
- flags = coordinateOrderOfElement(element);
+ if (pathHasFillOnRight && !checkAnomaly)
+ flags = Element::FillOnRight;
+ else
+ flags = coordinateOrderOfElement(element);
} else if (checkAnomaly) {
Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
if (flags != newFlags) {
@@ -676,6 +689,8 @@ QQuadPath QQuadPath::flattened() const
QQuadPath res;
res.reserve(elementCountRecursive());
iterateElements([&](const QQuadPath::Element &element) { res.m_elements.append(element); });
+ res.setPathHints(pathHints());
+ res.setFillRule(fillRule());
return res;
}
@@ -843,6 +858,8 @@ QQuadPath QQuadPath::dashed(qreal lineWidth, const QList<qreal> &dashPattern, qr
}
}
}
+ res.setFillRule(fillRule());
+ res.setPathHints(pathHints());
return res;
}
diff --git a/src/quick/scenegraph/util/qquadpath_p.h b/src/quick/scenegraph/util/qquadpath_p.h
index eb27e7b4fe..82c571eff5 100644
--- a/src/quick/scenegraph/util/qquadpath_p.h
+++ b/src/quick/scenegraph/util/qquadpath_p.h
@@ -27,6 +27,18 @@ QT_BEGIN_NAMESPACE
class Q_QUICK_EXPORT QQuadPath
{
public:
+ // This is a copy of the flags in QQuickShapePath ### TODO: use a common definition
+ enum PathHint : quint8 {
+ PathLinear = 0x1,
+ PathQuadratic = 0x2,
+ PathConvex = 0x4,
+ PathFillOnRight = 0x8,
+ PathSolid = 0x10,
+ PathNonIntersecting = 0x20,
+ PathNonOverlappingControlPointTriangles = 0x40
+ };
+ Q_DECLARE_FLAGS(PathHints, PathHint)
+
class Element
{
public:
@@ -168,7 +180,7 @@ public:
quint8 m_isSubpathEnd : 1;
quint8 m_isLine : 1;
friend class QQuadPath;
- friend QDebug operator<<(QDebug, const QQuadPath::Element &);
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
};
void moveTo(const QVector2D &to)
@@ -212,7 +224,7 @@ public:
bool isEmpty() const { return m_elements.size() == 0; }
int elementCountRecursive() const;
- static QQuadPath fromPainterPath(const QPainterPath &path);
+ static QQuadPath fromPainterPath(const QPainterPath &path, PathHints hints = {});
QPainterPath toPainterPath() const;
QString asSvgString() const;
@@ -277,11 +289,33 @@ public:
static bool isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
static bool isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep);
+ bool testHint(PathHint hint) const
+ {
+ return m_hints.testFlag(hint);
+ }
+
+ void setHint(PathHint hint, bool on = true)
+ {
+ m_hints.setFlag(hint, on);
+ }
+
+ PathHints pathHints() const
+ {
+ return m_hints;
+ }
+
+ void setPathHints(PathHints newHints)
+ {
+ m_hints = newHints;
+ }
+
private:
void addElement(const QVector2D &control, const QVector2D &to, bool isLine = false);
Element::CurvatureFlags coordinateOrderOfElement(const Element &element) const;
- friend QDebug operator<<(QDebug, const QQuadPath &);
+ friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
+
+ PathHints m_hints;
bool subPathToStart = true;
Qt::FillRule m_fillRule = Qt::OddEvenFill;
QVector2D currentPoint;
@@ -289,8 +323,10 @@ private:
QList<Element> m_childElements;
};
-QDebug operator<<(QDebug, const QQuadPath::Element &);
-QDebug operator<<(QDebug, const QQuadPath &);
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuadPath::PathHints);
+
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath::Element &);
+Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &);
QT_END_NAMESPACE
diff --git a/src/quickshapes/qquickshapecurverenderer.cpp b/src/quickshapes/qquickshapecurverenderer.cpp
index df24d6df9a..93ccca4596 100644
--- a/src/quickshapes/qquickshapecurverenderer.cpp
+++ b/src/quickshapes/qquickshapecurverenderer.cpp
@@ -148,6 +148,9 @@ void QQuickShapeCurveRenderer::setPath(int index, const QQuickPath *path)
auto &pathData = m_paths[index];
pathData.originalPath = path->path();
pathData.m_dirty |= PathDirty;
+ const auto *shapePath = qobject_cast<const QQuickShapePath *>(path);
+ if (shapePath)
+ pathData.pathHints = shapePath->pathHints();
}
void QQuickShapeCurveRenderer::setStrokeColor(int index, const QColor &color)
@@ -419,14 +422,16 @@ void QQuickShapeCurveRenderer::processPath(PathData *pathData)
static const bool doIntersetionSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
static const bool useTriangulatingStroker = qEnvironmentVariableIntValue("QT_QUICKSHAPES_TRIANGULATING_STROKER");
static const bool simplifyPath = qEnvironmentVariableIntValue("QT_QUICKSHAPES_SIMPLIFY_PATHS");
+ static const QSGCurveProcessor::OverlapSolveMode overlapMode = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_CONCAVE_JOINT")
+ ? QSGCurveProcessor::FullOverlapSolve : QSGCurveProcessor::SkipConcaveJoinsSolve;
int &dirtyFlags = pathData->m_dirty;
if (dirtyFlags & PathDirty) {
if (simplifyPath)
- pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified());
+ pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified(), QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
else
- pathData->path = QQuadPath::fromPainterPath(pathData->originalPath);
+ pathData->path = QQuadPath::fromPainterPath(pathData->originalPath, QQuadPath::PathHints(int(pathData->pathHints)));
pathData->path.setFillRule(pathData->fillRule);
pathData->fillPath = {};
dirtyFlags |= (FillDirty | StrokeDirty);
@@ -440,7 +445,7 @@ void QQuickShapeCurveRenderer::processPath(PathData *pathData)
QSGCurveProcessor::solveIntersections(pathData->fillPath);
pathData->fillPath.addCurvatureData();
if (doOverlapSolving)
- QSGCurveProcessor::solveOverlaps(pathData->fillPath);
+ QSGCurveProcessor::solveOverlaps(pathData->fillPath, overlapMode);
}
pathData->fillNodes = addFillNodes(*pathData);
dirtyFlags |= StrokeDirty;
diff --git a/src/quickshapes/qquickshapecurverenderer_p.h b/src/quickshapes/qquickshapecurverenderer_p.h
index 4b192a9218..f46521d4f7 100644
--- a/src/quickshapes/qquickshapecurverenderer_p.h
+++ b/src/quickshapes/qquickshapecurverenderer_p.h
@@ -98,6 +98,7 @@ private:
QPen pen;
bool validPenWidth = true;
int m_dirty = 0;
+ QQuickShapePath::PathHints pathHints;
QPainterPath originalPath;
QQuadPath path;