/src/skia/src/gpu/tessellate/WangsFormula.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2020 Google Inc. |
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 | | #ifndef skgpu_tessellate_WangsFormula_DEFINED |
9 | | #define skgpu_tessellate_WangsFormula_DEFINED |
10 | | |
11 | | #include "include/core/SkM44.h" |
12 | | #include "include/core/SkMatrix.h" |
13 | | #include "include/core/SkPoint.h" |
14 | | #include "include/core/SkTypes.h" |
15 | | #include "src/base/SkFloatBits.h" |
16 | | #include "src/base/SkUtils.h" |
17 | | #include "src/base/SkVx.h" |
18 | | |
19 | | #include <math.h> |
20 | | #include <algorithm> |
21 | | #include <cstdint> |
22 | | #include <limits> |
23 | | |
24 | | #define AI [[maybe_unused]] SK_ALWAYS_INLINE |
25 | | |
26 | | // Wang's formula gives the minimum number of evenly spaced (in the parametric sense) line segments |
27 | | // that a bezier curve must be chopped into in order to guarantee all lines stay within a distance |
28 | | // of "1/precision" pixels from the true curve. Its definition for a bezier curve of degree "n" is |
29 | | // as follows: |
30 | | // |
31 | | // maxLength = max([length(p[i+2] - 2p[i+1] + p[i]) for (0 <= i <= n-2)]) |
32 | | // numParametricSegments = sqrt(maxLength * precision * n*(n - 1)/8) |
33 | | // |
34 | | // (Goldman, Ron. (2003). 5.6.3 Wang's Formula. "Pyramid Algorithms: A Dynamic Programming Approach |
35 | | // to Curves and Surfaces for Geometric Modeling". Morgan Kaufmann Publishers.) |
36 | | namespace skgpu::wangs_formula { |
37 | | |
38 | | // Returns the value by which to multiply length in Wang's formula. (See above.) |
39 | 0 | template<int Degree> constexpr float length_term(float precision) { |
40 | 0 | return (Degree * (Degree - 1) / 8.f) * precision; |
41 | 0 | } |
42 | 1.90M | template<int Degree> constexpr float length_term_p2(float precision) { |
43 | 1.90M | return ((Degree * Degree) * ((Degree - 1) * (Degree - 1)) / 64.f) * (precision * precision); |
44 | 1.90M | } float skgpu::wangs_formula::length_term_p2<3>(float) Line | Count | Source | 42 | 96.3k | template<int Degree> constexpr float length_term_p2(float precision) { | 43 | 96.3k | return ((Degree * Degree) * ((Degree - 1) * (Degree - 1)) / 64.f) * (precision * precision); | 44 | 96.3k | } |
float skgpu::wangs_formula::length_term_p2<2>(float) Line | Count | Source | 42 | 1.81M | template<int Degree> constexpr float length_term_p2(float precision) { | 43 | 1.81M | return ((Degree * Degree) * ((Degree - 1) * (Degree - 1)) / 64.f) * (precision * precision); | 44 | 1.81M | } |
|
45 | | |
46 | 0 | AI float root4(float x) { |
47 | 0 | return sqrtf(sqrtf(x)); |
48 | 0 | } |
49 | | |
50 | | // For finite positive x > 1, return ceil(log2(x)) otherwise, return 0. |
51 | | // For +/- NaN return 0. |
52 | | // For +infinity return 128. |
53 | | // For -infinity return 0. |
54 | | // |
55 | | // nextlog2((-inf..1]) -> 0 |
56 | | // nextlog2((1..2]) -> 1 |
57 | | // nextlog2((2..4]) -> 2 |
58 | | // nextlog2((4..8]) -> 3 |
59 | | // ... |
60 | 1.90M | AI int nextlog2(float x) { |
61 | 1.90M | if (x <= 1) { |
62 | 206k | return 0; |
63 | 206k | } |
64 | | |
65 | 1.70M | uint32_t bits = SkFloat2Bits(x); |
66 | 1.70M | static constexpr uint32_t kDigitsAfterBinaryPoint = std::numeric_limits<float>::digits - 1; |
67 | | |
68 | | // The constant is a significand of all 1s -- 0b0'00000000'111'1111111111'111111111. So, if |
69 | | // the significand of x is all 0s (and therefore an integer power of two) this will not |
70 | | // increment the exponent, but if it is just one ULP above the power of two the carry will |
71 | | // ripple into the exponent incrementing the exponent by 1. |
72 | 1.70M | bits += (1u << kDigitsAfterBinaryPoint) - 1u; |
73 | | |
74 | | // Shift the exponent down, and adjust it by the exponent offset so that 2^0 is really 0 instead |
75 | | // of 127. Remember that 1 was added to the exponent, if x is NaN, then the exponent will |
76 | | // carry a 1 into the sign bit during the addition to bits. Be sure to strip off the sign bit. |
77 | | // In addition, infinity is an exponent of all 1's, and a significand of all 0, so |
78 | | // the exponent is not affected during the addition to bits, and the exponent remains all 1's. |
79 | 1.70M | const int exp = ((bits >> kDigitsAfterBinaryPoint) & 0b1111'1111) - 127; |
80 | | |
81 | | // Return 0 for x <= 1. |
82 | 1.70M | return exp > 0 ? exp : 0; |
83 | 1.90M | } |
84 | | |
85 | | // Returns nextlog2(sqrt(x)): |
86 | | // |
87 | | // log2(sqrt(x)) == log2(x^(1/2)) == log2(x)/2 == log2(x)/log2(4) == log4(x) |
88 | | // |
89 | 0 | AI int nextlog4(float x) { |
90 | 0 | return (nextlog2(x) + 1) >> 1; |
91 | 0 | } |
92 | | |
93 | | // Returns nextlog2(sqrt(sqrt(x))): |
94 | | // |
95 | | // log2(sqrt(sqrt(x))) == log2(x^(1/4)) == log2(x)/4 == log2(x)/log2(16) == log16(x) |
96 | | // |
97 | 1.90M | AI int nextlog16(float x) { |
98 | 1.90M | return (nextlog2(x) + 3) >> 2; |
99 | 1.90M | } |
100 | | |
101 | | // Represents the upper-left 2x2 matrix of an affine transform for applying to vectors: |
102 | | // |
103 | | // VectorXform(p1 - p0) == M * float3(p1, 1) - M * float3(p0, 1) |
104 | | // |
105 | | class VectorXform { |
106 | | public: |
107 | 1.90M | AI VectorXform() : fC0{1.0f, 0.f}, fC1{0.f, 1.f} {} |
108 | 0 | AI explicit VectorXform(const SkMatrix& m) { *this = m; } |
109 | 0 | AI explicit VectorXform(const SkM44& m) { *this = m; } |
110 | | |
111 | 0 | AI VectorXform& operator=(const SkMatrix& m) { |
112 | 0 | SkASSERT(!m.hasPerspective()); |
113 | 0 | fC0 = {m.rc(0,0), m.rc(1,0)}; |
114 | 0 | fC1 = {m.rc(0,1), m.rc(1,1)}; |
115 | 0 | return *this; |
116 | 0 | } Unexecuted instantiation: skgpu::wangs_formula::VectorXform::operator=(SkMatrix const&) Unexecuted instantiation: skgpu::wangs_formula::VectorXform::operator=(SkMatrix const&) |
117 | 0 | AI VectorXform& operator=(const SkM44& m) { |
118 | 0 | SkASSERT(m.rc(3,0) == 0.f && m.rc(3,1) == 0.f && m.rc(3,2) == 0.f && m.rc(3,3) == 1.f); |
119 | 0 | fC0 = {m.rc(0,0), m.rc(1,0)}; |
120 | 0 | fC1 = {m.rc(0,1), m.rc(1,1)}; |
121 | 0 | return *this; |
122 | 0 | } Unexecuted instantiation: skgpu::wangs_formula::VectorXform::operator=(SkM44 const&) Unexecuted instantiation: skgpu::wangs_formula::VectorXform::operator=(SkM44 const&) |
123 | 1.81M | AI skvx::float2 operator()(skvx::float2 vector) const { |
124 | 1.81M | return fC0 * vector.x() + fC1 * vector.y(); |
125 | 1.81M | } |
126 | 96.3k | AI skvx::float4 operator()(skvx::float4 vectors) const { |
127 | 96.3k | return join(fC0 * vectors.x() + fC1 * vectors.y(), |
128 | 96.3k | fC0 * vectors.z() + fC1 * vectors.w()); |
129 | 96.3k | } |
130 | | private: |
131 | | // First and second columns of 2x2 matrix |
132 | | skvx::float2 fC0; |
133 | | skvx::float2 fC1; |
134 | | }; |
135 | | |
136 | | // Returns Wang's formula, raised to the 4th power, specialized for a quadratic curve. |
137 | | AI float quadratic_p4(float precision, |
138 | | skvx::float2 p0, skvx::float2 p1, skvx::float2 p2, |
139 | 1.81M | const VectorXform& vectorXform = VectorXform()) { |
140 | 1.81M | skvx::float2 v = -2*p1 + p0 + p2; |
141 | 1.81M | v = vectorXform(v); |
142 | 1.81M | skvx::float2 vv = v*v; |
143 | 1.81M | return (vv[0] + vv[1]) * length_term_p2<2>(precision); |
144 | 1.81M | } |
145 | | AI float quadratic_p4(float precision, |
146 | | const SkPoint pts[], |
147 | 1.81M | const VectorXform& vectorXform = VectorXform()) { |
148 | 1.81M | return quadratic_p4(precision, |
149 | 1.81M | sk_bit_cast<skvx::float2>(pts[0]), |
150 | 1.81M | sk_bit_cast<skvx::float2>(pts[1]), |
151 | 1.81M | sk_bit_cast<skvx::float2>(pts[2]), |
152 | 1.81M | vectorXform); |
153 | 1.81M | } |
154 | | |
155 | | // Returns Wang's formula specialized for a quadratic curve. |
156 | | AI float quadratic(float precision, |
157 | | const SkPoint pts[], |
158 | 0 | const VectorXform& vectorXform = VectorXform()) { |
159 | 0 | return root4(quadratic_p4(precision, pts, vectorXform)); |
160 | 0 | } |
161 | | |
162 | | // Returns the log2 value of Wang's formula specialized for a quadratic curve, rounded up to the |
163 | | // next int. |
164 | | AI int quadratic_log2(float precision, |
165 | | const SkPoint pts[], |
166 | 1.81M | const VectorXform& vectorXform = VectorXform()) { |
167 | | // nextlog16(x) == ceil(log2(sqrt(sqrt(x)))) |
168 | 1.81M | return nextlog16(quadratic_p4(precision, pts, vectorXform)); |
169 | 1.81M | } |
170 | | |
171 | | // Returns Wang's formula, raised to the 4th power, specialized for a cubic curve. |
172 | | AI float cubic_p4(float precision, |
173 | | skvx::float2 p0, skvx::float2 p1, skvx::float2 p2, skvx::float2 p3, |
174 | 96.3k | const VectorXform& vectorXform = VectorXform()) { |
175 | 96.3k | skvx::float4 p01{p0, p1}; |
176 | 96.3k | skvx::float4 p12{p1, p2}; |
177 | 96.3k | skvx::float4 p23{p2, p3}; |
178 | 96.3k | skvx::float4 v = -2*p12 + p01 + p23; |
179 | 96.3k | v = vectorXform(v); |
180 | 96.3k | skvx::float4 vv = v*v; |
181 | 96.3k | return std::max(vv[0] + vv[1], vv[2] + vv[3]) * length_term_p2<3>(precision); |
182 | 96.3k | } |
183 | | AI float cubic_p4(float precision, |
184 | | const SkPoint pts[], |
185 | 96.3k | const VectorXform& vectorXform = VectorXform()) { |
186 | 96.3k | return cubic_p4(precision, |
187 | 96.3k | sk_bit_cast<skvx::float2>(pts[0]), |
188 | 96.3k | sk_bit_cast<skvx::float2>(pts[1]), |
189 | 96.3k | sk_bit_cast<skvx::float2>(pts[2]), |
190 | 96.3k | sk_bit_cast<skvx::float2>(pts[3]), |
191 | 96.3k | vectorXform); |
192 | 96.3k | } |
193 | | |
194 | | // Returns Wang's formula specialized for a cubic curve. |
195 | | AI float cubic(float precision, |
196 | | const SkPoint pts[], |
197 | 0 | const VectorXform& vectorXform = VectorXform()) { |
198 | 0 | return root4(cubic_p4(precision, pts, vectorXform)); |
199 | 0 | } |
200 | | |
201 | | // Returns the log2 value of Wang's formula specialized for a cubic curve, rounded up to the next |
202 | | // int. |
203 | | AI int cubic_log2(float precision, |
204 | | const SkPoint pts[], |
205 | 96.3k | const VectorXform& vectorXform = VectorXform()) { |
206 | | // nextlog16(x) == ceil(log2(sqrt(sqrt(x)))) |
207 | 96.3k | return nextlog16(cubic_p4(precision, pts, vectorXform)); |
208 | 96.3k | } |
209 | | |
210 | | // Returns the maximum number of line segments a cubic with the given device-space bounding box size |
211 | | // would ever need to be divided into, raised to the 4th power. This is simply a special case of the |
212 | | // cubic formula where we maximize its value by placing control points on specific corners of the |
213 | | // bounding box. |
214 | 0 | AI float worst_case_cubic_p4(float precision, float devWidth, float devHeight) { |
215 | 0 | float kk = length_term_p2<3>(precision); |
216 | 0 | return 4*kk * (devWidth * devWidth + devHeight * devHeight); |
217 | 0 | } |
218 | | |
219 | | // Returns the maximum number of line segments a cubic with the given device-space bounding box size |
220 | | // would ever need to be divided into. |
221 | 0 | AI float worst_case_cubic(float precision, float devWidth, float devHeight) { |
222 | 0 | return root4(worst_case_cubic_p4(precision, devWidth, devHeight)); |
223 | 0 | } |
224 | | |
225 | | // Returns the maximum log2 number of line segments a cubic with the given device-space bounding box |
226 | | // size would ever need to be divided into. |
227 | 0 | AI int worst_case_cubic_log2(float precision, float devWidth, float devHeight) { |
228 | 0 | // nextlog16(x) == ceil(log2(sqrt(sqrt(x)))) |
229 | 0 | return nextlog16(worst_case_cubic_p4(precision, devWidth, devHeight)); |
230 | 0 | } |
231 | | |
232 | | // Returns Wang's formula specialized for a conic curve, raised to the second power. |
233 | | // Input points should be in projected space. |
234 | | // |
235 | | // This is not actually due to Wang, but is an analogue from (Theorem 3, corollary 1): |
236 | | // J. Zheng, T. Sederberg. "Estimating Tessellation Parameter Intervals for |
237 | | // Rational Curves and Surfaces." ACM Transactions on Graphics 19(1). 2000. |
238 | | AI float conic_p2(float precision, |
239 | | skvx::float2 p0, skvx::float2 p1, skvx::float2 p2, |
240 | | float w, |
241 | 0 | const VectorXform& vectorXform = VectorXform()) { |
242 | 0 | p0 = vectorXform(p0); |
243 | 0 | p1 = vectorXform(p1); |
244 | 0 | p2 = vectorXform(p2); |
245 | | |
246 | | // Compute center of bounding box in projected space |
247 | 0 | const skvx::float2 C = 0.5f * (min(min(p0, p1), p2) + max(max(p0, p1), p2)); |
248 | | |
249 | | // Translate by -C. This improves translation-invariance of the formula, |
250 | | // see Sec. 3.3 of cited paper |
251 | 0 | p0 -= C; |
252 | 0 | p1 -= C; |
253 | 0 | p2 -= C; |
254 | | |
255 | | // Compute max length |
256 | 0 | const float max_len = sqrtf(std::max(dot(p0, p0), std::max(dot(p1, p1), dot(p2, p2)))); |
257 | | |
258 | | |
259 | | // Compute forward differences |
260 | 0 | const skvx::float2 dp = -2*w*p1 + p0 + p2; |
261 | 0 | const float dw = fabsf(-2 * w + 2); |
262 | | |
263 | | // Compute numerator and denominator for parametric step size of linearization. Here, the |
264 | | // epsilon referenced from the cited paper is 1/precision. |
265 | 0 | const float rp_minus_1 = std::max(0.f, max_len * precision - 1); |
266 | 0 | const float numer = sqrtf(dot(dp, dp)) * precision + rp_minus_1 * dw; |
267 | 0 | const float denom = 4 * std::min(w, 1.f); |
268 | | |
269 | | // Number of segments = sqrt(numer / denom). |
270 | | // This assumes parametric interval of curve being linearized is [t0,t1] = [0, 1]. |
271 | | // If not, the number of segments is (tmax - tmin) / sqrt(denom / numer). |
272 | 0 | return numer / denom; |
273 | 0 | } |
274 | | AI float conic_p2(float precision, |
275 | | const SkPoint pts[], |
276 | | float w, |
277 | 0 | const VectorXform& vectorXform = VectorXform()) { |
278 | 0 | return conic_p2(precision, |
279 | 0 | sk_bit_cast<skvx::float2>(pts[0]), |
280 | 0 | sk_bit_cast<skvx::float2>(pts[1]), |
281 | 0 | sk_bit_cast<skvx::float2>(pts[2]), |
282 | 0 | w, |
283 | 0 | vectorXform); |
284 | 0 | } |
285 | | |
286 | | // Returns the value of Wang's formula specialized for a conic curve. |
287 | | AI float conic(float tolerance, |
288 | | const SkPoint pts[], |
289 | | float w, |
290 | 0 | const VectorXform& vectorXform = VectorXform()) { |
291 | 0 | return sqrtf(conic_p2(tolerance, pts, w, vectorXform)); |
292 | 0 | } |
293 | | |
294 | | // Returns the log2 value of Wang's formula specialized for a conic curve, rounded up to the next |
295 | | // int. |
296 | | AI int conic_log2(float tolerance, |
297 | | const SkPoint pts[], |
298 | | float w, |
299 | 0 | const VectorXform& vectorXform = VectorXform()) { |
300 | 0 | // nextlog4(x) == ceil(log2(sqrt(x))) |
301 | 0 | return nextlog4(conic_p2(tolerance, pts, w, vectorXform)); |
302 | 0 | } |
303 | | |
304 | | } // namespace skgpu::wangs_formula |
305 | | |
306 | | #undef AI |
307 | | |
308 | | #endif // skgpu_tessellate_WangsFormula_DEFINED |