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