1 /*
2  * Copyright 2020 Google LLC.
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/tessellate/GrStrokeTessellateOp.h"
9 
10 #include "src/core/SkPathPriv.h"
11 #include "src/gpu/GrRecordingContextPriv.h"
12 #include "src/gpu/geometry/GrPathUtils.h"
13 #include "src/gpu/tessellate/GrWangsFormula.h"
14 
15 using Patch = GrStrokeTessellateShader::Patch;
16 
onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView * writeView,GrAppliedClip * clip,const GrXferProcessor::DstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers)17 void GrStrokeTessellateOp::onPrePrepare(GrRecordingContext* context,
18                                         const GrSurfaceProxyView* writeView, GrAppliedClip* clip,
19                                         const GrXferProcessor::DstProxyView& dstProxyView,
20                                         GrXferBarrierFlags renderPassXferBarriers) {
21     SkArenaAlloc* arena = context->priv().recordTimeAllocator();
22     auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
23                 fStroke, fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
24     this->prePrepareColorProgram(arena, strokeTessellateShader, writeView, std::move(*clip),
25                                  dstProxyView, renderPassXferBarriers, *context->priv().caps());
26     context->priv().recordProgramInfo(fColorProgram);
27 }
28 
onPrepare(GrOpFlushState * flushState)29 void GrStrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
30     if (!fColorProgram) {
31         SkArenaAlloc* arena = flushState->allocator();
32         auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
33                 fStroke, fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
34         this->prePrepareColorProgram(flushState->allocator(), strokeTessellateShader,
35                                      flushState->writeView(), flushState->detachAppliedClip(),
36                                      flushState->dstProxyView(), flushState->renderPassBarriers(),
37                                      flushState->caps());
38     }
39 
40     fTarget = flushState;
41     this->prepareBuffers();
42     fTarget = nullptr;
43 }
44 
pow4(float x)45 static float pow4(float x) {
46     float xx = x*x;
47     return xx*xx;
48 }
49 
prepareBuffers()50 void GrStrokeTessellateOp::prepareBuffers() {
51     SkASSERT(fTarget);
52 
53     // Subtract 2 because the tessellation shader chops every cubic at two locations, and each chop
54     // has the potential to introduce an extra segment.
55     fMaxTessellationSegments = fTarget->caps().shaderCaps()->maxTessellationSegments() - 2;
56 
57     // Calculate the worst-case numbers of parametric segments our hardware can support for the
58     // current stroke radius, in the event that there are also enough radial segments to rotate
59     // 180 and 360 degrees respectively. These are used for "quick accepts" that allow us to
60     // send almost all curves directly to the hardware without having to chop.
61     float numRadialSegments180 = std::max(std::ceil(
62             SK_ScalarPI * fNumRadialSegmentsPerRadian), 1.f);
63     float maxParametricSegments180 = NumParametricSegments(fMaxTessellationSegments,
64                                                            numRadialSegments180);
65     fMaxParametricSegments180_pow4 = pow4(maxParametricSegments180);
66 
67     float numRadialSegments360 = std::max(std::ceil(
68             2*SK_ScalarPI * fNumRadialSegmentsPerRadian), 1.f);
69     float maxParametricSegments360 = NumParametricSegments(fMaxTessellationSegments,
70                                                            numRadialSegments360);
71     fMaxParametricSegments360_pow4 = pow4(maxParametricSegments360);
72 
73     // Now calculate the worst-case numbers of parametric segments if we are to integrate a join
74     // into the same patch as the curve.
75     float maxNumSegmentsInJoin;
76     switch (fStroke.getJoin()) {
77         case SkPaint::kBevel_Join:
78             maxNumSegmentsInJoin = 1;
79             break;
80         case SkPaint::kMiter_Join:
81             maxNumSegmentsInJoin = 2;
82             break;
83         case SkPaint::kRound_Join:
84             // 180-degree round join.
85             maxNumSegmentsInJoin = numRadialSegments180;
86             break;
87     }
88     // Subtract an extra 1 off the end because when we integrate a join, the tessellator has to add
89     // a redundant edge between the join and curve.
90     fMaxParametricSegments180_pow4_withJoin = pow4(std::max(
91             maxParametricSegments180 - maxNumSegmentsInJoin - 1, 0.f));
92     fMaxParametricSegments360_pow4_withJoin = pow4(std::max(
93             maxParametricSegments360 - maxNumSegmentsInJoin - 1, 0.f));
94     fMaxCombinedSegments_withJoin = fMaxTessellationSegments - maxNumSegmentsInJoin - 1;
95     fSoloRoundJoinAlwaysFitsInPatch = (numRadialSegments180 <= fMaxTessellationSegments);
96 
97     // Pre-allocate at least enough vertex space for 1 in 4 strokes to chop, and for 8 caps.
98     int strokePreallocCount = fTotalCombinedVerbCnt * 5/4;
99     int capPreallocCount = 8;
100     this->allocPatchChunkAtLeast(strokePreallocCount + capPreallocCount);
101 
102     for (const SkPath& path : fPathList) {
103         fHasLastControlPoint = false;
104         SkDEBUGCODE(fHasCurrentPoint = false;)
105         SkPathVerb previousVerb = SkPathVerb::kClose;
106         for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
107             switch (verb) {
108                 case SkPathVerb::kMove:
109                     // "A subpath ... consisting of a single moveto shall not be stroked."
110                     // https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
111                     if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
112                         this->cap();
113                     }
114                     this->moveTo(pts[0]);
115                     break;
116                 case SkPathVerb::kLine:
117                     SkASSERT(fHasCurrentPoint);
118                     SkASSERT(pts[0] == fCurrentPoint);
119                     this->lineTo(pts[1]);
120                     break;
121                 case SkPathVerb::kQuad:
122                     this->quadraticTo(pts);
123                     break;
124                 case SkPathVerb::kCubic:
125                     this->cubicTo(pts);
126                     break;
127                 case SkPathVerb::kConic:
128                     SkUNREACHABLE;
129                 case SkPathVerb::kClose:
130                     this->close();
131                     break;
132             }
133             previousVerb = verb;
134         }
135         if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
136             this->cap();
137         }
138     }
139 }
140 
moveTo(SkPoint pt)141 void GrStrokeTessellateOp::moveTo(SkPoint pt) {
142     fCurrentPoint = fCurrContourStartPoint = pt;
143     fHasLastControlPoint = false;
144     SkDEBUGCODE(fHasCurrentPoint = true;)
145 }
146 
moveTo(SkPoint pt,SkPoint lastControlPoint)147 void GrStrokeTessellateOp::moveTo(SkPoint pt, SkPoint lastControlPoint) {
148     fCurrentPoint = fCurrContourStartPoint = pt;
149     fCurrContourFirstControlPoint = fLastControlPoint = lastControlPoint;
150     fHasLastControlPoint = true;
151     SkDEBUGCODE(fHasCurrentPoint = true;)
152 }
153 
lineTo(SkPoint pt,JoinType prevJoinType)154 void GrStrokeTessellateOp::lineTo(SkPoint pt, JoinType prevJoinType) {
155     SkASSERT(fHasCurrentPoint);
156 
157     // Zero-length paths need special treatment because they are spec'd to behave differently.
158     if (pt == fCurrentPoint) {
159         return;
160     }
161 
162     if (fMaxCombinedSegments_withJoin < 1 || prevJoinType == JoinType::kCusp) {
163         // Either the stroke has extremely thick round joins and there aren't enough guaranteed
164         // segments to always combine a join with a line patch, or we need a cusp. Either way we
165         // handle the join in its own separate patch.
166         this->joinTo(prevJoinType, pt);
167         prevJoinType = JoinType::kNone;
168     }
169 
170     SkPoint asCubic[4] = {fCurrentPoint, fCurrentPoint, pt, pt};
171     this->cubicToRaw(prevJoinType, asCubic);
172 }
173 
chop_pt_is_cusp(const SkPoint & prevControlPoint,const SkPoint & chopPoint,const SkPoint & nextControlPoint)174 static bool chop_pt_is_cusp(const SkPoint& prevControlPoint, const SkPoint& chopPoint,
175                             const SkPoint& nextControlPoint) {
176     // Adjacent chops should almost always be colinear. The only case where they will not be is a
177     // cusp, which will rotate a minimum of 180 degrees.
178     return (nextControlPoint - chopPoint).dot(chopPoint - prevControlPoint) <= 0;
179 }
180 
quad_chop_is_cusp(const SkPoint chops[5])181 static bool quad_chop_is_cusp(const SkPoint chops[5]) {
182     SkPoint chopPt = chops[2];
183     SkPoint prevCtrlPt = (chops[1] != chopPt) ? chops[1] : chops[0];
184     SkPoint nextCtrlPt = (chops[3] != chopPt) ? chops[3] : chops[4];
185     return chop_pt_is_cusp(prevCtrlPt, chopPt, nextCtrlPt);
186 }
187 
quadraticTo(const SkPoint p[3],JoinType prevJoinType,int maxDepth)188 void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType, int maxDepth) {
189     SkASSERT(fHasCurrentPoint);
190     SkASSERT(p[0] == fCurrentPoint);
191 
192     // Zero-length paths need special treatment because they are spec'd to behave differently. If
193     // the control point is colocated on an endpoint then this might end up being the case. Fall
194     // back on a lineTo and let it make the final check.
195     if (p[1] == p[0] || p[1] == p[2]) {
196         this->lineTo(p[2], prevJoinType);
197         return;
198     }
199 
200     // Convert to a cubic.
201     SkPoint asCubic[4];
202     GrPathUtils::convertQuadToCubic(p, asCubic);
203 
204     // Ensure our hardware supports enough tessellation segments to render the curve. This early out
205     // assumes a worst-case quadratic rotation of 180 degrees and a worst-case number of segments in
206     // the join.
207     //
208     // An informal survey of skottie animations and gms revealed that even with a bare minimum of 64
209     // tessellation segments, 99.9%+ of quadratics take this early out.
210     float numParametricSegments_pow4 = GrWangsFormula::quadratic_pow4(fParametricIntolerance, p);
211     if (numParametricSegments_pow4 <= fMaxParametricSegments180_pow4_withJoin &&
212         prevJoinType != JoinType::kCusp) {
213         this->cubicToRaw(prevJoinType, asCubic);
214         return;
215     }
216 
217     if (numParametricSegments_pow4 <= fMaxParametricSegments180_pow4 || maxDepth == 0) {
218         if (numParametricSegments_pow4 > fMaxParametricSegments180_pow4_withJoin ||
219             prevJoinType == JoinType::kCusp) {
220             // Either there aren't enough guaranteed segments to include the join in the quadratic's
221             // patch, or we need a cusp. Emit a standalone patch for the join.
222             this->joinTo(prevJoinType, asCubic);
223             prevJoinType = JoinType::kNone;
224         }
225         this->cubicToRaw(prevJoinType, asCubic);
226         return;
227     }
228 
229     // We still might have enough tessellation segments to render the curve. Check again with the
230     // actual rotation.
231     float numRadialSegments = SkMeasureQuadRotation(p) * fNumRadialSegmentsPerRadian;
232     numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
233     float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
234     numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
235     float numCombinedSegments = NumCombinedSegments(numParametricSegments, numRadialSegments);
236     if (numCombinedSegments > fMaxTessellationSegments) {
237         // The hardware doesn't support enough segments for this curve. Chop and recurse.
238         if (maxDepth < 0) {
239             // Decide on an extremely conservative upper bound for when to quit chopping. This
240             // is solely to protect us from infinite recursion in instances where FP error
241             // prevents us from chopping at the correct midtangent.
242             maxDepth = sk_float_nextlog2(numParametricSegments) +
243                        sk_float_nextlog2(numRadialSegments) + 1;
244             maxDepth = std::max(maxDepth, 1);
245         }
246         SkPoint chops[5];
247         if (numParametricSegments >= numRadialSegments) {
248             SkChopQuadAtHalf(p, chops);
249         } else {
250             SkChopQuadAtMidTangent(p, chops);
251         }
252         this->quadraticTo(chops, prevJoinType, maxDepth - 1);
253         // If we chopped at a cusp then rotation is not continuous between the two curves. Insert a
254         // cusp to make up for lost rotation.
255         JoinType nextJoinType = (quad_chop_is_cusp(chops)) ?
256                 JoinType::kCusp : JoinType::kFromStroke;
257         this->quadraticTo(chops + 2, nextJoinType, maxDepth - 1);
258         return;
259     }
260 
261     if (numCombinedSegments > fMaxCombinedSegments_withJoin ||
262         prevJoinType == JoinType::kCusp) {
263         // Either there aren't enough guaranteed segments to include the join in the quadratic's
264         // patch, or we need a cusp. Emit a standalone patch for the join.
265         this->joinTo(prevJoinType, asCubic);
266         prevJoinType = JoinType::kNone;
267     }
268     this->cubicToRaw(prevJoinType, asCubic);
269 }
270 
cubic_chop_is_cusp(const SkPoint chops[7])271 static bool cubic_chop_is_cusp(const SkPoint chops[7]) {
272     SkPoint chopPt = chops[3];
273     auto prevCtrlPt = (chops[2] != chopPt) ? chops[2] : (chops[1] != chopPt) ? chops[1] : chops[0];
274     auto nextCtrlPt = (chops[4] != chopPt) ? chops[4] : (chops[5] != chopPt) ? chops[5] : chops[6];
275     return chop_pt_is_cusp(prevCtrlPt, chopPt, nextCtrlPt);
276 }
277 
cubicTo(const SkPoint p[4],JoinType prevJoinType,Convex180Status convex180Status,int maxDepth)278 void GrStrokeTessellateOp::cubicTo(const SkPoint p[4], JoinType prevJoinType,
279                                    Convex180Status convex180Status, int maxDepth) {
280     SkASSERT(fHasCurrentPoint);
281     SkASSERT(p[0] == fCurrentPoint);
282 
283     // The stroke tessellation shader assigns special meaning to p0==p1==p2 and p1==p2==p3. If this
284     // is the case then we need to rewrite the cubic.
285     if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
286         this->lineTo(p[3], prevJoinType);
287         return;
288     }
289 
290     // Ensure our hardware supports enough tessellation segments to render the curve. This early out
291     // assumes a worst-case cubic rotation of 360 degrees and a worst-case number of segments in the
292     // join.
293     //
294     // An informal survey of skottie animations revealed that with a bare minimum of 64 tessellation
295     // segments, 95% of cubics take this early out.
296     float numParametricSegments_pow4 = GrWangsFormula::cubic_pow4(fParametricIntolerance, p);
297     if (numParametricSegments_pow4 <= fMaxParametricSegments360_pow4_withJoin &&
298         prevJoinType != JoinType::kCusp) {
299         this->cubicToRaw(prevJoinType, p);
300         return;
301     }
302 
303     float maxParametricSegments_pow4 = (convex180Status == Convex180Status::kYes) ?
304             fMaxParametricSegments180_pow4 : fMaxParametricSegments360_pow4;
305     if (numParametricSegments_pow4 <= maxParametricSegments_pow4 || maxDepth == 0) {
306         float maxParametricSegments_pow4_withJoin = (convex180Status == Convex180Status::kYes) ?
307                 fMaxParametricSegments180_pow4_withJoin : fMaxParametricSegments360_pow4_withJoin;
308         if (numParametricSegments_pow4 > maxParametricSegments_pow4_withJoin ||
309             prevJoinType == JoinType::kCusp) {
310             // Either there aren't enough guaranteed segments to include the join in the cubic's
311             // patch, or we need a cusp. Emit a standalone patch for the join.
312             this->joinTo(prevJoinType, p);
313             prevJoinType = JoinType::kNone;
314         }
315         this->cubicToRaw(prevJoinType, p);
316         return;
317     }
318 
319     // Ensure the curve does not inflect or rotate >180 degrees before we start subdividing and
320     // measuring rotation.
321     SkPoint chops[10];
322     if (convex180Status == Convex180Status::kUnknown) {
323         float chopT[2];
324         if (int n = GrPathUtils::findCubicConvex180Chops(p, chopT)) {
325             SkChopCubicAt(p, chops, chopT, n);
326             this->cubicTo(chops, prevJoinType, Convex180Status::kYes, maxDepth);
327             for (int i = 1; i <= n; ++i) {
328                 // If we chopped at a cusp then rotation is not continuous between the two curves.
329                 // Insert a double cusp to make up for lost rotation.
330                 JoinType nextJoinType = (cubic_chop_is_cusp(chops + (i - 1)*3)) ?
331                         JoinType::kCusp : JoinType::kFromStroke;
332                 this->cubicTo(chops + i*3, nextJoinType, Convex180Status::kYes, maxDepth);
333             }
334         } else {
335             // The cubic was Convex180Status::kYes after all. Try again when we can use 180-degree
336             // max segment limits instead of 360.
337             this->cubicTo(p, prevJoinType, Convex180Status::kYes, maxDepth);
338         }
339         return;
340     }
341 
342     // We still might have enough tessellation segments to render the curve. Check again with
343     // its actual rotation.
344     float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRadian;
345     numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
346     float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
347     numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
348     float numCombinedSegments = NumCombinedSegments(numParametricSegments, numRadialSegments);
349     if (numCombinedSegments > fMaxTessellationSegments) {
350         // The hardware doesn't support enough segments for this curve. Chop and recurse.
351         if (maxDepth < 0) {
352             // Decide on an extremely conservative upper bound for when to quit chopping. This
353             // is solely to protect us from infinite recursion in instances where FP error
354             // prevents us from chopping at the correct midtangent.
355             maxDepth = sk_float_nextlog2(numParametricSegments) +
356                        sk_float_nextlog2(numRadialSegments) + 1;
357             maxDepth = std::max(maxDepth, 1);
358         }
359         if (numParametricSegments >= numRadialSegments) {
360             SkChopCubicAtHalf(p, chops);
361         } else {
362             SkChopCubicAtMidTangent(p, chops);
363         }
364         // If we chopped at a cusp then rotation is not continuous between the two curves. Insert a
365         // cusp to make up for lost rotation.
366         JoinType nextJoinType = (cubic_chop_is_cusp(chops)) ?
367                 JoinType::kCusp : JoinType::kFromStroke;
368         this->cubicTo(chops, prevJoinType, Convex180Status::kYes, maxDepth - 1);
369         this->cubicTo(chops + 3, nextJoinType, Convex180Status::kYes, maxDepth - 1);
370         return;
371     }
372 
373     if (numCombinedSegments > fMaxCombinedSegments_withJoin || prevJoinType == JoinType::kCusp) {
374         // Either there aren't enough guaranteed segments to include the join in the cubic's patch,
375         // or we need a cusp. Emit a standalone patch for the join.
376         this->joinTo(prevJoinType, p);
377         prevJoinType = JoinType::kNone;
378     }
379     this->cubicToRaw(prevJoinType, p);
380 }
381 
joinTo(JoinType joinType,SkPoint nextControlPoint,int maxDepth)382 void GrStrokeTessellateOp::joinTo(JoinType joinType, SkPoint nextControlPoint, int maxDepth) {
383     SkASSERT(fHasCurrentPoint);
384 
385     if (!fHasLastControlPoint) {
386         // The first stroke doesn't have a previous join.
387         return;
388     }
389 
390     if (!fSoloRoundJoinAlwaysFitsInPatch && maxDepth != 0 &&
391         (fStroke.getJoin() == SkPaint::kRound_Join || joinType == JoinType::kCusp)) {
392         SkVector tan0 = fCurrentPoint - fLastControlPoint;
393         SkVector tan1 = nextControlPoint - fCurrentPoint;
394         float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
395         float numRadialSegments = rotation * fNumRadialSegmentsPerRadian;
396         if (numRadialSegments > fMaxTessellationSegments) {
397             // This is a round join that requires more segments than the tessellator supports.
398             // Split it and recurse.
399             if (maxDepth < 0) {
400                 // Decide on an upper bound for when to quit chopping. This is solely to protect
401                 // us from infinite recursion due to FP precision issues.
402                 maxDepth = sk_float_nextlog2(numRadialSegments / fMaxTessellationSegments);
403                 maxDepth = std::max(maxDepth, 1);
404             }
405             // Find the bisector so we can split the join in half.
406             SkPoint bisector = SkFindBisector(tan0, tan1);
407             // c0 will be the "next" control point for the first join half, and c1 will be the
408             // "previous" control point for the second join half.
409             SkPoint c0, c1;
410             // FIXME: This hack ensures "c0 - fCurrentPoint" gives the exact same ieee fp32 vector
411             // as "-(c1 - fCurrentPoint)". If our current strategy of join chopping sticks, we may
412             // want to think of a cleaner method to avoid T-junctions when we chop joins.
413             int maxAttempts = 10;
414             do {
415                 bisector = (fCurrentPoint + bisector) - (fCurrentPoint - bisector);
416                 c0 = fCurrentPoint + bisector;
417                 c1 = fCurrentPoint - bisector;
418             } while (c0 - fCurrentPoint != -(c1 - fCurrentPoint) && --maxAttempts);
419             this->joinTo(joinType, c0, maxDepth - 1);  // First join half.
420             fLastControlPoint = c1;
421             this->joinTo(joinType, nextControlPoint, maxDepth - 1);  // Second join half.
422             return;
423         }
424     }
425 
426     this->joinToRaw(joinType, nextControlPoint);
427 }
428 
close()429 void GrStrokeTessellateOp::close() {
430     SkASSERT(fHasCurrentPoint);
431 
432     if (!fHasLastControlPoint) {
433         // Draw caps instead of closing if the subpath is zero length:
434         //
435         //   "Any zero length subpath ...  shall be stroked if the 'stroke-linecap' property has a
436         //   value of round or square producing respectively a circle or a square."
437         //
438         //   (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
439         //
440         this->cap();
441         return;
442     }
443 
444     // Draw a line back to the beginning. (This will be discarded if
445     // fCurrentPoint == fCurrContourStartPoint.)
446     this->lineTo(fCurrContourStartPoint);
447     this->joinTo(JoinType::kFromStroke, fCurrContourFirstControlPoint);
448 
449     fHasLastControlPoint = false;
450     SkDEBUGCODE(fHasCurrentPoint = false;)
451 }
452 
normalize(const SkVector & v)453 static SkVector normalize(const SkVector& v) {
454     SkVector norm = v;
455     norm.normalize();
456     return norm;
457 }
458 
cap()459 void GrStrokeTessellateOp::cap() {
460     SkASSERT(fHasCurrentPoint);
461 
462     if (!fHasLastControlPoint) {
463         // We don't have any control points to orient the caps. In this case, square and round caps
464         // are specified to be drawn as an axis-aligned square or circle respectively. Assign
465         // default control points that achieve this.
466         fCurrContourFirstControlPoint = fCurrContourStartPoint - SkPoint{1,0};
467         fLastControlPoint = fCurrContourStartPoint + SkPoint{1,0};
468         fCurrentPoint = fCurrContourStartPoint;
469         fHasLastControlPoint = true;
470     }
471 
472     switch (fStroke.getCap()) {
473         case SkPaint::kButt_Cap:
474             break;
475         case SkPaint::kRound_Cap: {
476             // A round cap is the same thing as a 180-degree round join.
477             // If our join type isn't round we can alternatively use a cusp.
478             JoinType roundCapJoinType = (fStroke.getJoin() == SkPaint::kRound_Join) ?
479                     JoinType::kFromStroke : JoinType::kCusp;
480             this->joinTo(roundCapJoinType, fLastControlPoint);
481             this->moveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
482             this->joinTo(roundCapJoinType, fCurrContourFirstControlPoint);
483             break;
484         }
485 
486         case SkPaint::kSquare_Cap: {
487             // A square cap is the same as appending lineTos.
488             float rad = fStroke.getWidth() * .5f;
489             this->lineTo(fCurrentPoint + normalize(fCurrentPoint - fLastControlPoint) * rad);
490             this->moveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
491             this->lineTo(fCurrContourStartPoint +
492                          normalize(fCurrContourStartPoint - fCurrContourFirstControlPoint) * rad);
493             break;
494         }
495     }
496 
497     fHasLastControlPoint = false;
498     SkDEBUGCODE(fHasCurrentPoint = false;)
499 }
500 
cubicToRaw(JoinType prevJoinType,const SkPoint pts[4])501 void GrStrokeTessellateOp::cubicToRaw(JoinType prevJoinType, const SkPoint pts[4]) {
502     // Cusps can't be combined with a stroke patch. They need to have been written out already as
503     // their own standalone patch.
504     SkASSERT(prevJoinType != JoinType::kCusp);
505 
506     SkPoint c1 = (pts[1] == pts[0]) ? pts[2] : pts[1];
507     SkPoint c2 = (pts[2] == pts[3]) ? pts[1] : pts[2];
508 
509     if (!fHasLastControlPoint) {
510         // The first stroke doesn't have a previous join (yet). If the current contour ends up
511         // closing itself, we will add that join as its own patch.
512         // TODO: Consider deferring the first stroke until we know whether the contour will close.
513         // This will allow us to use the closing join as the first patch's previous join.
514         prevJoinType = JoinType::kNone;
515         fCurrContourFirstControlPoint = c1;
516         fHasLastControlPoint = true;
517     } else {
518         // By using JoinType::kNone, the caller promises to have written out their own join that
519         // seams exactly with this curve.
520         SkASSERT((prevJoinType != JoinType::kNone) || fLastControlPoint == c1);
521     }
522 
523     if (Patch* patch = this->reservePatch()) {
524         // Disable the join section of this patch if prevJoinType is kNone by setting the previous
525         // control point equal to p0.
526         patch->fPrevControlPoint = (prevJoinType == JoinType::kNone) ? pts[0] : fLastControlPoint;
527         patch->fPts = {pts[0], pts[1], pts[2], pts[3]};
528     }
529 
530     fLastControlPoint = c2;
531     fCurrentPoint = pts[3];
532 }
533 
joinToRaw(JoinType joinType,SkPoint nextControlPoint)534 void GrStrokeTessellateOp::joinToRaw(JoinType joinType, SkPoint nextControlPoint) {
535     // We should never write out joins before the first curve.
536     SkASSERT(fHasLastControlPoint);
537     SkASSERT(fHasCurrentPoint);
538 
539     if (Patch* joinPatch = this->reservePatch()) {
540         joinPatch->fPrevControlPoint = fLastControlPoint;
541         joinPatch->fPts[0] = fCurrentPoint;
542         if (joinType == JoinType::kFromStroke) {
543             // [p0, p3, p3, p3] is a reserved pattern that means this patch is a join only (no cubic
544             // sections in the patch).
545             joinPatch->fPts[1] = joinPatch->fPts[2] = nextControlPoint;
546         } else {
547             SkASSERT(joinType == JoinType::kCusp);
548             // [p0, p0, p0, p3] is a reserved pattern that means this patch is a cusp point.
549             joinPatch->fPts[1] = joinPatch->fPts[2] = fCurrentPoint;
550         }
551         joinPatch->fPts[3] = nextControlPoint;
552     }
553 
554     fLastControlPoint = nextControlPoint;
555 }
556 
reservePatch()557 Patch* GrStrokeTessellateOp::reservePatch() {
558     if (fPatchChunks.back().fPatchCount >= fCurrChunkPatchCapacity) {
559         // The current chunk is full. Time to allocate a new one. (And no need to put back vertices;
560         // the buffer is full.)
561         this->allocPatchChunkAtLeast(fCurrChunkMinPatchAllocCount * 2);
562     }
563     if (!fCurrChunkPatchData) {
564         SkDebugf("WARNING: Failed to allocate vertex buffer for tessellated stroke.");
565         return nullptr;
566     }
567     SkASSERT(fPatchChunks.back().fPatchCount <= fCurrChunkPatchCapacity);
568     Patch* patch = fCurrChunkPatchData + fPatchChunks.back().fPatchCount;
569     ++fPatchChunks.back().fPatchCount;
570     return patch;
571 }
572 
allocPatchChunkAtLeast(int minPatchAllocCount)573 void GrStrokeTessellateOp::allocPatchChunkAtLeast(int minPatchAllocCount) {
574     PatchChunk* chunk = &fPatchChunks.push_back();
575     fCurrChunkPatchData = (Patch*)fTarget->makeVertexSpaceAtLeast(sizeof(Patch), minPatchAllocCount,
576                                                                   minPatchAllocCount,
577                                                                   &chunk->fPatchBuffer,
578                                                                   &chunk->fBasePatch,
579                                                                   &fCurrChunkPatchCapacity);
580     fCurrChunkMinPatchAllocCount = minPatchAllocCount;
581 }
582 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)583 void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
584     SkASSERT(fColorProgram);
585     SkASSERT(chainBounds == this->bounds());
586 
587     flushState->bindPipelineAndScissorClip(*fColorProgram, this->bounds());
588     flushState->bindTextures(fColorProgram->primProc(), nullptr, fColorProgram->pipeline());
589     for (const auto& chunk : fPatchChunks) {
590         if (chunk.fPatchBuffer) {
591             flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fPatchBuffer));
592             flushState->draw(chunk.fPatchCount, chunk.fBasePatch);
593         }
594     }
595 }
596