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