• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/StrokeHardwareTessellator.h"
9 
10 #include "src/core/SkPathPriv.h"
11 #include "src/gpu/GrMeshDrawTarget.h"
12 #include "src/gpu/GrRecordingContextPriv.h"
13 #include "src/gpu/geometry/GrPathUtils.h"
14 #include "src/gpu/tessellate/WangsFormula.h"
15 #include "src/gpu/tessellate/shaders/GrTessellationShader.h"
16 
17 #if SK_GPU_V1
18 #include "src/gpu/GrOpFlushState.h"
19 #endif
20 
21 namespace skgpu {
22 
23 namespace {
24 
num_combined_segments(float numParametricSegments,float numRadialSegments)25 float num_combined_segments(float numParametricSegments, float numRadialSegments) {
26     // The first and last edges are shared by both the parametric and radial sets of edges, so
27     // the total number of edges is:
28     //
29     //   numCombinedEdges = numParametricEdges + numRadialEdges - 2
30     //
31     // It's also important to differentiate between the number of edges and segments in a strip:
32     //
33     //   numCombinedSegments = numCombinedEdges - 1
34     //
35     // So the total number of segments in the combined strip is:
36     //
37     //   numCombinedSegments = numParametricEdges + numRadialEdges - 2 - 1
38     //                       = numParametricSegments + 1 + numRadialSegments + 1 - 2 - 1
39     //                       = numParametricSegments + numRadialSegments - 1
40     //
41     return numParametricSegments + numRadialSegments - 1;
42 }
43 
pow4(float2 x)44 float2 pow4(float2 x) {
45     auto xx = x*x;
46     return xx*xx;
47 }
48 
49 class PatchWriter {
50 public:
51     enum class JoinType {
52         kMiter = SkPaint::kMiter_Join,
53         kRound = SkPaint::kRound_Join,
54         kBevel = SkPaint::kBevel_Join,
55         kBowtie = SkPaint::kLast_Join + 1  // Double sided round join.
56     };
57 
PatchWriter(PatchAttribs attribs,GrMeshDrawTarget * target,const SkMatrix & viewMatrix,float matrixMaxScale,GrVertexChunkArray * patchChunks,size_t patchStride,int minPatchesPerChunk)58     PatchWriter(PatchAttribs attribs,
59                 GrMeshDrawTarget* target,
60                 const SkMatrix& viewMatrix,
61                 float matrixMaxScale,
62                 GrVertexChunkArray* patchChunks,
63                 size_t patchStride,
64                 int minPatchesPerChunk)
65             : fAttribs(attribs)
66             , fChunkBuilder(target, patchChunks, patchStride, minPatchesPerChunk)
67             // Subtract 2 because the tessellation shader chops every cubic at two locations, and
68             // each chop has the potential to introduce an extra segment.
69             , fMaxTessellationSegments(target->caps().shaderCaps()->maxTessellationSegments() - 2)
70             , fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
71     }
72 
73     // This is the precision value, adjusted for the view matrix, to use with Wang's formulas when
74     // determining how many parametric segments a curve will require.
parametricPrecision() const75     float parametricPrecision() const {
76         return fParametricPrecision;
77     }
78     // Will a line and worst-case previous join both fit in a single patch together?
lineFitsInPatch_withJoin()79     bool lineFitsInPatch_withJoin() {
80         return fMaxCombinedSegments_withJoin >= 1;
81     }
82     // Will a stroke with the given number of parametric segments and a worst-case rotation of 180
83     // degrees fit in a single patch?
stroke180FitsInPatch(float numParametricSegments_pow4)84     bool stroke180FitsInPatch(float numParametricSegments_pow4) {
85         return numParametricSegments_pow4 <= fMaxParametricSegments_pow4[0];
86     }
87     // Will a worst-case 180-degree stroke with the given number of parametric segments, and a
88     // worst-case join fit in a single patch together?
stroke180FitsInPatch_withJoin(float numParametricSegments_pow4)89     bool stroke180FitsInPatch_withJoin(float numParametricSegments_pow4) {
90         return numParametricSegments_pow4 <= fMaxParametricSegments_pow4_withJoin[0];
91     }
92     // Will a stroke with the given number of parametric segments and a worst-case rotation of 360
93     // degrees fit in a single patch?
stroke360FitsInPatch(float numParametricSegments_pow4)94     bool stroke360FitsInPatch(float numParametricSegments_pow4) {
95         return numParametricSegments_pow4 <= fMaxParametricSegments_pow4[1];
96     }
97     // Will a worst-case 360-degree stroke with the given number of parametric segments, and a
98     // worst-case join fit in a single patch together?
stroke360FitsInPatch_withJoin(float numParametricSegments_pow4)99     bool stroke360FitsInPatch_withJoin(float numParametricSegments_pow4) {
100         return numParametricSegments_pow4 <= fMaxParametricSegments_pow4_withJoin[1];
101     }
102 
updateTolerances(float numRadialSegmentsPerRadian,SkPaint::Join joinType)103     void updateTolerances(float numRadialSegmentsPerRadian, SkPaint::Join joinType) {
104         fNumRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
105 
106         // Calculate the worst-case numbers of parametric segments our hardware can support for the
107         // current stroke radius, in the event that there are also enough radial segments to rotate
108         // 180 and 360 degrees respectively. These are used for "quick accepts" that allow us to
109         // send almost all curves directly to the hardware without having to chop.
110         float2 numRadialSegments_180_360 = skvx::max(skvx::ceil(
111                 float2{SK_ScalarPI, 2*SK_ScalarPI} * fNumRadialSegmentsPerRadian), 1);
112         // numEdges = numSegments + 1. See num_combined_segments().
113         float maxTotalEdges = fMaxTessellationSegments + 1;
114         // numParametricSegments = numTotalEdges - numRadialSegments. See num_combined_segments().
115         float2 maxParametricSegments = skvx::max(maxTotalEdges - numRadialSegments_180_360, 0);
116         float2 maxParametricSegments_pow4 = pow4(maxParametricSegments);
117         maxParametricSegments_pow4.store(fMaxParametricSegments_pow4);
118 
119         // Find the worst-case numbers of parametric segments if we are to integrate a join into the
120         // same patch as the curve.
121         float numRadialSegments180 = numRadialSegments_180_360[0];
122         float worstCaseNumSegmentsInJoin;
123         switch (joinType) {
124             case SkPaint::kBevel_Join: worstCaseNumSegmentsInJoin = 1; break;
125             case SkPaint::kMiter_Join: worstCaseNumSegmentsInJoin = 2; break;
126             case SkPaint::kRound_Join: worstCaseNumSegmentsInJoin = numRadialSegments180; break;
127         }
128 
129         // Now calculate the worst-case numbers of parametric segments if we also want to combine a
130         // join with the patch. Subtract an extra 1 off the end because when we integrate a join,
131         // the tessellator has to add a redundant edge between the join and curve.
132         float2 maxParametricSegments_pow4_withJoin = pow4(skvx::max(
133                 maxParametricSegments - worstCaseNumSegmentsInJoin - 1, 0));
134         maxParametricSegments_pow4_withJoin.store(fMaxParametricSegments_pow4_withJoin);
135 
136         fMaxCombinedSegments_withJoin = fMaxTessellationSegments - worstCaseNumSegmentsInJoin - 1;
137         fSoloRoundJoinAlwaysFitsInPatch = (numRadialSegments180 <= fMaxTessellationSegments);
138         fStrokeJoinType = JoinType(joinType);
139     }
140 
updateDynamicStroke(const SkStrokeRec & stroke)141     void updateDynamicStroke(const SkStrokeRec& stroke) {
142         SkASSERT(fAttribs & PatchAttribs::kStrokeParams);
143         fDynamicStroke.set(stroke);
144     }
145 
updateDynamicColor(const SkPMColor4f & color)146     void updateDynamicColor(const SkPMColor4f& color) {
147         SkASSERT(fAttribs & PatchAttribs::kColor);
148         bool wideColor = fAttribs & PatchAttribs::kWideColorIfEnabled;
149         SkASSERT(wideColor || color.fitsInBytes());
150         fDynamicColor.set(color, wideColor);
151     }
152 
moveTo(SkPoint pt)153     void moveTo(SkPoint pt) {
154         fCurrContourStartPoint = pt;
155         fHasLastControlPoint = false;
156     }
157 
158     // Writes out the given line, possibly chopping its previous join until the segments fit in
159     // tessellation patches.
writeLineTo(SkPoint p0,SkPoint p1)160     void writeLineTo(SkPoint p0, SkPoint p1) {
161         this->writeLineTo(fStrokeJoinType, p0, p1);
162     }
writeLineTo(JoinType prevJoinType,SkPoint p0,SkPoint p1)163     void writeLineTo(JoinType prevJoinType, SkPoint p0, SkPoint p1) {
164         // Zero-length paths need special treatment because they are spec'd to behave differently.
165         if (p0 == p1) {
166             return;
167         }
168         SkPoint asPatch[4] = {p0, p0, p1, p1};
169         this->internalPatchTo(prevJoinType, this->lineFitsInPatch_withJoin(), asPatch, p1);
170     }
171 
172     // Recursively chops the given conic and its previous join until the segments fit in
173     // tessellation patches.
writeConicPatchesTo(const SkPoint p[3],float w)174     void writeConicPatchesTo(const SkPoint p[3], float w) {
175         this->internalConicPatchesTo(fStrokeJoinType, p, w);
176     }
177 
178     // Chops the given cubic at points of inflection and 180-degree rotation, and then recursively
179     // chops the previous join and cubic sections as necessary until the segments fit in
180     // tessellation patches.
writeCubicConvex180PatchesTo(const SkPoint p[4])181     void writeCubicConvex180PatchesTo(const SkPoint p[4]) {
182         SkPoint chops[10];
183         float chopT[2];
184         bool areCusps;
185         int numChops = GrPathUtils::findCubicConvex180Chops(p, chopT, &areCusps);
186         if (numChops == 0) {
187             // The curve is already convex and rotates no more than 180 degrees.
188             this->internalCubicConvex180PatchesTo(fStrokeJoinType, p);
189         } else if (numChops == 1) {
190             SkChopCubicAt(p, chops, chopT[0]);
191             if (areCusps) {
192                 // When chopping on a perfect cusp, these 3 points will be equal.
193                 chops[2] = chops[4] = chops[3];
194             }
195             this->internalCubicConvex180PatchesTo(fStrokeJoinType, chops);
196             this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3);
197         } else {
198             SkASSERT(numChops == 2);
199             SkChopCubicAt(p, chops, chopT[0], chopT[1]);
200             // Two cusps are only possible on a flat line with two 180-degree turnarounds.
201             if (areCusps) {
202                 this->writeLineTo(chops[0], chops[3]);
203                 this->writeLineTo(JoinType::kBowtie, chops[3], chops[6]);
204                 this->writeLineTo(JoinType::kBowtie, chops[6], chops[9]);
205                 return;
206             }
207             this->internalCubicConvex180PatchesTo(fStrokeJoinType, chops);
208             this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3);
209             this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 6);
210         }
211     }
212 
213     // Writes out the given stroke patch exactly as provided, without chopping or checking the
214     // number of segments. Possibly chops its previous join until the segments fit in tessellation
215     // patches.
writePatchTo(bool prevJoinFitsInPatch,const SkPoint p[4],SkPoint endControlPoint)216     SK_ALWAYS_INLINE void writePatchTo(bool prevJoinFitsInPatch, const SkPoint p[4],
217                                        SkPoint endControlPoint) {
218         SkASSERT(fStrokeJoinType != JoinType::kBowtie);
219 
220         if (!fHasLastControlPoint) {
221             // The first stroke doesn't have a previous join (yet). If the current contour ends up
222             // closing itself, we will add that join as its own patch. TODO: Consider deferring the
223             // first stroke until we know whether the contour will close. This will allow us to use
224             // the closing join as the first patch's previous join.
225             fHasLastControlPoint = true;
226             fCurrContourFirstControlPoint = (p[1] != p[0]) ? p[1] : p[2];
227             fLastControlPoint = p[0];  // Disables the join section of this patch.
228         } else if (!prevJoinFitsInPatch) {
229             // There aren't enough guaranteed segments to fold the previous join into this patch.
230             // Emit the join in its own separate patch.
231             this->internalJoinTo(fStrokeJoinType, p[0], (p[1] != p[0]) ? p[1] : p[2]);
232             fLastControlPoint = p[0];  // Disables the join section of this patch.
233         }
234 
235         if (VertexWriter patchWriter = fChunkBuilder.appendVertex()) {
236             patchWriter << fLastControlPoint;
237             patchWriter.writeArray(p, 4);
238             this->writeDynamicAttribs(&patchWriter);
239         }
240 
241         fLastControlPoint = endControlPoint;
242     }
243 
writeClose(SkPoint contourEndpoint,const SkMatrix & viewMatrix,const SkStrokeRec & stroke)244     void writeClose(SkPoint contourEndpoint, const SkMatrix& viewMatrix,
245                     const SkStrokeRec& stroke) {
246         if (!fHasLastControlPoint) {
247             // Draw caps instead of closing if the subpath is zero length:
248             //
249             //   "Any zero length subpath ...  shall be stroked if the 'stroke-linecap' property has
250             //   a value of round or square producing respectively a circle or a square."
251             //
252             //   (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
253             //
254             this->writeCaps(contourEndpoint, viewMatrix, stroke);
255             return;
256         }
257 
258         // Draw a line back to the beginning. (This will be discarded if
259         // contourEndpoint == fCurrContourStartPoint.)
260         this->writeLineTo(contourEndpoint, fCurrContourStartPoint);
261         this->internalJoinTo(fStrokeJoinType, fCurrContourStartPoint, fCurrContourFirstControlPoint);
262 
263         fHasLastControlPoint = false;
264     }
265 
writeCaps(SkPoint contourEndpoint,const SkMatrix & viewMatrix,const SkStrokeRec & stroke)266     void writeCaps(SkPoint contourEndpoint, const SkMatrix& viewMatrix, const SkStrokeRec& stroke) {
267         if (!fHasLastControlPoint) {
268             // We don't have any control points to orient the caps. In this case, square and round
269             // caps are specified to be drawn as an axis-aligned square or circle respectively.
270             // Assign default control points that achieve this.
271             SkVector outset;
272             if (!stroke.isHairlineStyle()) {
273                 outset = {1, 0};
274             } else {
275                 // If the stroke is hairline, orient the square on the post-transform x-axis
276                 // instead. We don't need to worry about the vector length since it will be
277                 // normalized later. Since the matrix cannot have perspective, the below is
278                 // equivalent to:
279                 //
280                 //    outset = inverse(|a b|) * |1| * arbitrary_scale
281                 //                     |c d|    |0|
282                 //
283                 //    == 1/det * | d -b| * |1| * arbitrary_scale
284                 //               |-c  a|   |0|
285                 //
286                 //    == 1/det * | d| * arbitrary_scale
287                 //               |-c|
288                 //
289                 //    == | d|
290                 //       |-c|
291                 //
292                 SkASSERT(!viewMatrix.hasPerspective());
293                 float c=viewMatrix.getSkewY(), d=viewMatrix.getScaleY();
294                 outset = {d, -c};
295             }
296             fCurrContourFirstControlPoint = fCurrContourStartPoint - outset;
297             fLastControlPoint = fCurrContourStartPoint + outset;
298             fHasLastControlPoint = true;
299             contourEndpoint = fCurrContourStartPoint;
300         }
301 
302         switch (stroke.getCap()) {
303             case SkPaint::kButt_Cap:
304                 break;
305             case SkPaint::kRound_Cap: {
306                 // A round cap is the same thing as a 180-degree round join.
307                 // If our join type isn't round we can alternatively use a bowtie.
308                 JoinType roundCapJoinType = (stroke.getJoin() == SkPaint::kRound_Join)
309                         ? JoinType::kRound : JoinType::kBowtie;
310                 this->internalJoinTo(roundCapJoinType, contourEndpoint, fLastControlPoint);
311                 this->internalMoveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
312                 this->internalJoinTo(roundCapJoinType, fCurrContourStartPoint,
313                                      fCurrContourFirstControlPoint);
314                 break;
315             }
316             case SkPaint::kSquare_Cap: {
317                 // A square cap is the same as appending lineTos.
318                 auto strokeJoinType = JoinType(stroke.getJoin());
319                 SkVector lastTangent = contourEndpoint - fLastControlPoint;
320                 if (!stroke.isHairlineStyle()) {
321                     // Extend the cap by 1/2 stroke width.
322                     lastTangent *= (.5f * stroke.getWidth()) / lastTangent.length();
323                 } else {
324                     // Extend the cap by what will be 1/2 pixel after transformation.
325                     lastTangent *=
326                             .5f / viewMatrix.mapVector(lastTangent.fX, lastTangent.fY).length();
327                 }
328                 this->writeLineTo(strokeJoinType, contourEndpoint, contourEndpoint + lastTangent);
329                 this->internalMoveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
330                 SkVector firstTangent = fCurrContourFirstControlPoint - fCurrContourStartPoint;
331                 if (!stroke.isHairlineStyle()) {
332                     // Set the the cap back by 1/2 stroke width.
333                     firstTangent *= (-.5f * stroke.getWidth()) / firstTangent.length();
334                 } else {
335                     // Set the cap back by what will be 1/2 pixel after transformation.
336                     firstTangent *=
337                             -.5f / viewMatrix.mapVector(firstTangent.fX, firstTangent.fY).length();
338                 }
339                 this->writeLineTo(strokeJoinType, fCurrContourStartPoint,
340                                   fCurrContourStartPoint + firstTangent);
341                 break;
342             }
343         }
344 
345         fHasLastControlPoint = false;
346     }
347 
348 private:
internalMoveTo(SkPoint pt,SkPoint lastControlPoint)349     void internalMoveTo(SkPoint pt, SkPoint lastControlPoint) {
350         fCurrContourStartPoint = pt;
351         fCurrContourFirstControlPoint = fLastControlPoint = lastControlPoint;
352         fHasLastControlPoint = true;
353     }
354 
355     // Recursively chops the given conic and its previous join until the segments fit in
356     // tessellation patches.
internalConicPatchesTo(JoinType prevJoinType,const SkPoint p[3],float w,int maxDepth=-1)357     void internalConicPatchesTo(JoinType prevJoinType, const SkPoint p[3], float w,
358                                 int maxDepth = -1) {
359         // Zero-length paths need special treatment because they are spec'd to behave differently.
360         // If the control point is colocated on an endpoint then this might end up being the case.
361         // Fall back on a lineTo and let it make the final check.
362         if (p[1] == p[0] || p[1] == p[2] || w == 0) {
363             this->writeLineTo(prevJoinType, p[0], p[2]);
364             return;
365         }
366 
367         // Convert to a patch.
368         SkPoint asPatch[4];
369         if (w == 1) {
370             GrPathUtils::convertQuadToCubic(p, asPatch);
371         } else {
372             GrTessellationShader::WriteConicPatch(p, w, asPatch);
373         }
374 
375         float numParametricSegments_pow4;
376         if (w == 1) {
377             numParametricSegments_pow4 = wangs_formula::quadratic_pow4(fParametricPrecision, p);
378         } else {
379             float n = wangs_formula::conic_pow2(fParametricPrecision, p, w);
380             numParametricSegments_pow4 = n*n;
381         }
382         if (this->stroke180FitsInPatch(numParametricSegments_pow4) || maxDepth == 0) {
383             this->internalPatchTo(prevJoinType,
384                                   this->stroke180FitsInPatch_withJoin(numParametricSegments_pow4),
385                                   asPatch, p[2]);
386             return;
387         }
388 
389         // We still might have enough tessellation segments to render the curve. Check again with
390         // the actual rotation.
391         float numRadialSegments = SkMeasureQuadRotation(p) * fNumRadialSegmentsPerRadian;
392         numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
393         float numParametricSegments = wangs_formula::root4(numParametricSegments_pow4);
394         numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
395         float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
396         if (numCombinedSegments > fMaxTessellationSegments) {
397             // The hardware doesn't support enough segments for this curve. Chop and recurse.
398             if (maxDepth < 0) {
399                 // Decide on an extremely conservative upper bound for when to quit chopping. This
400                 // is solely to protect us from infinite recursion in instances where FP error
401                 // prevents us from chopping at the correct midtangent.
402                 maxDepth = sk_float_nextlog2(numParametricSegments) +
403                            sk_float_nextlog2(numRadialSegments) + 1;
404                 maxDepth = std::max(maxDepth, 1);
405             }
406             if (w == 1) {
407                 SkPoint chops[5];
408                 if (numParametricSegments >= numRadialSegments) {
409                     SkChopQuadAtHalf(p, chops);
410                 } else {
411                     SkChopQuadAtMidTangent(p, chops);
412                 }
413                 this->internalConicPatchesTo(prevJoinType, chops, 1, maxDepth - 1);
414                 this->internalConicPatchesTo(JoinType::kBowtie, chops + 2, 1, maxDepth - 1);
415             } else {
416                 SkConic conic(p, w);
417                 float chopT = (numParametricSegments >= numRadialSegments) ? .5f
418                                                                            : conic.findMidTangent();
419                 SkConic chops[2];
420                 if (conic.chopAt(chopT, chops)) {
421                     this->internalConicPatchesTo(prevJoinType, chops[0].fPts, chops[0].fW,
422                                                   maxDepth - 1);
423                     this->internalConicPatchesTo(JoinType::kBowtie, chops[1].fPts, chops[1].fW,
424                                                   maxDepth - 1);
425                 }
426             }
427             return;
428         }
429 
430         this->internalPatchTo(prevJoinType, (numCombinedSegments <= fMaxCombinedSegments_withJoin),
431                               asPatch, p[2]);
432     }
433 
434     // Recursively chops the given cubic and its previous join until the segments fit in
435     // tessellation patches. The cubic must be convex and must not rotate more than 180 degrees.
internalCubicConvex180PatchesTo(JoinType prevJoinType,const SkPoint p[4],int maxDepth=-1)436     void internalCubicConvex180PatchesTo(JoinType prevJoinType, const SkPoint p[4],
437                                          int maxDepth = -1) {
438         // The stroke tessellation shader assigns special meaning to p0==p1==p2 and p1==p2==p3. If
439         // this is the case then we need to rewrite the cubic.
440         if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
441             this->writeLineTo(prevJoinType, p[0], p[3]);
442             return;
443         }
444 
445         float numParametricSegments_pow4 = wangs_formula::cubic_pow4(fParametricPrecision, p);
446         if (this->stroke180FitsInPatch(numParametricSegments_pow4) || maxDepth == 0) {
447             this->internalPatchTo(prevJoinType,
448                                   this->stroke180FitsInPatch_withJoin(numParametricSegments_pow4),
449                                   p, p[3]);
450             return;
451         }
452 
453         // We still might have enough tessellation segments to render the curve. Check again with
454         // its actual rotation.
455         float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRadian;
456         numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
457         float numParametricSegments = wangs_formula::root4(numParametricSegments_pow4);
458         numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
459         float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
460         if (numCombinedSegments > fMaxTessellationSegments) {
461             // The hardware doesn't support enough segments for this curve. Chop and recurse.
462             SkPoint chops[7];
463             if (maxDepth < 0) {
464                 // Decide on an extremely conservative upper bound for when to quit chopping. This
465                 // is solely to protect us from infinite recursion in instances where FP error
466                 // prevents us from chopping at the correct midtangent.
467                 maxDepth = sk_float_nextlog2(numParametricSegments) +
468                            sk_float_nextlog2(numRadialSegments) + 1;
469                 maxDepth = std::max(maxDepth, 1);
470             }
471             if (numParametricSegments >= numRadialSegments) {
472                 SkChopCubicAtHalf(p, chops);
473             } else {
474                 SkChopCubicAtMidTangent(p, chops);
475             }
476             this->internalCubicConvex180PatchesTo(prevJoinType, chops, maxDepth - 1);
477             this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3, maxDepth - 1);
478             return;
479         }
480 
481         this->internalPatchTo(prevJoinType, (numCombinedSegments <= fMaxCombinedSegments_withJoin),
482                               p, p[3]);
483     }
484 
485     // Writes out the given stroke patch exactly as provided, without chopping or checking the
486     // number of segments. Possibly chops its previous join until the segments fit in tessellation
487     // patches. It is valid for prevJoinType to be kBowtie.
internalPatchTo(JoinType prevJoinType,bool prevJoinFitsInPatch,const SkPoint p[4],SkPoint endPt)488     void internalPatchTo(JoinType prevJoinType, bool prevJoinFitsInPatch, const SkPoint p[4],
489                          SkPoint endPt) {
490         if (prevJoinType == JoinType::kBowtie) {
491             SkASSERT(fHasLastControlPoint);
492             // Bowtie joins are only used on internal chops, and internal chops almost always have
493             // continuous tangent angles (i.e., the ending tangent of the first chop and the
494             // beginning tangent of the second both point in the same direction). The tangents will
495             // only ever not point in the same direction if we chopped at a cusp point, so that's
496             // the only time we actually need a bowtie.
497             SkPoint nextControlPoint = (p[1] == p[0]) ? p[2] : p[1];
498             SkVector a = p[0] - fLastControlPoint;
499             SkVector b = nextControlPoint - p[0];
500             float ab_cosTheta = a.dot(b);
501             float ab_pow2 = a.dot(a) * b.dot(b);
502             // To check if tangents 'a' and 'b' do not point in the same direction, any of the
503             // following formulas work:
504             //
505             //          0 != theta
506             //          1 != cosTheta
507             //          1 != cosTheta * abs(cosTheta)  [Still false when cosTheta == -1]
508             //
509             // Introducing a slop term for fuzzy equality gives:
510             //
511             //          1 !~= cosTheta * abs(cosTheta)                [tolerance = epsilon]
512             //     (ab)^2 !~= (ab)^2 * cosTheta * abs(cosTheta)       [tolerance = (ab)^2 * epsilon]
513             //     (ab)^2 !~= (ab * cosTheta) * (ab * abs(cosTheta))  [tolerance = (ab)^2 * epsilon]
514             //     (ab)^2 !~= (ab * cosTheta) * abs(ab * cosTheta)    [tolerance = (ab)^2 * epsilon]
515             //
516             // Since we also scale the tolerance, the formula is unaffected by the magnitude of the
517             // tangent vectors. (And we can fold "ab" in to the abs() because it's always positive.)
518             if (!SkScalarNearlyEqual(ab_pow2, ab_cosTheta * fabsf(ab_cosTheta),
519                                      ab_pow2 * SK_ScalarNearlyZero)) {
520                 this->internalJoinTo(JoinType::kBowtie, p[0], nextControlPoint);
521                 fLastControlPoint = p[0];  // Disables the join section of this patch.
522                 prevJoinFitsInPatch = true;
523             }
524         }
525 
526         this->writePatchTo(prevJoinFitsInPatch, p, (p[2] != endPt) ? p[2] : p[1]);
527     }
528 
529     // Recursively chops the given join until the segments fit in tessellation patches.
internalJoinTo(JoinType joinType,SkPoint junctionPoint,SkPoint nextControlPoint,int maxDepth=-1)530     void internalJoinTo(JoinType joinType, SkPoint junctionPoint, SkPoint nextControlPoint,
531                         int maxDepth = -1) {
532         if (!fHasLastControlPoint) {
533             // The first stroke doesn't have a previous join.
534             return;
535         }
536 
537         if (!fSoloRoundJoinAlwaysFitsInPatch && maxDepth != 0 &&
538             (joinType == JoinType::kRound || joinType == JoinType::kBowtie)) {
539             SkVector tan0 = junctionPoint - fLastControlPoint;
540             SkVector tan1 = nextControlPoint - junctionPoint;
541             float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
542             float numRadialSegments = rotation * fNumRadialSegmentsPerRadian;
543             if (numRadialSegments > fMaxTessellationSegments) {
544                 // This is a round join that requires more segments than the tessellator supports.
545                 // Split it and recurse.
546                 if (maxDepth < 0) {
547                     // Decide on an upper bound for when to quit chopping. This is solely to protect
548                     // us from infinite recursion due to FP precision issues.
549                     maxDepth = sk_float_nextlog2(numRadialSegments / fMaxTessellationSegments);
550                     maxDepth = std::max(maxDepth, 1);
551                 }
552                 // Find the bisector so we can split the join in half.
553                 SkPoint bisector = SkFindBisector(tan0, tan1);
554                 // c0 will be the "next" control point for the first join half, and c1 will be the
555                 // "previous" control point for the second join half.
556                 SkPoint c0, c1;
557                 // FIXME(skia:11347): This hack ensures "c0 - junctionPoint" gives the exact same
558                 // ieee fp32 vector as "-(c1 - junctionPoint)". Tessellated stroking is becoming
559                 // less experimental, so t's time to think of a cleaner method to avoid T-junctions
560                 // when we chop joins.
561                 int maxAttempts = 10;
562                 do {
563                     bisector = (junctionPoint + bisector) - (junctionPoint - bisector);
564                     c0 = junctionPoint + bisector;
565                     c1 = junctionPoint - bisector;
566                 } while (c0 - junctionPoint != -(c1 - junctionPoint) && --maxAttempts);
567                 // First join half.
568                 this->internalJoinTo(joinType, junctionPoint, c0, maxDepth - 1);
569                 fLastControlPoint = c1;
570                 // Second join half.
571                 this->internalJoinTo(joinType, junctionPoint, nextControlPoint, maxDepth - 1);
572                 return;
573             }
574         }
575 
576         // We should never write out joins before the first curve.
577         SkASSERT(fHasLastControlPoint);
578 
579         if (VertexWriter patchWriter = fChunkBuilder.appendVertex()) {
580             patchWriter << fLastControlPoint << junctionPoint;
581             if (joinType == JoinType::kBowtie) {
582                 // {prevControlPoint, [p0, p0, p0, p3]} is a reserved patch pattern that means this
583                 // patch is a bowtie. The bowtie is anchored on p0 and its tangent angles go from
584                 // (p0 - prevControlPoint) to (p3 - p0).
585                 patchWriter << junctionPoint << junctionPoint;
586             } else {
587                 // {prevControlPoint, [p0, p3, p3, p3]} is a reserved patch pattern that means this
588                 // patch is a join only (no curve sections in the patch). The join is anchored on p0
589                 // and its tangent angles go from (p0 - prevControlPoint) to (p3 - p0).
590                 patchWriter << nextControlPoint << nextControlPoint;
591             }
592             patchWriter << (nextControlPoint);
593             this->writeDynamicAttribs(&patchWriter);
594         }
595 
596         fLastControlPoint = nextControlPoint;
597     }
598 
writeDynamicAttribs(VertexWriter * patchWriter)599     SK_ALWAYS_INLINE void writeDynamicAttribs(VertexWriter* patchWriter) {
600         if (fAttribs & PatchAttribs::kStrokeParams) {
601             *patchWriter << fDynamicStroke;
602         }
603         if (fAttribs & PatchAttribs::kColor) {
604             *patchWriter << fDynamicColor;
605         }
606         SkASSERT(!(fAttribs & PatchAttribs::kExplicitCurveType));
607     }
608 
discardStroke(const SkPoint p[],int numPoints)609     void discardStroke(const SkPoint p[], int numPoints) {
610         if (!fHasLastControlPoint) {
611             // This disables the first join, if any. (The first join gets added as a standalone
612             // patch during close(), but setting fCurrContourFirstControlPoint to p[0] causes us to
613             // skip that join if we attempt to add it later.)
614             fCurrContourFirstControlPoint = p[0];
615             fHasLastControlPoint = true;
616         }
617         // Set fLastControlPoint to the next stroke's p0 (which will be equal to the final point of
618         // this stroke). This has the effect of disabling the next stroke's join.
619         fLastControlPoint = p[numPoints - 1];
620     }
621 
622     const PatchAttribs fAttribs;
623     GrVertexChunkBuilder fChunkBuilder;
624 
625     // The maximum number of tessellation segments the hardware can emit for a single patch.
626     const int fMaxTessellationSegments;
627 
628     // This is the precision value, adjusted for the view matrix, to use with Wang's formulas when
629     // determining how many parametric segments a curve will require.
630     const float fParametricPrecision;
631 
632     // Number of radial segments required for each radian of rotation in order to look smooth with
633     // the current stroke radius.
634     float fNumRadialSegmentsPerRadian;
635 
636     // These arrays contain worst-case numbers of parametric segments, raised to the 4th power, that
637     // our hardware can support for the current stroke radius. They assume curve rotations of 180
638     // and 360 degrees respectively. These are used for "quick accepts" that allow us to send almost
639     // all curves directly to the hardware without having to chop. We raise to the 4th power because
640     // the "pow4" variants of Wang's formula are the quickest to evaluate.
641     float fMaxParametricSegments_pow4[2];  // Values for strokes that rotate 180 and 360 degrees.
642     float fMaxParametricSegments_pow4_withJoin[2];  // For strokes that rotate 180 and 360 degrees.
643 
644     // Maximum number of segments we can allocate for a stroke if we are stuffing it in a patch
645     // together with a worst-case join.
646     float fMaxCombinedSegments_withJoin;
647 
648     // Additional info on the current stroke radius/join type.
649     bool fSoloRoundJoinAlwaysFitsInPatch;
650     JoinType fStrokeJoinType;
651 
652     // Variables related to the specific contour that we are currently iterating during
653     // prepareBuffers().
654     bool fHasLastControlPoint = false;
655     SkPoint fCurrContourStartPoint;
656     SkPoint fCurrContourFirstControlPoint;
657     SkPoint fLastControlPoint;
658 
659     // Values for the current dynamic state (if any) that will get written out with each patch.
660     StrokeParams fDynamicStroke;
661     GrVertexColor fDynamicColor;
662 };
663 
cubic_has_cusp(const SkPoint p[4])664 SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) {
665     float2 p0 = skvx::bit_pun<float2>(p[0]);
666     float2 p1 = skvx::bit_pun<float2>(p[1]);
667     float2 p2 = skvx::bit_pun<float2>(p[2]);
668     float2 p3 = skvx::bit_pun<float2>(p[3]);
669 
670     // See GrPathUtils::findCubicConvex180Chops() for the math.
671     float2 C = p1 - p0;
672     float2 D = p2 - p1;
673     float2 E = p3 - p0;
674     float2 B = D - C;
675     float2 A = -3*D + E;
676 
677     float a = cross(A, B);
678     float b = cross(A, C);
679     float c = cross(B, C);
680     float discr = b*b - 4*a*c;
681 
682     // If -cuspThreshold <= discr <= cuspThreshold, it means the two roots are within a distance of
683     // 2^-11 from one another in parametric space. This is close enough for our purposes to take the
684     // slow codepath that knows how to handle cusps.
685     constexpr static float kEpsilon = 1.f / (1 << 11);
686     float cuspThreshold = (2*kEpsilon) * a;
687     cuspThreshold *= cuspThreshold;
688 
689     return fabsf(discr) <= cuspThreshold &&
690            // The most common type of cusp we encounter is when p0==p1 or p2==p3. Unless the curve
691            // is a flat line (a==b==c==0), these don't actually need special treatment because the
692            // cusp occurs at t=0 or t=1.
693            (!(skvx::all(p0 == p1) || skvx::all(p2 == p3)) || (a == 0 && b == 0 && c == 0));
694 }
695 
696 }  // namespace
697 
698 
prepare(GrMeshDrawTarget * target,const SkMatrix & shaderMatrix,std::array<float,2> matrixMinMaxScales,PathStrokeList * pathStrokeList,int totalCombinedVerbCnt)699 int StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target,
700                                        const SkMatrix& shaderMatrix,
701                                        std::array<float,2> matrixMinMaxScales,
702                                        PathStrokeList* pathStrokeList,
703                                        int totalCombinedVerbCnt) {
704     using JoinType = PatchWriter::JoinType;
705 
706     // Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps.
707     int strokePreallocCount = totalCombinedVerbCnt * 5/4;
708     int capPreallocCount = 8;
709     int minPatchesPerChunk = strokePreallocCount + capPreallocCount;
710     PatchWriter patchWriter(fAttribs,
711                             target,
712                             shaderMatrix,
713                             matrixMinMaxScales[1],
714                             &fPatchChunks,
715                             sizeof(SkPoint) * 5 + PatchAttribsStride(fAttribs),
716                             minPatchesPerChunk);
717 
718     if (!(fAttribs & PatchAttribs::kStrokeParams)) {
719         // Strokes are static. Calculate tolerances once.
720         const SkStrokeRec& stroke = pathStrokeList->fStroke;
721         float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
722                                                                        stroke.getWidth());
723         float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
724                 patchWriter.parametricPrecision(), localStrokeWidth);
725         patchWriter.updateTolerances(numRadialSegmentsPerRadian, stroke.getJoin());
726     }
727 
728     // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
729     // have dynamic strokes.
730     StrokeToleranceBuffer toleranceBuffer(patchWriter.parametricPrecision());
731 
732     for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
733         const SkStrokeRec& stroke = pathStroke->fStroke;
734         if (fAttribs & PatchAttribs::kStrokeParams) {
735             // Strokes are dynamic. Update tolerances with every new stroke.
736             patchWriter.updateTolerances(toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke),
737                                          stroke.getJoin());
738             patchWriter.updateDynamicStroke(stroke);
739         }
740         if (fAttribs & PatchAttribs::kColor) {
741             patchWriter.updateDynamicColor(pathStroke->fColor);
742         }
743 
744         const SkPath& path = pathStroke->fPath;
745         bool contourIsEmpty = true;
746         for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
747             bool prevJoinFitsInPatch;
748             SkPoint scratchPts[4];
749             const SkPoint* patchPts;
750             SkPoint endControlPoint;
751             switch (verb) {
752                 case SkPathVerb::kMove:
753                     // "A subpath ... consisting of a single moveto shall not be stroked."
754                     // https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
755                     if (!contourIsEmpty) {
756                         patchWriter.writeCaps(p[-1], shaderMatrix, stroke);
757                     }
758                     patchWriter.moveTo(p[0]);
759                     contourIsEmpty = true;
760                     continue;
761                 case SkPathVerb::kClose:
762                     patchWriter.writeClose(p[0], shaderMatrix, stroke);
763                     contourIsEmpty = true;
764                     continue;
765                 case SkPathVerb::kLine:
766                     // Set this to false first, before the upcoming continue might disrupt our flow.
767                     contourIsEmpty = false;
768                     if (p[0] == p[1]) {
769                         continue;
770                     }
771                     prevJoinFitsInPatch = patchWriter.lineFitsInPatch_withJoin();
772                     scratchPts[0] = scratchPts[1] = p[0];
773                     scratchPts[2] = scratchPts[3] = p[1];
774                     patchPts = scratchPts;
775                     endControlPoint = p[0];
776                     break;
777                 case SkPathVerb::kQuad: {
778                     contourIsEmpty = false;
779                     if (p[1] == p[0] || p[1] == p[2]) {
780                         // Zero-length paths need special treatment because they are spec'd to
781                         // behave differently. If the control point is colocated on an endpoint then
782                         // this might end up being the case. Fall back on a lineTo and let it make
783                         // the final check.
784                         patchWriter.writeLineTo(p[0], p[2]);
785                         continue;
786                     }
787                     if (GrPathUtils::conicHasCusp(p)) {
788                         // Cusps are rare, but the tessellation shader can't handle them. Chop the
789                         // curve into segments that the shader can handle.
790                         SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
791                         patchWriter.writeLineTo(p[0], cusp);
792                         patchWriter.writeLineTo(JoinType::kBowtie, cusp, p[2]);
793                         continue;
794                     }
795                     float numParametricSegments_pow4 =
796                             wangs_formula::quadratic_pow4(patchWriter.parametricPrecision(), p);
797                     if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
798                         // The curve requires more tessellation segments than the hardware can
799                         // support. This is rare. Recursively chop until each sub-curve fits.
800                         patchWriter.writeConicPatchesTo(p, 1);
801                         continue;
802                     }
803                     // The curve fits in a single tessellation patch. This is the most common case.
804                     // Write it out directly.
805                     prevJoinFitsInPatch = patchWriter.stroke180FitsInPatch_withJoin(
806                             numParametricSegments_pow4);
807                     GrPathUtils::convertQuadToCubic(p, scratchPts);
808                     patchPts = scratchPts;
809                     endControlPoint = patchPts[2];
810                     break;
811                 }
812                 case SkPathVerb::kConic: {
813                     contourIsEmpty = false;
814                     if (p[1] == p[0] || p[1] == p[2]) {
815                         // Zero-length paths need special treatment because they are spec'd to
816                         // behave differently. If the control point is colocated on an endpoint then
817                         // this might end up being the case. Fall back on a lineTo and let it make
818                         // the final check.
819                         patchWriter.writeLineTo(p[0], p[2]);
820                         continue;
821                     }
822                     if (GrPathUtils::conicHasCusp(p)) {
823                         // Cusps are rare, but the tessellation shader can't handle them. Chop the
824                         // curve into segments that the shader can handle.
825                         SkConic conic(p, *w);
826                         SkPoint cusp = conic.evalAt(conic.findMidTangent());
827                         patchWriter.writeLineTo(p[0], cusp);
828                         patchWriter.writeLineTo(JoinType::kBowtie, cusp, p[2]);
829                         continue;
830                     }
831                     // For now, the tessellation shader still uses Wang's quadratic formula when it
832                     // draws conics.
833                     // TODO: Update here when the shader starts using the real conic formula.
834                     float n = wangs_formula::conic_pow2(patchWriter.parametricPrecision(), p, *w);
835                     float numParametricSegments_pow4 = n*n;
836                     if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
837                         // The curve requires more tessellation segments than the hardware can
838                         // support. This is rare. Recursively chop until each sub-curve fits.
839                         patchWriter.writeConicPatchesTo(p, *w);
840                         continue;
841                     }
842                     // The curve fits in a single tessellation patch. This is the most common
843                     // case. Write it out directly.
844                     prevJoinFitsInPatch = patchWriter.stroke180FitsInPatch_withJoin(
845                             numParametricSegments_pow4);
846                     GrTessellationShader::WriteConicPatch(p, *w, scratchPts);
847                     patchPts = scratchPts;
848                     endControlPoint = p[1];
849                     break;
850                 }
851                 case SkPathVerb::kCubic: {
852                     contourIsEmpty = false;
853                     if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
854                         // The stroke tessellation shader assigns special meaning to p0==p1==p2 and
855                         // p1==p2==p3. If this is the case then we need to rewrite the cubic.
856                         patchWriter.writeLineTo(p[0], p[3]);
857                         continue;
858                     }
859                     float numParametricSegments_pow4 =
860                             wangs_formula::cubic_pow4(patchWriter.parametricPrecision(), p);
861                     if (!patchWriter.stroke360FitsInPatch(numParametricSegments_pow4) ||
862                         cubic_has_cusp(p)) {
863                         // Either the curve requires more tessellation segments than the hardware
864                         // can support, or it has cusp(s). Either case is rare. Chop it into
865                         // sections that rotate 180 degrees or less (which will naturally be the
866                         // cusp points if there are any), and then recursively chop each section
867                         // until it fits.
868                         patchWriter.writeCubicConvex180PatchesTo(p);
869                         continue;
870                     }
871                     // The curve fits in a single tessellation patch. This is the most common case.
872                     // Write it out directly.
873                     prevJoinFitsInPatch = patchWriter.stroke360FitsInPatch_withJoin(
874                             numParametricSegments_pow4);
875                     patchPts = p;
876                     endControlPoint = (p[2] != p[3]) ? p[2] : p[1];
877                     break;
878                 }
879             }
880             patchWriter.writePatchTo(prevJoinFitsInPatch, patchPts, endControlPoint);
881         }
882         if (!contourIsEmpty) {
883             const SkPoint* p = SkPathPriv::PointData(path);
884             patchWriter.writeCaps(p[path.countPoints() - 1], shaderMatrix, stroke);
885         }
886     }
887     return 0;
888 }
889 
890 #if SK_GPU_V1
draw(GrOpFlushState * flushState) const891 void StrokeHardwareTessellator::draw(GrOpFlushState* flushState) const {
892     for (const auto& vertexChunk : fPatchChunks) {
893         flushState->bindBuffers(nullptr, nullptr, vertexChunk.fBuffer);
894         flushState->draw(vertexChunk.fCount, vertexChunk.fBase);
895     }
896 }
897 #endif
898 
899 }  // namespace skgpu
900