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