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.
17static constexpr float kMaxErrorFromLinearization = 1/8.f;
18
19static inline float length(const Sk2f& n) {
20 Sk2f nn = n*n;
21 return SkScalarSqrt(nn[0] + nn[1]);
22}
23
24static 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
30static 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
37static 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
44static 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
52static 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
65void 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 = std::max(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
94void 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
102void GrCCStrokeGeometry::lineTo(SkPoint pt) {
103 SkASSERT(fInsideContour);
104 this->lineTo(fCurrStrokeJoinVerb, pt);
105}
106
107void 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
123void 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.
131static 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
137void 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 = std::min(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
231void 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.
244static 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
252void 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 = std::min(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
387void 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
398void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) {
399 this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
400 fNormals.push_back(normal);
401}
402
403void 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
479void 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
489void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) {
490 fVerbs.push_back(Verb::kMiterJoin);
491 fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
492 fCurrStrokeTallies->fTriangles += 2;
493}
494
495void 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
510void 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
529void 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
543void 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