• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef GrTriangulator_DEFINED
9 #define GrTriangulator_DEFINED
10 
11 #if !defined(SK_ENABLE_OPTIMIZE_SIZE)
12 
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkScalar.h"
16 #include "include/private/base/SkAssert.h"
17 #include "src/base/SkArenaAlloc.h"
18 #include "src/gpu/BufferWriter.h"
19 
20 #include <algorithm>
21 #include <cmath>
22 #include <cstdint>
23 #include <tuple>
24 
25 class GrEagerVertexAllocator;
26 enum class SkPathFillType;
27 struct SkRect;
28 
29 #define TRIANGULATOR_LOGGING 0
30 #define TRIANGULATOR_WIREFRAME 0
31 
32 /**
33  * Provides utility functions for converting paths to a collection of triangles.
34  */
35 class GrTriangulator {
36 public:
37     constexpr static int kArenaDefaultChunkSize = 16 * 1024;
38 
PathToTriangles(const SkPath & path,SkScalar tolerance,const SkRect & clipBounds,GrEagerVertexAllocator * vertexAllocator,bool * isLinear)39     static int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
40                                GrEagerVertexAllocator* vertexAllocator, bool* isLinear) {
41         if (!path.isFinite()) {
42             return 0;
43         }
44         SkArenaAlloc alloc(kArenaDefaultChunkSize);
45         GrTriangulator triangulator(path, &alloc);
46         auto [ polys, success ] = triangulator.pathToPolys(tolerance, clipBounds, isLinear);
47         if (!success) {
48             return 0;
49         }
50         int count = triangulator.polysToTriangles(polys, vertexAllocator);
51         return count;
52     }
53 
54     // Enums used by GrTriangulator internals.
55     enum class Side { kLeft, kRight };
56     enum class EdgeType { kInner, kOuter, kConnector };
57 
58     // Structs used by GrTriangulator internals.
59     struct Vertex;
60     struct VertexList;
61     struct Line;
62     struct Edge;
63     struct EdgeList;
64     struct MonotonePoly;
65     struct Poly;
66     struct Comparator;
67 
68 protected:
GrTriangulator(const SkPath & path,SkArenaAlloc * alloc)69     GrTriangulator(const SkPath& path, SkArenaAlloc* alloc) : fPath(path), fAlloc(alloc) {}
~GrTriangulator()70     virtual ~GrTriangulator() {}
71 
72     // There are six stages to the basic algorithm:
73     //
74     // 1) Linearize the path contours into piecewise linear segments:
75     void pathToContours(float tolerance, const SkRect& clipBounds, VertexList* contours,
76                         bool* isLinear) const;
77 
78     // 2) Build a mesh of edges connecting the vertices:
79     void contoursToMesh(VertexList* contours, int contourCnt, VertexList* mesh,
80                         const Comparator&);
81 
82     // 3) Sort the vertices in Y (and secondarily in X):
83     static void SortedMerge(VertexList* front, VertexList* back, VertexList* result,
84                             const Comparator&);
85     static void SortMesh(VertexList* vertices, const Comparator&);
86 
87     // 4) Simplify the mesh by inserting new vertices at intersecting edges:
88     enum class SimplifyResult {
89         kFailed,
90         kAlreadySimple,
91         kFoundSelfIntersection
92     };
93 
94     enum class BoolFail {
95         kFalse,
96         kTrue,
97         kFail
98     };
99 
100     [[nodiscard]] SimplifyResult simplify(VertexList* mesh, const Comparator&);
101 
102     // 5) Tessellate the simplified mesh into monotone polygons:
103     virtual std::tuple<Poly*, bool> tessellate(const VertexList& vertices, const Comparator&);
104 
105     // 6) Triangulate the monotone polygons directly into a vertex buffer:
106     skgpu::VertexWriter polysToTriangles(Poly* polys,
107                                          SkPathFillType overrideFillType,
108                                          skgpu::VertexWriter data) const;
109 
110     // The vertex sorting in step (3) is a merge sort, since it plays well with the linked list
111     // of vertices (and the necessity of inserting new vertices on intersection).
112     //
113     // Stages (4) and (5) use an active edge list -- a list of all edges for which the
114     // sweep line has crossed the top vertex, but not the bottom vertex.  It's sorted
115     // left-to-right based on the point where both edges are active (when both top vertices
116     // have been seen, so the "lower" top vertex of the two). If the top vertices are equal
117     // (shared), it's sorted based on the last point where both edges are active, so the
118     // "upper" bottom vertex.
119     //
120     // The most complex step is the simplification (4). It's based on the Bentley-Ottman
121     // line-sweep algorithm, but due to floating point inaccuracy, the intersection points are
122     // not exact and may violate the mesh topology or active edge list ordering. We
123     // accommodate this by adjusting the topology of the mesh and AEL to match the intersection
124     // points. This occurs in two ways:
125     //
126     // A) Intersections may cause a shortened edge to no longer be ordered with respect to its
127     //    neighbouring edges at the top or bottom vertex. This is handled by merging the
128     //    edges (mergeCollinearVertices()).
129     // B) Intersections may cause an edge to violate the left-to-right ordering of the
130     //    active edge list. This is handled by detecting potential violations and rewinding
131     //    the active edge list to the vertex before they occur (rewind() during merging,
132     //    rewind_if_necessary() during splitting).
133     //
134     // The tessellation steps (5) and (6) are based on "Triangulating Simple Polygons and
135     // Equivalent Problems" (Fournier and Montuno); also a line-sweep algorithm. Note that it
136     // currently uses a linked list for the active edge list, rather than a 2-3 tree as the
137     // paper describes. The 2-3 tree gives O(lg N) lookups, but insertion and removal also
138     // become O(lg N). In all the test cases, it was found that the cost of frequent O(lg N)
139     // insertions and removals was greater than the cost of infrequent O(N) lookups with the
140     // linked list implementation. With the latter, all removals are O(1), and most insertions
141     // are O(1), since we know the adjacent edge in the active edge list based on the topology.
142     // Only type 2 vertices (see paper) require the O(N) lookups, and these are much less
143     // frequent. There may be other data structures worth investigating, however.
144     //
145     // Note that the orientation of the line sweep algorithms is determined by the aspect ratio of
146     // the path bounds. When the path is taller than it is wide, we sort vertices based on
147     // increasing Y coordinate, and secondarily by increasing X coordinate. When the path is wider
148     // than it is tall, we sort by increasing X coordinate, but secondarily by *decreasing* Y
149     // coordinate. This is so that the "left" and "right" orientation in the code remains correct
150     // (edges to the left are increasing in Y; edges to the right are decreasing in Y). That is, the
151     // setting rotates 90 degrees counterclockwise, rather that transposing.
152 
153     // Additional helpers and driver functions.
154     skgpu::VertexWriter emitMonotonePoly(const MonotonePoly*, skgpu::VertexWriter data) const;
155     skgpu::VertexWriter emitTriangle(Vertex* prev, Vertex* curr, Vertex* next, int winding,
156                                      skgpu::VertexWriter data) const;
157     skgpu::VertexWriter emitPoly(const Poly*, skgpu::VertexWriter data) const;
158 
159     Poly* makePoly(Poly** head, Vertex* v, int winding) const;
160     void appendPointToContour(const SkPoint& p, VertexList* contour) const;
161     void appendQuadraticToContour(const SkPoint[3], SkScalar toleranceSqd,
162                                   VertexList* contour) const;
163     void generateCubicPoints(const SkPoint&, const SkPoint&, const SkPoint&, const SkPoint&,
164                              SkScalar tolSqd, VertexList* contour, int pointsLeft) const;
165     bool applyFillType(int winding) const;
166     MonotonePoly* allocateMonotonePoly(Edge* edge, Side side, int winding);
167     Edge* allocateEdge(Vertex* top, Vertex* bottom, int winding, EdgeType type);
168     Edge* makeEdge(Vertex* prev, Vertex* next, EdgeType type, const Comparator&);
169     [[nodiscard]] bool setTop(
170         Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, const Comparator&) const;
171     [[nodiscard]] bool setBottom(
172         Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current, const Comparator&) const;
173     [[nodiscard]] bool mergeEdgesAbove(
174         Edge* edge, Edge* other, EdgeList* activeEdges, Vertex** current, const Comparator&) const;
175     [[nodiscard]] bool mergeEdgesBelow(
176         Edge* edge, Edge* other, EdgeList* activeEdges, Vertex** current, const Comparator&) const;
177     Edge* makeConnectingEdge(Vertex* prev, Vertex* next, EdgeType, const Comparator&,
178                              int windingScale = 1);
179     void mergeVertices(Vertex* src, Vertex* dst, VertexList* mesh, const Comparator&) const;
180     static void FindEnclosingEdges(const Vertex& v, const EdgeList& edges,
181                                    Edge** left, Edge** right);
182     bool mergeCollinearEdges(Edge* edge, EdgeList* activeEdges, Vertex** current,
183                              const Comparator&) const;
184     BoolFail splitEdge(Edge* edge, Vertex* v, EdgeList* activeEdges, Vertex** current,
185                        const Comparator&);
186     BoolFail intersectEdgePair(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current,
187                                const Comparator&);
188     Vertex* makeSortedVertex(const SkPoint&, uint8_t alpha, VertexList* mesh, Vertex* reference,
189                              const Comparator&) const;
190     void computeBisector(Edge* edge1, Edge* edge2, Vertex*) const;
191     BoolFail checkForIntersection(Edge* left, Edge* right, EdgeList* activeEdges, Vertex** current,
192                                   VertexList* mesh, const Comparator&);
193     void sanitizeContours(VertexList* contours, int contourCnt) const;
194     bool mergeCoincidentVertices(VertexList* mesh, const Comparator&) const;
195     void buildEdges(VertexList* contours, int contourCnt, VertexList* mesh,
196                     const Comparator&);
197     std::tuple<Poly*, bool> contoursToPolys(VertexList* contours, int contourCnt);
198     std::tuple<Poly*, bool> pathToPolys(float tolerance, const SkRect& clipBounds,
199                       bool* isLinear);
200     static int64_t CountPoints(Poly* polys, SkPathFillType overrideFillType);
201     int polysToTriangles(Poly*, GrEagerVertexAllocator*) const;
202 
203     // FIXME: fPath should be plumbed through function parameters instead.
204     const SkPath fPath;
205     SkArenaAlloc* const fAlloc;
206     int fNumMonotonePolys = 0;
207     int fNumEdges = 0;
208     // Track how deep of a stack we get from mergeCollinearEdges()
209     mutable int fMergeCollinearStackCount = 0;
210 
211     // Internal control knobs.
212     bool fRoundVerticesToQuarterPixel = false;
213     bool fEmitCoverage = false;
214     bool fPreserveCollinearVertices = false;
215     bool fCollectBreadcrumbTriangles = false;
216 
217     // The breadcrumb triangles serve as a glue that erases T-junctions between a path's outer
218     // curves and its inner polygon triangulation. Drawing a path's outer curves, breadcrumb
219     // triangles, and inner polygon triangulation all together into the stencil buffer has the same
220     // identical rasterized effect as stenciling a classic Redbook fan.
221     //
222     // The breadcrumb triangles track all the edge splits that led from the original inner polygon
223     // edges to the final triangulation. Every time an edge splits, we emit a razor-thin breadcrumb
224     // triangle consisting of the edge's original endpoints and the split point. (We also add
225     // supplemental breadcrumb triangles to areas where abs(winding) > 1.)
226     //
227     //                a
228     //               /
229     //              /
230     //             /
231     //            x  <- Edge splits at x. New breadcrumb triangle is: [a, b, x].
232     //           /
233     //          /
234     //         b
235     //
236     // The opposite-direction shared edges between the triangulation and breadcrumb triangles should
237     // all cancel out, leaving just the set of edges from the original polygon.
238     class BreadcrumbTriangleList {
239     public:
240         struct Node {
NodeNode241             Node(SkPoint a, SkPoint b, SkPoint c) : fPts{a, b, c} {}
242             SkPoint fPts[3];
243             Node* fNext = nullptr;
244         };
head()245         const Node* head() const { return fHead; }
count()246         int count() const { return fCount; }
247 
append(SkArenaAlloc * alloc,SkPoint a,SkPoint b,SkPoint c,int winding)248         void append(SkArenaAlloc* alloc, SkPoint a, SkPoint b, SkPoint c, int winding) {
249             if (a == b || a == c || b == c || winding == 0) {
250                 return;
251             }
252             if (winding < 0) {
253                 std::swap(a, b);
254                 winding = -winding;
255             }
256             for (int i = 0; i < winding; ++i) {
257                 SkASSERT(fTail && !(*fTail));
258                 *fTail = alloc->make<Node>(a, b, c);
259                 fTail = &(*fTail)->fNext;
260             }
261             fCount += winding;
262         }
263 
concat(BreadcrumbTriangleList && list)264         void concat(BreadcrumbTriangleList&& list) {
265             SkASSERT(fTail && !(*fTail));
266             if (list.fHead) {
267                 *fTail = list.fHead;
268                 fTail = list.fTail;
269                 fCount += list.fCount;
270                 list.fHead = nullptr;
271                 list.fTail = &list.fHead;
272                 list.fCount = 0;
273             }
274         }
275 
276     private:
277         Node* fHead = nullptr;
278         Node** fTail = &fHead;
279         int fCount = 0;
280     };
281 
282     mutable BreadcrumbTriangleList fBreadcrumbList;
283 };
284 
285 /**
286  * Vertices are used in three ways: first, the path contours are converted into a
287  * circularly-linked list of Vertices for each contour. After edge construction, the same Vertices
288  * are re-ordered by the merge sort according to the sweep_lt comparator (usually, increasing
289  * in Y) using the same fPrev/fNext pointers that were used for the contours, to avoid
290  * reallocation. Finally, MonotonePolys are built containing a circularly-linked list of
291  * Vertices. (Currently, those Vertices are newly-allocated for the MonotonePolys, since
292  * an individual Vertex from the path mesh may belong to multiple
293  * MonotonePolys, so the original Vertices cannot be re-used.
294  */
295 
296 struct GrTriangulator::Vertex {
VertexVertex297   Vertex(const SkPoint& point, uint8_t alpha)
298     : fPoint(point), fPrev(nullptr), fNext(nullptr)
299     , fFirstEdgeAbove(nullptr), fLastEdgeAbove(nullptr)
300     , fFirstEdgeBelow(nullptr), fLastEdgeBelow(nullptr)
301     , fLeftEnclosingEdge(nullptr), fRightEnclosingEdge(nullptr)
302     , fPartner(nullptr)
303     , fAlpha(alpha)
304     , fSynthetic(false)
305 #if TRIANGULATOR_LOGGING
306     , fID (-1.0f)
307 #endif
308     {}
309     SkPoint fPoint;               // Vertex position
310     Vertex* fPrev;                // Linked list of contours, then Y-sorted vertices.
311     Vertex* fNext;                // "
312     Edge*   fFirstEdgeAbove;      // Linked list of edges above this vertex.
313     Edge*   fLastEdgeAbove;       // "
314     Edge*   fFirstEdgeBelow;      // Linked list of edges below this vertex.
315     Edge*   fLastEdgeBelow;       // "
316     Edge*   fLeftEnclosingEdge;   // Nearest edge in the AEL left of this vertex.
317     Edge*   fRightEnclosingEdge;  // Nearest edge in the AEL right of this vertex.
318     Vertex* fPartner;             // Corresponding inner or outer vertex (for AA).
319     uint8_t fAlpha;
320     bool    fSynthetic;           // Is this a synthetic vertex?
321 #if TRIANGULATOR_LOGGING
322     float   fID;                  // Identifier used for logging.
323 #endif
isConnectedVertex324     bool isConnected() const { return this->fFirstEdgeAbove || this->fFirstEdgeBelow; }
325 };
326 
327 struct GrTriangulator::VertexList {
VertexListVertexList328     VertexList() : fHead(nullptr), fTail(nullptr) {}
VertexListVertexList329     VertexList(Vertex* head, Vertex* tail) : fHead(head), fTail(tail) {}
330     Vertex* fHead;
331     Vertex* fTail;
332     void insert(Vertex* v, Vertex* prev, Vertex* next);
appendVertexList333     void append(Vertex* v) { insert(v, fTail, nullptr); }
appendVertexList334     void append(const VertexList& list) {
335         if (!list.fHead) {
336             return;
337         }
338         if (fTail) {
339             fTail->fNext = list.fHead;
340             list.fHead->fPrev = fTail;
341         } else {
342             fHead = list.fHead;
343         }
344         fTail = list.fTail;
345     }
prependVertexList346     void prepend(Vertex* v) { insert(v, nullptr, fHead); }
347     void remove(Vertex* v);
closeVertexList348     void close() {
349         if (fHead && fTail) {
350             fTail->fNext = fHead;
351             fHead->fPrev = fTail;
352         }
353     }
354 #if TRIANGULATOR_LOGGING
355     void dump() const;
356 #endif
357 };
358 
359 // A line equation in implicit form. fA * x + fB * y + fC = 0, for all points (x, y) on the line.
360 struct GrTriangulator::Line {
LineLine361     Line(double a, double b, double c) : fA(a), fB(b), fC(c) {}
LineLine362     Line(Vertex* p, Vertex* q) : Line(p->fPoint, q->fPoint) {}
LineLine363     Line(const SkPoint& p, const SkPoint& q)
364         : fA(static_cast<double>(q.fY) - p.fY)      // a = dY
365         , fB(static_cast<double>(p.fX) - q.fX)      // b = -dX
366         , fC(static_cast<double>(p.fY) * q.fX -     // c = cross(q, p)
367              static_cast<double>(p.fX) * q.fY) {}
distLine368     double dist(const SkPoint& p) const { return fA * p.fX + fB * p.fY + fC; }
369     Line operator*(double v) const { return Line(fA * v, fB * v, fC * v); }
magSqLine370     double magSq() const { return fA * fA + fB * fB; }
normalizeLine371     void normalize() {
372         double len = sqrt(this->magSq());
373         if (len == 0.0) {
374             return;
375         }
376         double scale = 1.0f / len;
377         fA *= scale;
378         fB *= scale;
379         fC *= scale;
380     }
nearParallelLine381     bool nearParallel(const Line& o) const {
382         return fabs(o.fA - fA) < 0.00001 && fabs(o.fB - fB) < 0.00001;
383     }
384 
385     // Compute the intersection of two (infinite) Lines.
386     bool intersect(const Line& other, SkPoint* point) const;
387     double fA, fB, fC;
388 };
389 
390 /**
391  * An Edge joins a top Vertex to a bottom Vertex. Edge ordering for the list of "edges above" and
392  * "edge below" a vertex as well as for the active edge list is handled by isLeftOf()/isRightOf().
393  * Note that an Edge will give occasionally dist() != 0 for its own endpoints (because floating
394  * point). For speed, that case is only tested by the callers that require it (e.g.,
395  * rewind_if_necessary()). Edges also handle checking for intersection with other edges.
396  * Currently, this converts the edges to the parametric form, in order to avoid doing a division
397  * until an intersection has been confirmed. This is slightly slower in the "found" case, but
398  * a lot faster in the "not found" case.
399  *
400  * The coefficients of the line equation stored in double precision to avoid catastrophic
401  * cancellation in the isLeftOf() and isRightOf() checks. Using doubles ensures that the result is
402  * correct in float, since it's a polynomial of degree 2. The intersect() function, being
403  * degree 5, is still subject to catastrophic cancellation. We deal with that by assuming its
404  * output may be incorrect, and adjusting the mesh topology to match (see comment at the top of
405  * this file).
406  */
407 
408 struct GrTriangulator::Edge {
EdgeEdge409     Edge(Vertex* top, Vertex* bottom, int winding, EdgeType type)
410         : fWinding(winding)
411         , fTop(top)
412         , fBottom(bottom)
413         , fType(type)
414         , fLeft(nullptr)
415         , fRight(nullptr)
416         , fPrevEdgeAbove(nullptr)
417         , fNextEdgeAbove(nullptr)
418         , fPrevEdgeBelow(nullptr)
419         , fNextEdgeBelow(nullptr)
420         , fLeftPoly(nullptr)
421         , fRightPoly(nullptr)
422         , fLeftPolyPrev(nullptr)
423         , fLeftPolyNext(nullptr)
424         , fRightPolyPrev(nullptr)
425         , fRightPolyNext(nullptr)
426         , fUsedInLeftPoly(false)
427         , fUsedInRightPoly(false)
428         , fLine(top, bottom) {
429         }
430     int      fWinding;          // 1 == edge goes downward; -1 = edge goes upward.
431     Vertex*  fTop;              // The top vertex in vertex-sort-order (sweep_lt).
432     Vertex*  fBottom;           // The bottom vertex in vertex-sort-order.
433     EdgeType fType;
434     Edge*    fLeft;             // The linked list of edges in the active edge list.
435     Edge*    fRight;            // "
436     Edge*    fPrevEdgeAbove;    // The linked list of edges in the bottom Vertex's "edges above".
437     Edge*    fNextEdgeAbove;    // "
438     Edge*    fPrevEdgeBelow;    // The linked list of edges in the top Vertex's "edges below".
439     Edge*    fNextEdgeBelow;    // "
440     Poly*    fLeftPoly;         // The Poly to the left of this edge, if any.
441     Poly*    fRightPoly;        // The Poly to the right of this edge, if any.
442     Edge*    fLeftPolyPrev;
443     Edge*    fLeftPolyNext;
444     Edge*    fRightPolyPrev;
445     Edge*    fRightPolyNext;
446     bool     fUsedInLeftPoly;
447     bool     fUsedInRightPoly;
448     Line     fLine;
449 
distEdge450     double dist(const SkPoint& p) const {
451         // Coerce points coincident with the vertices to have dist = 0, since converting from
452         // a double intersection point back to float storage might construct a point that's no
453         // longer on the ideal line.
454         return (p == fTop->fPoint || p == fBottom->fPoint) ? 0.0 : fLine.dist(p);
455     }
isRightOfEdge456     bool isRightOf(const Vertex& v) const { return this->dist(v.fPoint) < 0.0; }
isLeftOfEdge457     bool isLeftOf(const Vertex& v) const { return this->dist(v.fPoint) > 0.0; }
recomputeEdge458     void recompute() { fLine = Line(fTop, fBottom); }
459     void insertAbove(Vertex*, const Comparator&);
460     void insertBelow(Vertex*, const Comparator&);
461     void disconnect();
462     bool intersect(const Edge& other, SkPoint* p, uint8_t* alpha = nullptr) const;
463 };
464 
465 struct GrTriangulator::EdgeList {
EdgeListEdgeList466     EdgeList() : fHead(nullptr), fTail(nullptr) {}
467     Edge* fHead;
468     Edge* fTail;
469     void insert(Edge* edge, Edge* prev, Edge* next);
470     bool insert(Edge* edge, Edge* prev);
appendEdgeList471     void append(Edge* e) { insert(e, fTail, nullptr); }
472     bool remove(Edge* edge);
removeAllEdgeList473     void removeAll() {
474         while (fHead) {
475             this->remove(fHead);
476         }
477     }
closeEdgeList478     void close() {
479         if (fHead && fTail) {
480             fTail->fRight = fHead;
481             fHead->fLeft = fTail;
482         }
483     }
containsEdgeList484     bool contains(Edge* edge) const { return edge->fLeft || edge->fRight || fHead == edge; }
485 };
486 
487 struct GrTriangulator::MonotonePoly {
MonotonePolyMonotonePoly488     MonotonePoly(Edge* edge, Side side, int winding)
489         : fSide(side)
490         , fFirstEdge(nullptr)
491         , fLastEdge(nullptr)
492         , fPrev(nullptr)
493         , fNext(nullptr)
494         , fWinding(winding) {
495         this->addEdge(edge);
496     }
497     Side          fSide;
498     Edge*         fFirstEdge;
499     Edge*         fLastEdge;
500     MonotonePoly* fPrev;
501     MonotonePoly* fNext;
502     int fWinding;
503     void addEdge(Edge*);
504 };
505 
506 struct GrTriangulator::Poly {
507     Poly(Vertex* v, int winding);
508 
509     Poly* addEdge(Edge* e, Side side, GrTriangulator*);
lastVertexPoly510     Vertex* lastVertex() const { return fTail ? fTail->fLastEdge->fBottom : fFirstVertex; }
511     Vertex* fFirstVertex;
512     int fWinding;
513     MonotonePoly* fHead;
514     MonotonePoly* fTail;
515     Poly* fNext;
516     Poly* fPartner;
517     int fCount;
518 #if TRIANGULATOR_LOGGING
519     int fID;
520 #endif
521 };
522 
523 struct GrTriangulator::Comparator {
524     enum class Direction { kVertical, kHorizontal };
ComparatorComparator525     Comparator(Direction direction) : fDirection(direction) {}
526     bool sweep_lt(const SkPoint& a, const SkPoint& b) const;
527     Direction fDirection;
528 };
529 
530 #endif // SK_ENABLE_OPTIMIZE_SIZE
531 
532 #endif // GrTriangulator_DEFINED
533