1 /*
2  * Copyright 2018 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 #include "src/gpu/ccpr/GrCCStrokeGeometry.h"
9 
10 #include "include/core/SkStrokeRec.h"
11 #include "include/private/SkNx.h"
12 #include "src/core/SkGeometry.h"
13 #include "src/core/SkMathPriv.h"
14 
15 // This is the maximum distance in pixels that we can stray from the edge of a stroke when
16 // converting it to flat line segments.
17 static constexpr float kMaxErrorFromLinearization = 1/8.f;
18 
length(const Sk2f & n)19 static inline float length(const Sk2f& n) {
20     Sk2f nn = n*n;
21     return SkScalarSqrt(nn[0] + nn[1]);
22 }
23 
normalize(const Sk2f & v)24 static inline Sk2f normalize(const Sk2f& v) {
25     Sk2f vv = v*v;
26     vv += SkNx_shuffle<1,0>(vv);
27     return v * vv.rsqrt();
28 }
29 
transpose(const Sk2f & a,const Sk2f & b,Sk2f * X,Sk2f * Y)30 static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) {
31     float transpose[4];
32     a.store(transpose);
33     b.store(transpose+2);
34     Sk2f::Load2(transpose, X, Y);
35 }
36 
normalize2(const Sk2f & v0,const Sk2f & v1,SkPoint out[2])37 static inline void normalize2(const Sk2f& v0, const Sk2f& v1, SkPoint out[2]) {
38     Sk2f X, Y;
39     transpose(v0, v1, &X, &Y);
40     Sk2f invlength = (X*X + Y*Y).rsqrt();
41     Sk2f::Store2(out, Y * invlength, -X * invlength);
42 }
43 
calc_curvature_costheta(const Sk2f & leftTan,const Sk2f & rightTan)44 static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) {
45     Sk2f X, Y;
46     transpose(leftTan, rightTan, &X, &Y);
47     Sk2f invlength = (X*X + Y*Y).rsqrt();
48     Sk2f dotprod = leftTan * rightTan;
49     return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
50 }
51 
join_verb_from_join(SkPaint::Join join)52 static GrCCStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) {
53     using Verb = GrCCStrokeGeometry::Verb;
54     switch (join) {
55         case SkPaint::kBevel_Join:
56             return Verb::kBevelJoin;
57         case SkPaint::kMiter_Join:
58             return Verb::kMiterJoin;
59         case SkPaint::kRound_Join:
60             return Verb::kRoundJoin;
61     }
62     SK_ABORT("Invalid SkPaint::Join.");
63 }
64 
beginPath(const SkStrokeRec & stroke,float strokeDevWidth,InstanceTallies * tallies)65 void GrCCStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth,
66                                    InstanceTallies* tallies) {
67     SkASSERT(!fInsideContour);
68     // Client should have already converted the stroke to device space (i.e. width=1 for hairline).
69     SkASSERT(strokeDevWidth > 0);
70 
71     fCurrStrokeRadius = strokeDevWidth/2;
72     fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin());
73     fCurrStrokeCapType = stroke.getCap();
74     fCurrStrokeTallies = tallies;
75 
76     if (Verb::kMiterJoin == fCurrStrokeJoinVerb) {
77         // We implement miters by placing a triangle-shaped cap on top of a bevel join. Convert the
78         // "miter limit" to how tall that triangle cap can be.
79         float m = stroke.getMiter();
80         fMiterMaxCapHeightOverWidth = .5f * SkScalarSqrt(m*m - 1);
81     }
82 
83     // Find the angle of curvature where the arc height above a simple line from point A to point B
84     // is equal to kMaxErrorFromLinearization.
85     float r = SkTMax(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
86     fMaxCurvatureCosTheta = 2*r*r - 1;
87 
88     fCurrContourFirstPtIdx = -1;
89     fCurrContourFirstNormalIdx = -1;
90 
91     fVerbs.push_back(Verb::kBeginPath);
92 }
93 
moveTo(SkPoint pt)94 void GrCCStrokeGeometry::moveTo(SkPoint pt) {
95     SkASSERT(!fInsideContour);
96     fCurrContourFirstPtIdx = fPoints.count();
97     fCurrContourFirstNormalIdx = fNormals.count();
98     fPoints.push_back(pt);
99     SkDEBUGCODE(fInsideContour = true);
100 }
101 
lineTo(SkPoint pt)102 void GrCCStrokeGeometry::lineTo(SkPoint pt) {
103     SkASSERT(fInsideContour);
104     this->lineTo(fCurrStrokeJoinVerb, pt);
105 }
106 
lineTo(Verb leftJoinVerb,SkPoint pt)107 void GrCCStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) {
108     Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back());
109     if ((tan == 0).allTrue()) {
110         return;
111     }
112 
113     tan = normalize(tan);
114     SkVector n = SkVector::Make(tan[1], -tan[0]);
115 
116     this->recordLeftJoinIfNotEmpty(leftJoinVerb, n);
117     fNormals.push_back(n);
118 
119     this->recordStroke(Verb::kLinearStroke, 0);
120     fPoints.push_back(pt);
121 }
122 
quadraticTo(const SkPoint P[3])123 void GrCCStrokeGeometry::quadraticTo(const SkPoint P[3]) {
124     SkASSERT(fInsideContour);
125     this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P));
126 }
127 
128 // Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
129 // sense) line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
130 // from the actual curve.
wangs_formula_quadratic(const Sk2f & p0,const Sk2f & p1,const Sk2f & p2)131 static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
132     static constexpr float k = 2 / (8 * kMaxErrorFromLinearization);
133     float f = SkScalarSqrt(k * length(p2 - p1*2 + p0));
134     return SkScalarCeilToInt(f);
135 }
136 
quadraticTo(Verb leftJoinVerb,const SkPoint P[3],float maxCurvatureT)137 void GrCCStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) {
138     Sk2f p0 = Sk2f::Load(P);
139     Sk2f p1 = Sk2f::Load(P+1);
140     Sk2f p2 = Sk2f::Load(P+2);
141 
142     Sk2f tan0 = p1 - p0;
143     Sk2f tan1 = p2 - p1;
144 
145     // Snap to a "lineTo" if the control point is so close to an endpoint that FP error will become
146     // an issue.
147     if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() ||  // p0 ~= p1
148         (tan1.abs() < SK_ScalarNearlyZero).allTrue()) {  // p1 ~= p2
149         this->lineTo(leftJoinVerb, P[2]);
150         return;
151     }
152 
153     SkPoint normals[2];
154     normalize2(tan0, tan1, normals);
155 
156     // Decide how many flat line segments to chop the curve into.
157     int numSegments = wangs_formula_quadratic(p0, p1, p2);
158     numSegments = SkTMin(numSegments, 1 << kMaxNumLinearSegmentsLog2);
159     if (numSegments <= 1) {
160         this->rotateTo(leftJoinVerb, normals[0]);
161         this->lineTo(Verb::kInternalRoundJoin, P[2]);
162         this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
163         return;
164     }
165 
166     // At + B gives a vector tangent to the quadratic.
167     Sk2f A = p0 - p1*2 + p2;
168     Sk2f B = p1 - p0;
169 
170     // Find a line segment that crosses max curvature.
171     float segmentLength = SkScalarInvert(numSegments);
172     float leftT = maxCurvatureT - segmentLength/2;
173     float rightT = maxCurvatureT + segmentLength/2;
174     Sk2f leftTan, rightTan;
175     if (leftT <= 0) {
176         leftT = 0;
177         leftTan = tan0;
178         rightT = segmentLength;
179         rightTan = A*rightT + B;
180     } else if (rightT >= 1) {
181         leftT = 1 - segmentLength;
182         leftTan = A*leftT + B;
183         rightT = 1;
184         rightTan = tan1;
185     } else {
186         leftTan = A*leftT + B;
187         rightTan = A*rightT + B;
188     }
189 
190     // Check if curvature is too strong for a triangle strip on the line segment that crosses max
191     // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
192     //
193     // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
194     // would benefit significantly from a quick reject that detects curves that don't need special
195     // treatment for strong curvature.
196     bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
197     if (isCurvatureTooStrong) {
198         SkPoint ptsBuffer[5];
199         const SkPoint* currQuadratic = P;
200 
201         if (leftT > 0) {
202             SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
203             this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1);
204             if (rightT < 1) {
205                 rightT = (rightT - leftT) / (1 - leftT);
206             }
207             currQuadratic = ptsBuffer + 2;
208         } else {
209             this->rotateTo(leftJoinVerb, normals[0]);
210         }
211 
212         if (rightT < 1) {
213             SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
214             this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]);
215             this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0);
216         } else {
217             this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]);
218             this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
219         }
220         return;
221     }
222 
223     this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
224     fNormals.push_back_n(2, normals);
225 
226     this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments));
227     p1.store(&fPoints.push_back());
228     p2.store(&fPoints.push_back());
229 }
230 
cubicTo(const SkPoint P[4])231 void GrCCStrokeGeometry::cubicTo(const SkPoint P[4]) {
232     SkASSERT(fInsideContour);
233     float roots[3];
234     int numRoots = SkFindCubicMaxCurvature(P, roots);
235     this->cubicTo(fCurrStrokeJoinVerb, P,
236                   numRoots > 0 ? roots[numRoots/2] : 0,
237                   numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
238                   numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
239 }
240 
241 // Wang's formula for cubics (1985) gives us the number of evenly spaced (in the parametric sense)
242 // line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
243 // from the actual curve.
wangs_formula_cubic(const Sk2f & p0,const Sk2f & p1,const Sk2f & p2,const Sk2f & p3)244 static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
245                                         const Sk2f& p3) {
246     static constexpr float k = (3 * 2) / (8 * kMaxErrorFromLinearization);
247     float f = SkScalarSqrt(k * length(Sk2f::Max((p2 - p1*2 + p0).abs(),
248                                                 (p3 - p2*2 + p1).abs())));
249     return SkScalarCeilToInt(f);
250 }
251 
cubicTo(Verb leftJoinVerb,const SkPoint P[4],float maxCurvatureT,float leftMaxCurvatureT,float rightMaxCurvatureT)252 void GrCCStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT,
253                                  float leftMaxCurvatureT, float rightMaxCurvatureT) {
254     Sk2f p0 = Sk2f::Load(P);
255     Sk2f p1 = Sk2f::Load(P+1);
256     Sk2f p2 = Sk2f::Load(P+2);
257     Sk2f p3 = Sk2f::Load(P+3);
258 
259     Sk2f tan0 = p1 - p0;
260     Sk2f tan1 = p3 - p2;
261 
262     // Snap control points to endpoints if they are so close that FP error will become an issue.
263     if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) {  // p0 ~= p1
264         p1 = p0;
265         tan0 = p2 - p0;
266         if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) {  // p0 ~= p1 ~= p2
267             this->lineTo(leftJoinVerb, P[3]);
268             return;
269         }
270     }
271     if ((tan1.abs() < SK_ScalarNearlyZero).allTrue()) {  // p2 ~= p3
272         p2 = p3;
273         tan1 = p3 - p1;
274         if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() ||  // p1 ~= p2 ~= p3
275             (p0 == p1).allTrue()) {  // p0 ~= p1 AND p2 ~= p3
276             this->lineTo(leftJoinVerb, P[3]);
277             return;
278         }
279     }
280 
281     SkPoint normals[2];
282     normalize2(tan0, tan1, normals);
283 
284     // Decide how many flat line segments to chop the curve into.
285     int numSegments = wangs_formula_cubic(p0, p1, p2, p3);
286     numSegments = SkTMin(numSegments, 1 << kMaxNumLinearSegmentsLog2);
287     if (numSegments <= 1) {
288         this->rotateTo(leftJoinVerb, normals[0]);
289         this->lineTo(leftJoinVerb, P[3]);
290         this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
291         return;
292     }
293 
294     // At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative
295     // minus an irrelevant scale by 3, since all we care about is the direction.)
296     Sk2f A = p3 + (p1 - p2)*3 - p0;
297     Sk2f B = (p0 - p1*2 + p2)*2;
298     Sk2f C = p1 - p0;
299 
300     // Find a line segment that crosses max curvature.
301     float segmentLength = SkScalarInvert(numSegments);
302     float leftT = maxCurvatureT - segmentLength/2;
303     float rightT = maxCurvatureT + segmentLength/2;
304     Sk2f leftTan, rightTan;
305     if (leftT <= 0) {
306         leftT = 0;
307         leftTan = tan0;
308         rightT = segmentLength;
309         rightTan = A*rightT*rightT + B*rightT + C;
310     } else if (rightT >= 1) {
311         leftT = 1 - segmentLength;
312         leftTan = A*leftT*leftT + B*leftT + C;
313         rightT = 1;
314         rightTan = tan1;
315     } else {
316         leftTan = A*leftT*leftT + B*leftT + C;
317         rightTan = A*rightT*rightT + B*rightT + C;
318     }
319 
320     // Check if curvature is too strong for a triangle strip on the line segment that crosses max
321     // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
322     //
323     // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
324     // would benefit significantly from a quick reject that detects curves that don't need special
325     // treatment for strong curvature.
326     bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
327     if (isCurvatureTooStrong) {
328         SkPoint ptsBuffer[7];
329         p0.store(ptsBuffer);
330         p1.store(ptsBuffer + 1);
331         p2.store(ptsBuffer + 2);
332         p3.store(ptsBuffer + 3);
333         const SkPoint* currCubic = ptsBuffer;
334 
335         if (leftT > 0) {
336             SkChopCubicAt(currCubic, ptsBuffer, leftT);
337             this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1,
338                           (kLeftMaxCurvatureNone != leftMaxCurvatureT)
339                                   ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
340                           kRightMaxCurvatureNone);
341             if (rightT < 1) {
342                 rightT = (rightT - leftT) / (1 - leftT);
343             }
344             if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) {
345                 rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT);
346             }
347             currCubic = ptsBuffer + 3;
348         } else {
349             this->rotateTo(leftJoinVerb, normals[0]);
350         }
351 
352         if (rightT < 1) {
353             SkChopCubicAt(currCubic, ptsBuffer, rightT);
354             this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]);
355             currCubic = ptsBuffer + 3;
356             this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0,
357                           kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
358         } else {
359             this->lineTo(Verb::kInternalRoundJoin, currCubic[3]);
360             this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
361         }
362         return;
363     }
364 
365     // Recurse and check the other two points of max curvature, if any.
366     if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
367         this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT,
368                       kRightMaxCurvatureNone);
369         return;
370     }
371     if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
372         SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
373         this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
374                       kRightMaxCurvatureNone);
375         return;
376     }
377 
378     this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
379     fNormals.push_back_n(2, normals);
380 
381     this->recordStroke(Verb::kCubicStroke, SkNextLog2(numSegments));
382     p1.store(&fPoints.push_back());
383     p2.store(&fPoints.push_back());
384     p3.store(&fPoints.push_back());
385 }
386 
recordStroke(Verb verb,int numSegmentsLog2)387 void GrCCStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) {
388     SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2);
389     SkASSERT(numSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
390     fVerbs.push_back(verb);
391     if (Verb::kLinearStroke != verb) {
392         SkASSERT(numSegmentsLog2 > 0);
393         fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2;
394     }
395     ++fCurrStrokeTallies->fStrokes[numSegmentsLog2];
396 }
397 
rotateTo(Verb leftJoinVerb,SkVector normal)398 void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) {
399     this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
400     fNormals.push_back(normal);
401 }
402 
recordLeftJoinIfNotEmpty(Verb joinVerb,SkVector nextNormal)403 void GrCCStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) {
404     if (fNormals.count() <= fCurrContourFirstNormalIdx) {
405         // The contour is empty. Nothing to join with.
406         SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
407         return;
408     }
409 
410     if (Verb::kBevelJoin == joinVerb) {
411         this->recordBevelJoin(Verb::kBevelJoin);
412         return;
413     }
414 
415     Sk2f n0 = Sk2f::Load(&fNormals.back());
416     Sk2f n1 = Sk2f::Load(&nextNormal);
417     Sk2f base = n1 - n0;
418     if ((base.abs() * fCurrStrokeRadius < kMaxErrorFromLinearization).allTrue()) {
419         // Treat any join as a bevel when the outside corners of the two adjoining strokes are
420         // close enough to each other. This is important because "miterCapHeightOverWidth" becomes
421         // unstable when n0 and n1 are nearly equal.
422         this->recordBevelJoin(joinVerb);
423         return;
424     }
425 
426     // We implement miters and round joins by placing a triangle-shaped cap on top of a bevel join.
427     // (For round joins this triangle cap comprises the conic control points.) Find how tall to make
428     // this triangle cap, relative to its width.
429     //
430     // NOTE: This value would be infinite at 180 degrees, but we clamp miterCapHeightOverWidth at
431     // near-infinity. 180-degree round joins still look perfectly acceptable like this (though
432     // technically not pure arcs).
433     Sk2f cross = base * SkNx_shuffle<1,0>(n0);
434     Sk2f dot = base * n0;
435     float miterCapHeight = SkScalarAbs(dot[0] + dot[1]);
436     float miterCapWidth = SkScalarAbs(cross[0] - cross[1]) * 2;
437 
438     if (Verb::kMiterJoin == joinVerb) {
439         if (miterCapHeight > fMiterMaxCapHeightOverWidth * miterCapWidth) {
440             // This join is tighter than the miter limit. Treat it as a bevel.
441             this->recordBevelJoin(Verb::kMiterJoin);
442             return;
443         }
444         this->recordMiterJoin(miterCapHeight / miterCapWidth);
445         return;
446     }
447 
448     SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
449 
450     // Conic arcs become unstable when they approach 180 degrees. When the conic control point
451     // begins shooting off to infinity (i.e., height/width > 32), split the conic into two.
452     static constexpr float kAlmost180Degrees = 32;
453     if (miterCapHeight > kAlmost180Degrees * miterCapWidth) {
454         Sk2f bisect = normalize(n0 - n1);
455         this->rotateTo(joinVerb, SkVector::Make(-bisect[1], bisect[0]));
456         this->recordLeftJoinIfNotEmpty(joinVerb, nextNormal);
457         return;
458     }
459 
460     float miterCapHeightOverWidth = miterCapHeight / miterCapWidth;
461 
462     // Find the heights of this round join's conic control point as well as the arc itself.
463     Sk2f X, Y;
464     transpose(base * base, n0 * n1, &X, &Y);
465     Sk2f r = Sk2f::Max(X + Y + Sk2f(0, 1), 0.f).sqrt();
466     Sk2f heights = SkNx_fma(r, Sk2f(miterCapHeightOverWidth, -SK_ScalarRoot2Over2), Sk2f(0, 1));
467     float controlPointHeight = SkScalarAbs(heights[0]);
468     float curveHeight = heights[1];
469     if (curveHeight * fCurrStrokeRadius < kMaxErrorFromLinearization) {
470         // Treat round joins as bevels when their curvature is nearly flat.
471         this->recordBevelJoin(joinVerb);
472         return;
473     }
474 
475     float w = curveHeight / (controlPointHeight - curveHeight);
476     this->recordRoundJoin(joinVerb, miterCapHeightOverWidth, w);
477 }
478 
recordBevelJoin(Verb originalJoinVerb)479 void GrCCStrokeGeometry::recordBevelJoin(Verb originalJoinVerb) {
480     if (!IsInternalJoinVerb(originalJoinVerb)) {
481         fVerbs.push_back(Verb::kBevelJoin);
482         ++fCurrStrokeTallies->fTriangles;
483     } else {
484         fVerbs.push_back(Verb::kInternalBevelJoin);
485         fCurrStrokeTallies->fTriangles += 2;
486     }
487 }
488 
recordMiterJoin(float miterCapHeightOverWidth)489 void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) {
490     fVerbs.push_back(Verb::kMiterJoin);
491     fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
492     fCurrStrokeTallies->fTriangles += 2;
493 }
494 
recordRoundJoin(Verb joinVerb,float miterCapHeightOverWidth,float conicWeight)495 void GrCCStrokeGeometry::recordRoundJoin(Verb joinVerb, float miterCapHeightOverWidth,
496                                          float conicWeight) {
497     fVerbs.push_back(joinVerb);
498     fParams.push_back().fConicWeight = conicWeight;
499     fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
500     if (Verb::kRoundJoin == joinVerb) {
501         ++fCurrStrokeTallies->fTriangles;
502         ++fCurrStrokeTallies->fConics;
503     } else {
504         SkASSERT(Verb::kInternalRoundJoin == joinVerb);
505         fCurrStrokeTallies->fTriangles += 2;
506         fCurrStrokeTallies->fConics += 2;
507     }
508 }
509 
closeContour()510 void GrCCStrokeGeometry::closeContour() {
511     SkASSERT(fInsideContour);
512     SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
513     if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) {
514         // Draw a line back to the beginning.
515         this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]);
516     }
517     if (fNormals.count() > fCurrContourFirstNormalIdx) {
518         // Join the first and last lines.
519         this->rotateTo(fCurrStrokeJoinVerb,fNormals[fCurrContourFirstNormalIdx]);
520     } else {
521         // This contour is empty. Add a bogus normal since the iterator always expects one.
522         SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
523         fNormals.push_back({0, 0});
524     }
525     fVerbs.push_back(Verb::kEndContour);
526     SkDEBUGCODE(fInsideContour = false);
527 }
528 
capContourAndExit()529 void GrCCStrokeGeometry::capContourAndExit() {
530     SkASSERT(fInsideContour);
531     if (fCurrContourFirstNormalIdx >= fNormals.count()) {
532         // This contour is empty. Add a normal in the direction that caps orient on empty geometry.
533         SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
534         fNormals.push_back({1, 0});
535     }
536 
537     this->recordCapsIfAny();
538     fVerbs.push_back(Verb::kEndContour);
539 
540     SkDEBUGCODE(fInsideContour = false);
541 }
542 
recordCapsIfAny()543 void GrCCStrokeGeometry::recordCapsIfAny() {
544     SkASSERT(fInsideContour);
545     SkASSERT(fCurrContourFirstNormalIdx < fNormals.count());
546 
547     if (SkPaint::kButt_Cap == fCurrStrokeCapType) {
548         return;
549     }
550 
551     Verb capVerb;
552     if (SkPaint::kSquare_Cap == fCurrStrokeCapType) {
553         if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) {
554             return;
555         }
556         capVerb = Verb::kSquareCap;
557         fCurrStrokeTallies->fStrokes[0] += 2;
558     } else {
559         SkASSERT(SkPaint::kRound_Cap == fCurrStrokeCapType);
560         if (fCurrStrokeRadius < kMaxErrorFromLinearization) {
561             return;
562         }
563         capVerb = Verb::kRoundCap;
564         fCurrStrokeTallies->fTriangles += 2;
565         fCurrStrokeTallies->fConics += 4;
566     }
567 
568     fVerbs.push_back(capVerb);
569     fVerbs.push_back(Verb::kEndContour);
570 
571     fVerbs.push_back(capVerb);
572 
573     // Reserve the space first, since push_back() takes the point by reference and might
574     // invalidate the reference if the array grows.
575     fPoints.reserve(fPoints.count() + 1);
576     fPoints.push_back(fPoints[fCurrContourFirstPtIdx]);
577 
578     // Reserve the space first, since push_back() takes the normal by reference and might
579     // invalidate the reference if the array grows. (Although in this case we should be fine
580     // since there is a negate operator.)
581     fNormals.reserve(fNormals.count() + 1);
582     fNormals.push_back(-fNormals[fCurrContourFirstNormalIdx]);
583 }
584