1 /*
2 * Copyright 2021 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/Tessellation.h"
9
10 #include "include/core/SkPath.h"
11 #include "src/base/SkUtils.h"
12 #include "src/core/SkGeometry.h"
13 #include "src/core/SkPathPriv.h"
14 #include "src/gpu/BufferWriter.h"
15 #include "src/gpu/tessellate/CullTest.h"
16 #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
17 #include "src/gpu/tessellate/WangsFormula.h"
18
19 namespace skgpu::tess {
20
21 namespace {
22
23 using float2 = skvx::float2;
24 using float4 = skvx::float4;
25
26 // This value only protects us against getting stuck in infinite recursion due to fp32 precision
27 // issues. Mathematically, every curve should reduce to manageable visible sections in O(log N)
28 // chops, where N is the the magnitude of its control points.
29 //
30 // But, to define a protective upper bound, a cubic can enter or exit the viewport as many as 6
31 // times. So we may need to refine the curve (via binary search chopping at T=.5) up to 6 times.
32 //
33 // Furthermore, chopping a cubic at T=.5 may only reduce its length by 1/8 (.5^3), so we may require
34 // up to 6 chops in order to reduce the length by 1/2.
35 constexpr static int kMaxChopsPerCurve = 128/*magnitude of +fp32_max - -fp32_max*/ *
36 6/*max number of chops to reduce the length by half*/ *
37 6/*max number of viewport boundary crosses*/;
38
39 // Writes a new path, chopping as necessary so no verbs require more segments than
40 // kMaxTessellationSegmentsPerCurve. Curves completely outside the viewport are flattened into
41 // lines.
42 class PathChopper {
43 public:
PathChopper(float tessellationPrecision,const SkMatrix & matrix,const SkRect & viewport)44 PathChopper(float tessellationPrecision, const SkMatrix& matrix, const SkRect& viewport)
45 : fTessellationPrecision(tessellationPrecision)
46 , fCullTest(viewport, matrix)
47 , fVectorXform(matrix) {
48 fPath.setIsVolatile(true);
49 }
50
path() const51 SkPath path() const { return fPath; }
52
moveTo(SkPoint p)53 void moveTo(SkPoint p) { fPath.moveTo(p); }
lineTo(const SkPoint p[2])54 void lineTo(const SkPoint p[2]) { fPath.lineTo(p[1]); }
close()55 void close() { fPath.close(); }
56
quadTo(const SkPoint quad[3])57 void quadTo(const SkPoint quad[3]) {
58 SkASSERT(fPointStack.empty());
59 // Use a heap stack to recursively chop the quad into manageable, on-screen segments.
60 fPointStack.push_back_n(3, quad);
61 int numChops = 0;
62 while (!fPointStack.empty()) {
63 const SkPoint* p = fPointStack.end() - 3;
64 if (!fCullTest.areVisible3(p)) {
65 fPath.lineTo(p[2]);
66 } else {
67 float n4 = wangs_formula::quadratic_p4(fTessellationPrecision, p, fVectorXform);
68 if (n4 > kMaxSegmentsPerCurve_p4 && numChops < kMaxChopsPerCurve) {
69 SkPoint chops[5];
70 SkChopQuadAtHalf(p, chops);
71 fPointStack.pop_back_n(3);
72 fPointStack.push_back_n(3, chops+2);
73 fPointStack.push_back_n(3, chops);
74 ++numChops;
75 continue;
76 }
77 fPath.quadTo(p[1], p[2]);
78 }
79 fPointStack.pop_back_n(3);
80 }
81 }
82
conicTo(const SkPoint conic[3],float weight)83 void conicTo(const SkPoint conic[3], float weight) {
84 SkASSERT(fPointStack.empty());
85 SkASSERT(fWeightStack.empty());
86 // Use a heap stack to recursively chop the conic into manageable, on-screen segments.
87 fPointStack.push_back_n(3, conic);
88 fWeightStack.push_back(weight);
89 int numChops = 0;
90 while (!fPointStack.empty()) {
91 const SkPoint* p = fPointStack.end() - 3;
92 float w = fWeightStack.back();
93 if (!fCullTest.areVisible3(p)) {
94 fPath.lineTo(p[2]);
95 } else {
96 float n2 = wangs_formula::conic_p2(fTessellationPrecision, p, w, fVectorXform);
97 if (n2 > kMaxSegmentsPerCurve_p2 && numChops < kMaxChopsPerCurve) {
98 SkConic chops[2];
99 if (!SkConic(p,w).chopAt(.5, chops)) {
100 SkPoint line[2] = {p[0], p[2]};
101 this->lineTo(line);
102 continue;
103 }
104 fPointStack.pop_back_n(3);
105 fWeightStack.pop_back();
106 fPointStack.push_back_n(3, chops[1].fPts);
107 fWeightStack.push_back(chops[1].fW);
108 fPointStack.push_back_n(3, chops[0].fPts);
109 fWeightStack.push_back(chops[0].fW);
110 ++numChops;
111 continue;
112 }
113 fPath.conicTo(p[1], p[2], w);
114 }
115 fPointStack.pop_back_n(3);
116 fWeightStack.pop_back();
117 }
118 SkASSERT(fWeightStack.empty());
119 }
120
cubicTo(const SkPoint cubic[4])121 void cubicTo(const SkPoint cubic[4]) {
122 SkASSERT(fPointStack.empty());
123 // Use a heap stack to recursively chop the cubic into manageable, on-screen segments.
124 fPointStack.push_back_n(4, cubic);
125 int numChops = 0;
126 while (!fPointStack.empty()) {
127 SkPoint* p = fPointStack.end() - 4;
128 if (!fCullTest.areVisible4(p)) {
129 fPath.lineTo(p[3]);
130 } else {
131 float n4 = wangs_formula::cubic_p4(fTessellationPrecision, p, fVectorXform);
132 if (n4 > kMaxSegmentsPerCurve_p4 && numChops < kMaxChopsPerCurve) {
133 SkPoint chops[7];
134 SkChopCubicAtHalf(p, chops);
135 fPointStack.pop_back_n(4);
136 fPointStack.push_back_n(4, chops+3);
137 fPointStack.push_back_n(4, chops);
138 ++numChops;
139 continue;
140 }
141 fPath.cubicTo(p[1], p[2], p[3]);
142 }
143 fPointStack.pop_back_n(4);
144 }
145 }
146
147 private:
148 const float fTessellationPrecision;
149 const CullTest fCullTest;
150 const wangs_formula::VectorXform fVectorXform;
151 SkPath fPath;
152
153 // Used for stack-based recursion (instead of using the runtime stack).
154 SkSTArray<8, SkPoint> fPointStack;
155 SkSTArray<2, float> fWeightStack;
156 };
157
158 } // namespace
159
PreChopPathCurves(float tessellationPrecision,const SkPath & path,const SkMatrix & matrix,const SkRect & viewport)160 SkPath PreChopPathCurves(float tessellationPrecision,
161 const SkPath& path,
162 const SkMatrix& matrix,
163 const SkRect& viewport) {
164 // If the viewport is exceptionally large, we could end up blowing out memory with an unbounded
165 // number of of chops. Therefore, we require that the viewport is manageable enough that a fully
166 // contained curve can be tessellated in kMaxTessellationSegmentsPerCurve or fewer. (Any larger
167 // and that amount of pixels wouldn't fit in memory anyway.)
168 SkASSERT(wangs_formula::worst_case_cubic(
169 tessellationPrecision,
170 viewport.width(),
171 viewport.height()) <= kMaxSegmentsPerCurve);
172 PathChopper chopper(tessellationPrecision, matrix, viewport);
173 for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
174 switch (verb) {
175 case SkPathVerb::kMove:
176 chopper.moveTo(p[0]);
177 break;
178 case SkPathVerb::kLine:
179 chopper.lineTo(p);
180 break;
181 case SkPathVerb::kQuad:
182 chopper.quadTo(p);
183 break;
184 case SkPathVerb::kConic:
185 chopper.conicTo(p, *w);
186 break;
187 case SkPathVerb::kCubic:
188 chopper.cubicTo(p);
189 break;
190 case SkPathVerb::kClose:
191 chopper.close();
192 break;
193 }
194 }
195 return chopper.path();
196 }
197
FindCubicConvex180Chops(const SkPoint pts[],float T[2],bool * areCusps)198 int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool* areCusps) {
199 SkASSERT(pts);
200 SkASSERT(T);
201 SkASSERT(areCusps);
202
203 // If a chop falls within a distance of "kEpsilon" from 0 or 1, throw it out. Tangents become
204 // unstable when we chop too close to the boundary. This works out because the tessellation
205 // shaders don't allow more than 2^10 parametric segments, and they snap the beginning and
206 // ending edges at 0 and 1. So if we overstep an inflection or point of 180-degree rotation by a
207 // fraction of a tessellation segment, it just gets snapped.
208 constexpr static float kEpsilon = 1.f / (1 << 11);
209 // Floating-point representation of "1 - 2*kEpsilon".
210 constexpr static uint32_t kIEEE_one_minus_2_epsilon = (127 << 23) - 2 * (1 << (24 - 11));
211 // Unfortunately we don't have a way to static_assert this, but we can runtime assert that the
212 // kIEEE_one_minus_2_epsilon bits are correct.
213 SkASSERT(sk_bit_cast<float>(kIEEE_one_minus_2_epsilon) == 1 - 2*kEpsilon);
214
215 float2 p0 = skvx::bit_pun<float2>(pts[0]);
216 float2 p1 = skvx::bit_pun<float2>(pts[1]);
217 float2 p2 = skvx::bit_pun<float2>(pts[2]);
218 float2 p3 = skvx::bit_pun<float2>(pts[3]);
219
220 // Find the cubic's power basis coefficients. These define the bezier curve as:
221 //
222 // |T^3|
223 // Cubic(T) = x,y = |A 3B 3C| * |T^2| + P0
224 // |. . .| |T |
225 //
226 // And the tangent direction (scaled by a uniform 1/3) will be:
227 //
228 // |T^2|
229 // Tangent_Direction(T) = dx,dy = |A 2B C| * |T |
230 // |. . .| |1 |
231 //
232 float2 C = p1 - p0;
233 float2 D = p2 - p1;
234 float2 E = p3 - p0;
235 float2 B = D - C;
236 float2 A = -3*D + E;
237
238 // Now find the cubic's inflection function. There are inflections where F' x F'' == 0.
239 // We formulate this as a quadratic equation: F' x F'' == aT^2 + bT + c == 0.
240 // See: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
241 // NOTE: We only need the roots, so a uniform scale factor does not affect the solution.
242 float a = cross(A,B);
243 float b = cross(A,C);
244 float c = cross(B,C);
245 float b_over_minus_2 = -.5f * b;
246 float discr_over_4 = b_over_minus_2*b_over_minus_2 - a*c;
247
248 // If -cuspThreshold <= discr_over_4 <= cuspThreshold, it means the two roots are within
249 // kEpsilon of one another (in parametric space). This is close enough for our purposes to
250 // consider them a single cusp.
251 float cuspThreshold = a * (kEpsilon/2);
252 cuspThreshold *= cuspThreshold;
253
254 if (discr_over_4 < -cuspThreshold) {
255 // The curve does not inflect or cusp. This means it might rotate more than 180 degrees
256 // instead. Chop were rotation == 180 deg. (This is the 2nd root where the tangent is
257 // parallel to tan0.)
258 //
259 // Tangent_Direction(T) x tan0 == 0
260 // (AT^2 x tan0) + (2BT x tan0) + (C x tan0) == 0
261 // (A x C)T^2 + (2B x C)T + (C x C) == 0 [[because tan0 == P1 - P0 == C]]
262 // bT^2 + 2cT + 0 == 0 [[because A x C == b, B x C == c]]
263 // T = [0, -2c/b]
264 //
265 // NOTE: if C == 0, then C != tan0. But this is fine because the curve is definitely
266 // convex-180 if any points are colocated, and T[0] will equal NaN which returns 0 chops.
267 *areCusps = false;
268 float root = sk_ieee_float_divide(c, b_over_minus_2);
269 // Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
270 if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
271 T[0] = root;
272 return 1;
273 }
274 return 0;
275 }
276
277 *areCusps = (discr_over_4 <= cuspThreshold);
278 if (*areCusps) {
279 // The two roots are close enough that we can consider them a single cusp.
280 if (a != 0 || b_over_minus_2 != 0 || c != 0) {
281 // Pick the average of both roots.
282 float root = sk_ieee_float_divide(b_over_minus_2, a);
283 // Is "root" inside the range [kEpsilon, 1 - kEpsilon)?
284 if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
285 T[0] = root;
286 return 1;
287 }
288 return 0;
289 }
290
291 // The curve is a flat line. The standard inflection function doesn't detect cusps from flat
292 // lines. Find cusps by searching instead for points where the tangent is perpendicular to
293 // tan0. This will find any cusp point.
294 //
295 // dot(tan0, Tangent_Direction(T)) == 0
296 //
297 // |T^2|
298 // tan0 * |A 2B C| * |T | == 0
299 // |. . .| |1 |
300 //
301 float2 tan0 = skvx::if_then_else(C != 0, C, p2 - p0);
302 a = dot(tan0, A);
303 b_over_minus_2 = -dot(tan0, B);
304 c = dot(tan0, C);
305 discr_over_4 = std::max(b_over_minus_2*b_over_minus_2 - a*c, 0.f);
306 }
307
308 // Solve our quadratic equation to find where to chop. See the quadratic formula from
309 // Numerical Recipes in C.
310 float q = sqrtf(discr_over_4);
311 q = copysignf(q, b_over_minus_2);
312 q = q + b_over_minus_2;
313 float2 roots = float2{q,c} / float2{a,q};
314
315 auto inside = (roots > kEpsilon) & (roots < (1 - kEpsilon));
316 if (inside[0]) {
317 if (inside[1] && roots[0] != roots[1]) {
318 if (roots[0] > roots[1]) {
319 roots = skvx::shuffle<1,0>(roots); // Sort.
320 }
321 roots.store(T);
322 return 2;
323 }
324 T[0] = roots[0];
325 return 1;
326 }
327 if (inside[1]) {
328 T[0] = roots[1];
329 return 1;
330 }
331 return 0;
332 }
333
334 } // namespace skgpu::tess
335