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