aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgcurveprocessor.cpp
Commit message (Collapse)AuthorAgeFilesLines
* Add ShapePath.cosmeticStrokeShawn Rutledge2025-09-121-30/+316
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | QSvgRenderer already obeys the QPen::isCosmetic() setting. Now we add a ShapePath.cosmeticStroke property and try to support cosmetic stroking both with SVG and with Shapes, in all renderers. The curve renderer now starts with skinny triangles: each segment's end vertices are passed to the vertex shader with their original positions, and the shader uses the normal and the stroke width to expand the stroke triangles outwards as needed. We always add triangles for end caps, regardless whether they are square or round. We get rid of addBevelTriangle and fix bevels, square caps and miters: All three of these cases are done by drawing lines adjusted to the right direction within the respective triangles. And to avoid seeing rounded ends at any reasonable zoom level, we need the line equation coefficients to take the line very far outside the actual triangles. Square caps are really square: we render line segments in those three triangles, not extensions of the adjacent curve or line. Miters are also rendered as straight tangent lines. The bevel's triangle is very short when the join is an acute angle, and almost as tall as the full stroke width when the join is very obtuse. But when the triangle is short, we need to diminish the stroke width rendered in the fragment shader so that the center of the stroke falls on the inner corner of that triangle, and the edge of the stroke is rendered along the outer bevel edge rather than trying to go outside. That's achieved by multiplying the stroke width by the cosine of half the total angle, AKA the dot product. That is now in the normalExt.z vertex attribute. Normals (normalExt.xy) can be premultiplied rather than normalized: in fact some of them already have length > 1. In qsgbatchrenderer, Renderer::prepareAlphaBatches() breaks batches when overlaps occur. Now that we stroke lines with vertices that represent them as zero-width lines (and thus Element.bounds has the same x or y coordinates on both corners), we must consider lines right on top of each other to be overlapping: e.g. the stacks of horizontal (dashed) line segments in paint-stroke-202-t.svg must be in separate batches. At the time QQuickShape::updatePaintNode() is called, the available transform node (from UpdatePaintNodeData or an individual node's parent which is a transform node) does not contain the scaling factor that we need to allow for the stroke width to be adjusted for cosmetic stroking. But in QQuickShapePrivate::sync(), windowToItemTransform() is known, and from bde55ad574ac84440e2cdc9c1122a344bb1cb67a we have a precedent in QSGCurveStrokeMaterialShader::updateUniformData() for using the square root of the matrix determinant as a scaling approximation (ok when the scaling is uniform). QQuickShapeSoftwareRenderer::setNode() was already adjusting a path's bounding rect by its stroke width, and we need a multiplicative factor there to account for cosmetic stroking, to avoid excessive clipping in the software renderer. So now we have another use for the triangulationScale that was introduced in bcfcaeb87be783d8c329b0bc96323f1c9863982d. When QQShapeGenericRenderer is used (rendererType == GeometryRenderer), and any ShapePath has cosmeticStroke, we need it to re-triangulate whenever scale changes. QQuickShapeGenericRenderer::triangulateStroke() calls QTriangulatingStroker::setInvScale(1 / triangulationScale), and QTriangulatingStroker::process() multiplies its m_width by the inverse scale that was set (since 2009). So this tells us that the intended meaning of triangulationScale is the inverse of the factor by which we multiply the pen width. And when QQShapeGenericRenderer is in use, and there are cosmetic strokes, QQuickShape::itemChange triggers re-triangulation on changes in scale. When setting the QQuickShapeCurveRenderer::DebugWireframe debug visualization flag, we need to repeat the vertex shader calculations to expand the "skinny" triangles according to stroke width, just as we do with the actual stroking vertices. For now customTriangulator2 remains as legacy code, to be removed later on. It's poorly named, and returns a list of TriangleData which need to be iterated afterwards ("fix it in post"), looking up the QQuadPath::Element again in that second loop, which can go wrong when a path contains a move command. (For example, it could calculate a bevel between the end-tangent of one subpath and the start-tangent of the next.) customTriangulator2() was called from only one place, processStroke(), to which addStrokeTriangleCallback() is given: so the new way is to just call the callback directly as soon as we've calculated each triangle. Because we are not iterating again afterwards, the switch(type) is not needed in that case, and we no longer need TriangleData::type, except for supporting customTriangulator2(). [ChangeLog][QtQuick][Shapes] ShapePath now has a cosmeticStroke property which causes strokeWidth to be constant despite scaling. Set the environment variable QT_QUICKSHAPES_STROKE_EXPANDING to 1 to enable an experimental method of expanding strokes in the vertex shader, minimizing the need to re-triangulate when strokeWidth changes. Task-number: QTBUG-124638 Change-Id: I4eac0ddcd6f623b79bc70c766ff116f4b77736cb Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Add debug streaming operator to TriangleDataShawn Rutledge2025-09-121-0/+25
| | | | | | | | Also ifndef QT_NO_DEBUG_STREAM around the QQuadPath debug op. Both will be omitted from the build if QT_NO_DEBUG_STREAM is set. Change-Id: Ie86577ba61fc4f2b118d7e0a2b1ab702b318a473 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Cleanup, refactoring and internal docs in curve processor and rendererShawn Rutledge2025-09-101-104/+109
| | | | | | | | | | | | | | | | | | | - calculateJoin() is too big to continue as an internal lambda, and changes to customTriangulator2 (the only user) are coming later; so move it out to a top-level static function - use const - capture simple constants in the tooLong lambda - explicit captures in remaining lambdas - replace some single-letter variables with more meaningful ones - turn some plain comments into internal function docs - a couple of spelling and grammar corrections - init TriangleData::pathElementIndex=INT_MIN by default, to be able to distinguish default-constructed instances. Of course they are usually >= 0, but customTriangulator2() uses small negative indices as special triangle-type indicators. Change-Id: I7b70a02ac56522ee0c6aff26be80ac4e3e546bbd Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Port away from QPairZhao Yuhang2025-04-121-9/+9
| | | | | | | | QPair is just an alias of std::pair anyway. Task-number: QTBUG-115841 Change-Id: I26fc90adcc775aac9955ad57304af914dc4ed48f Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
* Use std::array instead of C-style arrayMatthias Rauter2025-03-281-2/+2
| | | | | | | | | | Change the C-style array to std::array. Initializing C-style arrays from temporaries seems suspicious. This issue was flagged by Clang-Tidy as a warning. Pick-to: 6.9 6.8 Change-Id: I7f9e5aefcda802ee0b45b39634175a634976b4a9 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* QSGCurveProcessor: Replace bool isLine with TriangleFlagsShawn Rutledge2024-12-051-6/+8
| | | | | | | | We will soon need more flags to deal with cosmetic stroking. Passing a bool was predictably limiting. Change-Id: I3f1a2e27b4cb75fd3b974e4f24604c5284809251 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Tweak curve renderer codeKaj Grönholm2024-07-151-3/+3
| | | | | | | | | Minor code improvements, mostly removing unnecessary code. Task-number: QTBUG-127121 Pick-to: 6.8 Change-Id: I57732969c06c441c3e1296dc7618e50f279e5bd1 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* QtQuick: Straighten out some logging categoriesUlf Hermann2024-06-191-1/+1
| | | | | | | | | | | | Either make them static or declare them in a header. We want them to be static wherever possible, in order to reduce the number of visible symbols. If they can't be static, however, they should at least be declared in only one place. Task-number: QTBUG-67692 Change-Id: I485bb7e4379e86f72619f848399ad58c76586851 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
* Be more careful in intersection solver at start/end of segment 1Matthias Rauter2024-06-051-1/+1
| | | | | | | | | | | | | | At ordinary intersections we use the intersection angle to decide on the next step forward. This does not work for intersections at the start and end points and we are falling back to finding inside/outside numerically. However, this was only done for one of the segments, leading to rendering bugs if it happened on the other segment. This patch also applies the numerical method to the second segment. Pick-to: 6.7 Fixes: QTBUG-125864 Change-Id: I490acb74ea6215efa1986c7c6bfbcdd8c3172120 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Curverenderer: Cleanups & optimizations of intersection solvingEirik Aavitsland2024-04-111-33/+44
| | | | | | | | | | | Avoiding repeated calculations, allocations etc. No behavior change. Also replace the remaining qWarnings ("this should not happen") with qCDebugs(), since they are not intended for users. Pick-to: 6.7 Change-Id: I917fc61cba7281d68c9a8c22622a70f2003adf6f Reviewed-by: Matthias Rauter <matthias.rauter@qt.io>
* Curve renderer: Use a common QQuadPath iterator functionEirik Aavitsland2024-04-031-51/+34
| | | | | | | | | | | | For code simplification, replace a couple of ad-hoc QQuadPath iteration implementations with calls to QQuadPath's own iterateElements() function. To make that possible, extend iterateElements() to pass the element index too to the lambda function. Pick-to: 6.7 Change-Id: I2e6697b5d1404c7ed4fa1c247e18f93856394fa1 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Add optimizations and simplifications for curve renderer fillEirik Aavitsland2024-02-241-35/+34
| | | | | | | | | | | | | | | | | | | In addFillNodes(): - Pre-allocate estimated needed number of elements in temporary lists, to avoid reallocations. In processFill(): - Remove QHashes that were never read - Fewer conversions QVector2D<->QPointF (float<->double) - Add a qHash for QVector2D. Simplifies by removing need for conversion to qpair<float, float>, and is also more efficient since it hashes both elements at once. Pick-to: 6.7 Change-Id: If11c67ee198d9d29a9d290efe0e808fb7c70ee9c Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* curve renderer: Fix crash when splitting elementsEskil Abrahamsen Blomfeldt2024-02-081-2/+3
| | | | | | | | | | We would take a reference to the element and then modify the path, which would cause us to read uninitialized memory and later assert. Pick-to: 6.7 Change-Id: Ib8f3e289dea44fdd6f51649de776b7fba63c62d9 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Remove nested paths during intersection solvingMatthias Rauter2024-02-071-8/+142
| | | | | | | | | | | | | | | | | | | Nested paths that were neither generating a new hole or a new filling resulted in double filling of some areas. The previous workaround was to force the intersectionSolver to generate a new path, however, which is computational expensive and does not allow to check for changes. This patch introduces a dedicated function to remove superfluos nested subpaths. It should be faster than the previous solution and allows to track changes to the path. With this patch, the intersectionSolver returns true only if the path was changed. Pick-to: 6.7 Change-Id: I16919f20b4e5460ddd729e8be7703fb2879c3190 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Return from intersection solver early if path is emptyMatthias Rauter2024-02-041-0/+4
| | | | | | | | This also removes the warning that no valid starting point was found. Pick-to: 6.7 Change-Id: I7cc46bd0bfb57b869f3bc2e80ecba3e6d6ae1bc7 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Fix a bug in intersection solvingMatthias Rauter2024-01-311-65/+97
| | | | | | | | | | | | When we hit an intersection, we use the angle between the segments to decide in which direction to continue. This concept falls apart when we intersect a path at the join between two segments. For this case we have to fall back to check the fill side. This is more expensive and thus not done for every intersection. Pick-to: 6.7 Change-Id: Iebefc84fe3d299e5c31817ba34b3a1cdcb1e7d68 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Fix bug in intersection solver, triggered by SimpleShape.qmlMatthias Rauter2024-01-311-4/+11
| | | | | | | | | | | In order to fix the example, the isOverlap() function had to be removed from the findOverlapCandidates() function. The example that triggered the bug is added to the baseline testsuit. Pick-to: 6.7 Change-Id: Ibcbf7d1200a284917f6ad87114df29eafbe6aa5b Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Set non-intersecting flag if path has no intersectionsPaul Olav Tvete2024-01-261-0/+1
| | | | | | | | | | If the path has no intersections to start with, then solveIntersections will return the path unchanged. Set the PathNonIntersecting optimization flag in that case. Pick-to: 6.7 Change-Id: I6f560c97c8722b2940d69acc0c6cfd545f86e7f2 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Curverenderer: Simplify the workaround for 3 element pathsEirik Aavitsland2024-01-241-0/+3
| | | | | | | | | | For a single triangle inner hull, qTriangulate() will sometimes give an unexpected result in the index list. Fix by overwriting it with the expected result, since that is static and known for this simple case. Pick-to: 6.7 Change-Id: I76d697f85f233b0c2ecc50da3074def89e860697 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Curve renderer: Optimize overlap handlingPaul Olav Tvete2024-01-231-85/+8
| | | | | | | | | | | | | | | | Use QSGCurveProcessor::findOverlappingCandidates() to reduce the complexity from O(n^2) to O(n log n). The new code now supports the concave join case by default. This means that the QT_QUICKSHAPES_WIP_CONCAVE_JOINT environment variable no longer serves a purpose, so it is removed. Task-number: QTBUG-121215 Fixes: QTBUG-114807 Pick-to: 6.7 Change-Id: I080ef08cb5c7e581abfbb1e66d47bdd48f3019c5 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Preprocess paths in svgtoqmlPaul Olav Tvete2024-01-231-14/+24
| | | | | | | | | | | | Add --optimize-paths option to do expensive path manipulation at build time instead of at runtime. This may cause the generation of separate fill and stroke versions of the same source path. Task-number: QTBUG-116883 Task-number: QTBUG-121203 Pick-to: 6.7 Change-Id: Iacda16d8dbddf5b8219c290fac473d78c073576e Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
* Curve renderer: Handle ambiguous pointsPaul Olav Tvete2024-01-181-50/+69
| | | | | | | | | | | | A triangle could be mis-assigned to the wrong path element if a point could be found in multiple sub-paths. This change uses a QMultiHash instead of a QHash to look up the points from the triangulator, and chooses the path element that has the triangle on the fill side. Pick-to: 6.7 Change-Id: Ic2773da5c7b4a64386e20c19745ce36e2dcb2c57 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Fix intersections that coincide with a vertexMatthias Rauter2024-01-181-17/+35
| | | | | | | | | This issue was found in fonts where intersection match a vertex of the path. With this change we take care of such points properly. Pick-to: 6.7 Change-Id: Ibe2f0334e748351066e324f8bb10774fc505e832 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
* Use shape hints in the curve rendererPaul Olav Tvete2024-01-171-2/+10
| | | | | | | | | | 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>
* Return status of solveIntersectionsMatthias Rauter2024-01-171-6/+7
| | | | | | Pick-to: 6.7 Change-Id: I7fc0087c4f9219ac6834008e2c49438f81aae3d9 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Detect and remove self intersections of QQuadPathMatthias Rauter2024-01-071-10/+630
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Currently the CurveRenderer is not working when the path is self-intersecting. With this patch, the self-intersections are removed before the path is used for filling (optionally, default: on) The stroking path is untouched. The function findOverlappingCandidates finds candidates of elements that might be intersecting. Its complexity is O(n log n) and can also be used in other parts of the code where overlapping bounding triangles need to be identified. The function solveIntersections removes all intersections from a QQuadPath. If intersections are solved, the path is oriented such that the filling is on the right side of the path. If no intersections are found, the path is returned without any changes. The optional argument alwaysReorder can be used to force a reordering of the paths, such that the filling of the shape is always on the right side of the path. Intersections are found with Newtons algorithm with 9 different starting values. This is reliable in finding all intersections but the starting values could be improved/reduced to improve performance. Pick-to: 6.7 Change-Id: I088e4edfff755155521ed91114bc67f63c6e546a Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Curve renderer: drop the control point magic for line elementsEirik Aavitsland2023-12-081-4/+8
| | | | | | | | | | | | | | | | | Now that we are adding ever more manipulations of quadpaths, relying on this silent state to not having been lost on the way is bugprone. Also, being instead able to rely on the control point of a line always being the line's midpoint, so that the element is a valid representation even if treated as quad curve, makes for more resilient code: it can make it optional whether to treat lines specially. So replace the magic value by a new Element method referencePoint(), that returns the control point for curves and the inside point for lines, and use that in the one place that used to rely on the special line control point. Change-Id: Id54b9034d23d7bc7794f2f77fab36fbfe137a60e Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
* Introduce a CurveRendering backend for textEskil Abrahamsen Blomfeldt2023-11-111-0/+1109
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>