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