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