Convert GrTriangulator to a class

This simplifies the argument lists for functions and will also allow us
to extract the AA logic into its own subclass.

Bug: skia:10419
Change-Id: If51e86a7633da7a3ee9352c0236258a0a21f2ebe
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/347976
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
This commit is contained in:
Chris Dalton 2021-01-05 13:37:44 -07:00 committed by Skia Commit-Bot
parent 736c992966
commit 57ea1fcb74
3 changed files with 386 additions and 338 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,55 +8,174 @@
#ifndef GrTriangulator_DEFINED
#define GrTriangulator_DEFINED
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/private/SkColorData.h"
#include "src/core/SkArenaAlloc.h"
#include "src/gpu/GrColor.h"
class GrEagerVertexAllocator;
class SkPath;
struct SkRect;
#define TRIANGULATOR_WIREFRAME 0
/**
* Provides utility functions for converting paths to a collection of triangles.
*/
class GrTriangulator {
public:
enum class Mode {
kNormal,
#define TRIANGULATOR_WIREFRAME 0
// Surround path edges with coverage ramps for antialiasing.
kEdgeAntialias,
namespace GrTriangulator {
// Triangulate only each contour's inner polygon. The inner polygons connect the endpoints
// of each verb. (i.e., they are the path that would result from collapsing all curves to
// single lines.)
//
// If the inner polygons are not simple (e.g., self intersection, double winding), then the
// tessellator aborts and returns 0.
kSimpleInnerPolygons
};
struct WindingVertex {
SkPoint fPos;
int fWinding;
};
constexpr static size_t GetVertexStride(Mode mode) {
return sizeof(SkPoint) + ((Mode::kEdgeAntialias == mode) ? sizeof(float) : 0);
}
// Triangulates a path to an array of vertices. Each triangle is represented as a set of three
// WindingVertex entries, each of which contains the position and winding count (which is the same
// for all three vertices of a triangle). The 'verts' out parameter is set to point to the resultant
// vertex array. CALLER IS RESPONSIBLE for deleting this buffer to avoid a memory leak!
int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
WindingVertex** verts);
static int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
GrEagerVertexAllocator*, Mode, bool *isLinear);
enum class Mode {
kNormal,
struct WindingVertex {
SkPoint fPos;
int fWinding;
};
// Surround path edges with coverage ramps for antialiasing.
kEdgeAntialias,
// Triangulate only each contour's inner polygon. The inner polygons connect the endpoints of
// each verb. (i.e., they are the path that would result from collapsing all curves to single
// lines.)
// *DEPRECATED*: Once CCPR is removed this method will go away.
//
// If the inner polygons are not simple (e.g., self intersection, double winding), then the
// tessellator aborts and returns 0.
kSimpleInnerPolygons
// Triangulates a path to an array of vertices. Each triangle is represented as a set of three
// WindingVertex entries, each of which contains the position and winding count (which is the
// same for all three vertices of a triangle). The 'verts' out parameter is set to point to the
// resultant vertex array. CALLER IS RESPONSIBLE for deleting this buffer to avoid a memory
// leak!
static int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
WindingVertex** verts);
// Structs used by GrTriangulator internals.
struct Vertex;
struct VertexList;
struct Edge;
struct EdgeList;
struct Poly;
struct Comparator;
private:
GrTriangulator(const SkPath& path, Mode mode) : fPath(path), fMode(mode) {}
// There are six stages to the basic algorithm:
//
// 1) Linearize the path contours into piecewise linear segments:
void pathToContours(float tolerance, const SkRect& clipBounds, VertexList* contours);
// 2) Build a mesh of edges connecting the vertices:
void contoursToMesh(VertexList* contours, int contourCnt, VertexList* mesh, Comparator&);
// 3) Sort the vertices in Y (and secondarily in X) (merge_sort()).
static void SortMesh(VertexList* vertices, const Comparator&);
// 4) Simplify the mesh by inserting new vertices at intersecting edges:
enum class SimplifyResult {
kAlreadySimple,
kFoundSelfIntersection,
kAbort
};
SimplifyResult simplify(VertexList* mesh, Comparator&);
// 5) Tessellate the simplified mesh into monotone polygons:
Poly* tessellate(const VertexList& vertices);
// 6) Triangulate the monotone polygons directly into a vertex buffer:
void* polysToTriangles(Poly* polys, void* data, SkPathFillType overrideFillType);
// For screenspace antialiasing, the algorithm is modified as follows:
//
// Run steps 1-5 above to produce polygons.
// 5b) Apply fill rules to extract boundary contours from the polygons (extract_boundaries()).
// 5c) Simplify boundaries to remove "pointy" vertices that cause inversions
// (simplify_boundary()).
// 5d) Displace edges by half a pixel inward and outward along their normals. Intersect to find
// new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a
// new antialiased mesh from those vertices (stroke_boundary()).
// Run steps 3-6 above on the new mesh, and produce antialiased triangles.
//
// The vertex sorting in step (3) is a merge sort, since it plays well with the linked list
// of vertices (and the necessity of inserting new vertices on intersection).
//
// Stages (4) and (5) use an active edge list -- a list of all edges for which the
// sweep line has crossed the top vertex, but not the bottom vertex. It's sorted
// left-to-right based on the point where both edges are active (when both top vertices
// have been seen, so the "lower" top vertex of the two). If the top vertices are equal
// (shared), it's sorted based on the last point where both edges are active, so the
// "upper" bottom vertex.
//
// The most complex step is the simplification (4). It's based on the Bentley-Ottman
// line-sweep algorithm, but due to floating point inaccuracy, the intersection points are
// not exact and may violate the mesh topology or active edge list ordering. We
// accommodate this by adjusting the topology of the mesh and AEL to match the intersection
// points. This occurs in two ways:
//
// A) Intersections may cause a shortened edge to no longer be ordered with respect to its
// neighbouring edges at the top or bottom vertex. This is handled by merging the
// edges (merge_collinear_edges()).
// B) Intersections may cause an edge to violate the left-to-right ordering of the
// active edge list. This is handled by detecting potential violations and rewinding
// the active edge list to the vertex before they occur (rewind() during merging,
// rewind_if_necessary() during splitting).
//
// The tessellation steps (5) and (6) are based on "Triangulating Simple Polygons and
// Equivalent Problems" (Fournier and Montuno); also a line-sweep algorithm. Note that it
// currently uses a linked list for the active edge list, rather than a 2-3 tree as the
// paper describes. The 2-3 tree gives O(lg N) lookups, but insertion and removal also
// become O(lg N). In all the test cases, it was found that the cost of frequent O(lg N)
// insertions and removals was greater than the cost of infrequent O(N) lookups with the
// linked list implementation. With the latter, all removals are O(1), and most insertions
// are O(1), since we know the adjacent edge in the active edge list based on the topology.
// Only type 2 vertices (see paper) require the O(N) lookups, and these are much less
// frequent. There may be other data structures worth investigating, however.
//
// Note that the orientation of the line sweep algorithms is determined by the aspect ratio of
// the path bounds. When the path is taller than it is wide, we sort vertices based on
// increasing Y coordinate, and secondarily by increasing X coordinate. When the path is wider
// than it is tall, we sort by increasing X coordinate, but secondarily by *decreasing* Y
// coordinate. This is so that the "left" and "right" orientation in the code remains correct
// (edges to the left are increasing in Y; edges to the right are decreasing in Y). That is, the
// setting rotates 90 degrees counterclockwise, rather that transposing.
// Additional helpers and driver functions.
void appendPointToContour(const SkPoint& p, VertexList* contour);
void appendQuadraticToContour(const SkPoint[3], SkScalar toleranceSqd, VertexList* contour);
void generateCubicPoints(const SkPoint&, const SkPoint&, const SkPoint&, const SkPoint&,
SkScalar tolSqd, VertexList* contour, int pointsLeft);
bool splitEdge(Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, Comparator&);
bool intersectEdgePair(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current,
Comparator&);
bool checkForIntersection(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current,
VertexList* mesh, Comparator&);
void sanitizeContours(VertexList* contours, int contourCnt);
bool mergeCoincidentVertices(VertexList* mesh, Comparator&);
void buildEdges(VertexList* contours, int contourCnt, VertexList* mesh, Comparator&);
Poly* contoursToPolys(VertexList* contours, int contourCnt, VertexList* outerMesh);
Poly* pathToPolys(float tolerance, const SkRect& clipBounds, int contourCnt,
VertexList* outerMesh);
int pathToTriangles(float tolerance, const SkRect& clipBounds, GrEagerVertexAllocator*,
SkPathFillType overrideFillType);
constexpr static int kArenaChunkSize = 16 * 1024;
SkArenaAlloc fAlloc{kArenaChunkSize};
const SkPath fPath;
const Mode fMode;
bool fIsLinear = false;
};
constexpr size_t GetVertexStride(Mode mode) {
return sizeof(SkPoint) + ((Mode::kEdgeAntialias == mode) ? sizeof(float) : 0);
}
int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
GrEagerVertexAllocator*, Mode, bool *isLinear);
} // namespace GrTriangulator
#endif

View File

@ -172,7 +172,7 @@ bool GrPathTessellateOp::prePrepareInnerPolygonTriangulation(const PrePrepareArg
SkASSERT(!fStencilTrianglesProgram);
SkASSERT(!fFillTrianglesProgram);
using GrTriangulator::Mode;
using Mode = GrTriangulator::Mode;
fTriangleVertexCount = GrTriangulator::PathToTriangles(fPath, 0, SkRect::MakeEmpty(),
args.fInnerTriangleAllocator,