1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2020 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "include/utils/SkRandom.h"
9cb93a386Sopenharmony_ci#include "src/core/SkGeometry.h"
10cb93a386Sopenharmony_ci#include "src/gpu/tessellate/WangsFormula.h"
11cb93a386Sopenharmony_ci#include "tests/Test.h"
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_cinamespace skgpu {
14cb93a386Sopenharmony_ci
15cb93a386Sopenharmony_ciconstexpr static float kPrecision = 4;  // 1/4 pixel max error.
16cb93a386Sopenharmony_ci
17cb93a386Sopenharmony_ciconst SkPoint kSerp[4] = {
18cb93a386Sopenharmony_ci        {285.625f, 499.687f}, {411.625f, 808.188f}, {1064.62f, 135.688f}, {1042.63f, 585.187f}};
19cb93a386Sopenharmony_ci
20cb93a386Sopenharmony_ciconst SkPoint kLoop[4] = {
21cb93a386Sopenharmony_ci        {635.625f, 614.687f}, {171.625f, 236.188f}, {1064.62f, 135.688f}, {516.625f, 570.187f}};
22cb93a386Sopenharmony_ci
23cb93a386Sopenharmony_ciconst SkPoint kQuad[4] = {
24cb93a386Sopenharmony_ci        {460.625f, 557.187f}, {707.121f, 209.688f}, {779.628f, 577.687f}};
25cb93a386Sopenharmony_ci
26cb93a386Sopenharmony_cistatic float wangs_formula_quadratic_reference_impl(float precision, const SkPoint p[3]) {
27cb93a386Sopenharmony_ci    float k = (2 * 1) / 8.f * precision;
28cb93a386Sopenharmony_ci    return sqrtf(k * (p[0] - p[1]*2 + p[2]).length());
29cb93a386Sopenharmony_ci}
30cb93a386Sopenharmony_ci
31cb93a386Sopenharmony_cistatic float wangs_formula_cubic_reference_impl(float precision, const SkPoint p[4]) {
32cb93a386Sopenharmony_ci    float k = (3 * 2) / 8.f * precision;
33cb93a386Sopenharmony_ci    return sqrtf(k * std::max((p[0] - p[1]*2 + p[2]).length(),
34cb93a386Sopenharmony_ci                              (p[1] - p[2]*2 + p[3]).length()));
35cb93a386Sopenharmony_ci}
36cb93a386Sopenharmony_ci
37cb93a386Sopenharmony_ci// Returns number of segments for linearized quadratic rational. This is an analogue
38cb93a386Sopenharmony_ci// to Wang's formula, taken from:
39cb93a386Sopenharmony_ci//
40cb93a386Sopenharmony_ci//   J. Zheng, T. Sederberg. "Estimating Tessellation Parameter Intervals for
41cb93a386Sopenharmony_ci//   Rational Curves and Surfaces." ACM Transactions on Graphics 19(1). 2000.
42cb93a386Sopenharmony_ci// See Thm 3, Corollary 1.
43cb93a386Sopenharmony_ci//
44cb93a386Sopenharmony_ci// Input points should be in projected space.
45cb93a386Sopenharmony_cistatic float wangs_formula_conic_reference_impl(float precision,
46cb93a386Sopenharmony_ci                                                const SkPoint P[3],
47cb93a386Sopenharmony_ci                                                const float w) {
48cb93a386Sopenharmony_ci    // Compute center of bounding box in projected space
49cb93a386Sopenharmony_ci    float min_x = P[0].fX, max_x = min_x,
50cb93a386Sopenharmony_ci          min_y = P[0].fY, max_y = min_y;
51cb93a386Sopenharmony_ci    for (int i = 1; i < 3; i++) {
52cb93a386Sopenharmony_ci        min_x = std::min(min_x, P[i].fX);
53cb93a386Sopenharmony_ci        max_x = std::max(max_x, P[i].fX);
54cb93a386Sopenharmony_ci        min_y = std::min(min_y, P[i].fY);
55cb93a386Sopenharmony_ci        max_y = std::max(max_y, P[i].fY);
56cb93a386Sopenharmony_ci    }
57cb93a386Sopenharmony_ci    const SkPoint C = SkPoint::Make(0.5f * (min_x + max_x), 0.5f * (min_y + max_y));
58cb93a386Sopenharmony_ci
59cb93a386Sopenharmony_ci    // Translate control points and compute max length
60cb93a386Sopenharmony_ci    SkPoint tP[3] = {P[0] - C, P[1] - C, P[2] - C};
61cb93a386Sopenharmony_ci    float max_len = 0;
62cb93a386Sopenharmony_ci    for (int i = 0; i < 3; i++) {
63cb93a386Sopenharmony_ci        max_len = std::max(max_len, tP[i].length());
64cb93a386Sopenharmony_ci    }
65cb93a386Sopenharmony_ci    SkASSERT(max_len > 0);
66cb93a386Sopenharmony_ci
67cb93a386Sopenharmony_ci    // Compute delta = parametric step size of linearization
68cb93a386Sopenharmony_ci    const float eps = 1 / precision;
69cb93a386Sopenharmony_ci    const float r_minus_eps = std::max(0.f, max_len - eps);
70cb93a386Sopenharmony_ci    const float min_w = std::min(w, 1.f);
71cb93a386Sopenharmony_ci    const float numer = 4 * min_w * eps;
72cb93a386Sopenharmony_ci    const float denom =
73cb93a386Sopenharmony_ci            (tP[2] - tP[1] * 2 * w + tP[0]).length() + r_minus_eps * std::abs(1 - 2 * w + 1);
74cb93a386Sopenharmony_ci    const float delta = sqrtf(numer / denom);
75cb93a386Sopenharmony_ci
76cb93a386Sopenharmony_ci    // Return corresponding num segments in the interval [tmin,tmax]
77cb93a386Sopenharmony_ci    constexpr float tmin = 0, tmax = 1;
78cb93a386Sopenharmony_ci    SkASSERT(delta > 0);
79cb93a386Sopenharmony_ci    return (tmax - tmin) / delta;
80cb93a386Sopenharmony_ci}
81cb93a386Sopenharmony_ci
82cb93a386Sopenharmony_cistatic void for_random_matrices(SkRandom* rand, std::function<void(const SkMatrix&)> f) {
83cb93a386Sopenharmony_ci    SkMatrix m;
84cb93a386Sopenharmony_ci    m.setIdentity();
85cb93a386Sopenharmony_ci    f(m);
86cb93a386Sopenharmony_ci
87cb93a386Sopenharmony_ci    for (int i = -10; i <= 30; ++i) {
88cb93a386Sopenharmony_ci        for (int j = -10; j <= 30; ++j) {
89cb93a386Sopenharmony_ci            m.setScaleX(std::ldexp(1 + rand->nextF(), i));
90cb93a386Sopenharmony_ci            m.setSkewX(0);
91cb93a386Sopenharmony_ci            m.setSkewY(0);
92cb93a386Sopenharmony_ci            m.setScaleY(std::ldexp(1 + rand->nextF(), j));
93cb93a386Sopenharmony_ci            f(m);
94cb93a386Sopenharmony_ci
95cb93a386Sopenharmony_ci            m.setScaleX(std::ldexp(1 + rand->nextF(), i));
96cb93a386Sopenharmony_ci            m.setSkewX(std::ldexp(1 + rand->nextF(), (j + i) / 2));
97cb93a386Sopenharmony_ci            m.setSkewY(std::ldexp(1 + rand->nextF(), (j + i) / 2));
98cb93a386Sopenharmony_ci            m.setScaleY(std::ldexp(1 + rand->nextF(), j));
99cb93a386Sopenharmony_ci            f(m);
100cb93a386Sopenharmony_ci        }
101cb93a386Sopenharmony_ci    }
102cb93a386Sopenharmony_ci}
103cb93a386Sopenharmony_ci
104cb93a386Sopenharmony_cistatic void for_random_beziers(int numPoints, SkRandom* rand,
105cb93a386Sopenharmony_ci                               std::function<void(const SkPoint[])> f,
106cb93a386Sopenharmony_ci                               int maxExponent = 30) {
107cb93a386Sopenharmony_ci    SkASSERT(numPoints <= 4);
108cb93a386Sopenharmony_ci    SkPoint pts[4];
109cb93a386Sopenharmony_ci    for (int i = -10; i <= maxExponent; ++i) {
110cb93a386Sopenharmony_ci        for (int j = 0; j < numPoints; ++j) {
111cb93a386Sopenharmony_ci            pts[j].set(std::ldexp(1 + rand->nextF(), i), std::ldexp(1 + rand->nextF(), i));
112cb93a386Sopenharmony_ci        }
113cb93a386Sopenharmony_ci        f(pts);
114cb93a386Sopenharmony_ci    }
115cb93a386Sopenharmony_ci}
116cb93a386Sopenharmony_ci
117cb93a386Sopenharmony_ci// Ensure the optimized "*_log2" versions return the same value as ceil(std::log2(f)).
118cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_log2, r) {
119cb93a386Sopenharmony_ci    // Constructs a cubic such that the 'length' term in wang's formula == term.
120cb93a386Sopenharmony_ci    //
121cb93a386Sopenharmony_ci    //     f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
122cb93a386Sopenharmony_ci    //                             abs(p1 - p2*2 + p3))));
123cb93a386Sopenharmony_ci    auto setupCubicLengthTerm = [](int seed, SkPoint pts[], float term) {
124cb93a386Sopenharmony_ci        memset(pts, 0, sizeof(SkPoint) * 4);
125cb93a386Sopenharmony_ci
126cb93a386Sopenharmony_ci        SkPoint term2d = (seed & 1) ?
127cb93a386Sopenharmony_ci                SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
128cb93a386Sopenharmony_ci        seed >>= 1;
129cb93a386Sopenharmony_ci
130cb93a386Sopenharmony_ci        if (seed & 1) {
131cb93a386Sopenharmony_ci            term2d.fX = -term2d.fX;
132cb93a386Sopenharmony_ci        }
133cb93a386Sopenharmony_ci        seed >>= 1;
134cb93a386Sopenharmony_ci
135cb93a386Sopenharmony_ci        if (seed & 1) {
136cb93a386Sopenharmony_ci            std::swap(term2d.fX, term2d.fY);
137cb93a386Sopenharmony_ci        }
138cb93a386Sopenharmony_ci        seed >>= 1;
139cb93a386Sopenharmony_ci
140cb93a386Sopenharmony_ci        switch (seed % 4) {
141cb93a386Sopenharmony_ci            case 0:
142cb93a386Sopenharmony_ci                pts[0] = term2d;
143cb93a386Sopenharmony_ci                pts[3] = term2d * .75f;
144cb93a386Sopenharmony_ci                return;
145cb93a386Sopenharmony_ci            case 1:
146cb93a386Sopenharmony_ci                pts[1] = term2d * -.5f;
147cb93a386Sopenharmony_ci                return;
148cb93a386Sopenharmony_ci            case 2:
149cb93a386Sopenharmony_ci                pts[1] = term2d * -.5f;
150cb93a386Sopenharmony_ci                return;
151cb93a386Sopenharmony_ci            case 3:
152cb93a386Sopenharmony_ci                pts[3] = term2d;
153cb93a386Sopenharmony_ci                pts[0] = term2d * .75f;
154cb93a386Sopenharmony_ci                return;
155cb93a386Sopenharmony_ci        }
156cb93a386Sopenharmony_ci    };
157cb93a386Sopenharmony_ci
158cb93a386Sopenharmony_ci    // Constructs a quadratic such that the 'length' term in wang's formula == term.
159cb93a386Sopenharmony_ci    //
160cb93a386Sopenharmony_ci    //     f = sqrt(k * length(p0 - p1*2 + p2));
161cb93a386Sopenharmony_ci    auto setupQuadraticLengthTerm = [](int seed, SkPoint pts[], float term) {
162cb93a386Sopenharmony_ci        memset(pts, 0, sizeof(SkPoint) * 3);
163cb93a386Sopenharmony_ci
164cb93a386Sopenharmony_ci        SkPoint term2d = (seed & 1) ?
165cb93a386Sopenharmony_ci                SkPoint::Make(term, 0) : SkPoint::Make(.5f, std::sqrt(3)/2) * term;
166cb93a386Sopenharmony_ci        seed >>= 1;
167cb93a386Sopenharmony_ci
168cb93a386Sopenharmony_ci        if (seed & 1) {
169cb93a386Sopenharmony_ci            term2d.fX = -term2d.fX;
170cb93a386Sopenharmony_ci        }
171cb93a386Sopenharmony_ci        seed >>= 1;
172cb93a386Sopenharmony_ci
173cb93a386Sopenharmony_ci        if (seed & 1) {
174cb93a386Sopenharmony_ci            std::swap(term2d.fX, term2d.fY);
175cb93a386Sopenharmony_ci        }
176cb93a386Sopenharmony_ci        seed >>= 1;
177cb93a386Sopenharmony_ci
178cb93a386Sopenharmony_ci        switch (seed % 3) {
179cb93a386Sopenharmony_ci            case 0:
180cb93a386Sopenharmony_ci                pts[0] = term2d;
181cb93a386Sopenharmony_ci                return;
182cb93a386Sopenharmony_ci            case 1:
183cb93a386Sopenharmony_ci                pts[1] = term2d * -.5f;
184cb93a386Sopenharmony_ci                return;
185cb93a386Sopenharmony_ci            case 2:
186cb93a386Sopenharmony_ci                pts[2] = term2d;
187cb93a386Sopenharmony_ci                return;
188cb93a386Sopenharmony_ci        }
189cb93a386Sopenharmony_ci    };
190cb93a386Sopenharmony_ci
191cb93a386Sopenharmony_ci    // wangs_formula_cubic and wangs_formula_quadratic both use rsqrt instead of sqrt for speed.
192cb93a386Sopenharmony_ci    // Linearization is all approximate anyway, so as long as we are within ~1/2 tessellation
193cb93a386Sopenharmony_ci    // segment of the reference value we are good enough.
194cb93a386Sopenharmony_ci    constexpr static float kTessellationTolerance = 1/128.f;
195cb93a386Sopenharmony_ci
196cb93a386Sopenharmony_ci    for (int level = 0; level < 30; ++level) {
197cb93a386Sopenharmony_ci        float epsilon = std::ldexp(SK_ScalarNearlyZero, level * 2);
198cb93a386Sopenharmony_ci        SkPoint pts[4];
199cb93a386Sopenharmony_ci
200cb93a386Sopenharmony_ci        {
201cb93a386Sopenharmony_ci            // Test cubic boundaries.
202cb93a386Sopenharmony_ci            //     f = sqrt(k * length(max(abs(p0 - p1*2 + p2),
203cb93a386Sopenharmony_ci            //                             abs(p1 - p2*2 + p3))));
204cb93a386Sopenharmony_ci            constexpr static float k = (3 * 2) / (8 * (1.f/kPrecision));
205cb93a386Sopenharmony_ci            float x = std::ldexp(1, level * 2) / k;
206cb93a386Sopenharmony_ci            setupCubicLengthTerm(level << 1, pts, x - epsilon);
207cb93a386Sopenharmony_ci            float referenceValue = wangs_formula_cubic_reference_impl(kPrecision, pts);
208cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level);
209cb93a386Sopenharmony_ci            float c = wangs_formula::cubic(kPrecision, pts);
210cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(c/referenceValue, 1, kTessellationTolerance));
211cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, wangs_formula::cubic_log2(kPrecision, pts) == level);
212cb93a386Sopenharmony_ci            setupCubicLengthTerm(level << 1, pts, x + epsilon);
213cb93a386Sopenharmony_ci            referenceValue = wangs_formula_cubic_reference_impl(kPrecision, pts);
214cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level + 1);
215cb93a386Sopenharmony_ci            c = wangs_formula::cubic(kPrecision, pts);
216cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(c/referenceValue, 1, kTessellationTolerance));
217cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, wangs_formula::cubic_log2(kPrecision, pts) == level + 1);
218cb93a386Sopenharmony_ci        }
219cb93a386Sopenharmony_ci
220cb93a386Sopenharmony_ci        {
221cb93a386Sopenharmony_ci            // Test quadratic boundaries.
222cb93a386Sopenharmony_ci            //     f = std::sqrt(k * Length(p0 - p1*2 + p2));
223cb93a386Sopenharmony_ci            constexpr static float k = 2 / (8 * (1.f/kPrecision));
224cb93a386Sopenharmony_ci            float x = std::ldexp(1, level * 2) / k;
225cb93a386Sopenharmony_ci            setupQuadraticLengthTerm(level << 1, pts, x - epsilon);
226cb93a386Sopenharmony_ci            float referenceValue = wangs_formula_quadratic_reference_impl(kPrecision, pts);
227cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level);
228cb93a386Sopenharmony_ci            float q = wangs_formula::quadratic(kPrecision, pts);
229cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(q/referenceValue, 1, kTessellationTolerance));
230cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, wangs_formula::quadratic_log2(kPrecision, pts) == level);
231cb93a386Sopenharmony_ci            setupQuadraticLengthTerm(level << 1, pts, x + epsilon);
232cb93a386Sopenharmony_ci            referenceValue = wangs_formula_quadratic_reference_impl(kPrecision, pts);
233cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, std::ceil(std::log2(referenceValue)) == level+1);
234cb93a386Sopenharmony_ci            q = wangs_formula::quadratic(kPrecision, pts);
235cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(q/referenceValue, 1, kTessellationTolerance));
236cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, wangs_formula::quadratic_log2(kPrecision, pts) == level + 1);
237cb93a386Sopenharmony_ci        }
238cb93a386Sopenharmony_ci    }
239cb93a386Sopenharmony_ci
240cb93a386Sopenharmony_ci    auto check_cubic_log2 = [&](const SkPoint* pts) {
241cb93a386Sopenharmony_ci        float f = std::max(1.f, wangs_formula_cubic_reference_impl(kPrecision, pts));
242cb93a386Sopenharmony_ci        int f_log2 = wangs_formula::cubic_log2(kPrecision, pts);
243cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
244cb93a386Sopenharmony_ci        float c = std::max(1.f, wangs_formula::cubic(kPrecision, pts));
245cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, SkScalarNearlyEqual(c/f, 1, kTessellationTolerance));
246cb93a386Sopenharmony_ci    };
247cb93a386Sopenharmony_ci
248cb93a386Sopenharmony_ci    auto check_quadratic_log2 = [&](const SkPoint* pts) {
249cb93a386Sopenharmony_ci        float f = std::max(1.f, wangs_formula_quadratic_reference_impl(kPrecision, pts));
250cb93a386Sopenharmony_ci        int f_log2 = wangs_formula::quadratic_log2(kPrecision, pts);
251cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, SkScalarCeilToInt(std::log2(f)) == f_log2);
252cb93a386Sopenharmony_ci        float q = std::max(1.f, wangs_formula::quadratic(kPrecision, pts));
253cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, SkScalarNearlyEqual(q/f, 1, kTessellationTolerance));
254cb93a386Sopenharmony_ci    };
255cb93a386Sopenharmony_ci
256cb93a386Sopenharmony_ci    SkRandom rand;
257cb93a386Sopenharmony_ci
258cb93a386Sopenharmony_ci    for_random_matrices(&rand, [&](const SkMatrix& m) {
259cb93a386Sopenharmony_ci        SkPoint pts[4];
260cb93a386Sopenharmony_ci        m.mapPoints(pts, kSerp, 4);
261cb93a386Sopenharmony_ci        check_cubic_log2(pts);
262cb93a386Sopenharmony_ci
263cb93a386Sopenharmony_ci        m.mapPoints(pts, kLoop, 4);
264cb93a386Sopenharmony_ci        check_cubic_log2(pts);
265cb93a386Sopenharmony_ci
266cb93a386Sopenharmony_ci        m.mapPoints(pts, kQuad, 3);
267cb93a386Sopenharmony_ci        check_quadratic_log2(pts);
268cb93a386Sopenharmony_ci    });
269cb93a386Sopenharmony_ci
270cb93a386Sopenharmony_ci    for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
271cb93a386Sopenharmony_ci        check_cubic_log2(pts);
272cb93a386Sopenharmony_ci    });
273cb93a386Sopenharmony_ci
274cb93a386Sopenharmony_ci    for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
275cb93a386Sopenharmony_ci        check_quadratic_log2(pts);
276cb93a386Sopenharmony_ci    });
277cb93a386Sopenharmony_ci}
278cb93a386Sopenharmony_ci
279cb93a386Sopenharmony_ci// Ensure using transformations gives the same result as pre-transforming all points.
280cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_vectorXforms, r) {
281cb93a386Sopenharmony_ci    auto check_cubic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m){
282cb93a386Sopenharmony_ci        SkPoint ptsXformed[4];
283cb93a386Sopenharmony_ci        m.mapPoints(ptsXformed, pts, 4);
284cb93a386Sopenharmony_ci        int expected = wangs_formula::cubic_log2(kPrecision, ptsXformed);
285cb93a386Sopenharmony_ci        int actual = wangs_formula::cubic_log2(kPrecision, pts, wangs_formula::VectorXform(m));
286cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, actual == expected);
287cb93a386Sopenharmony_ci    };
288cb93a386Sopenharmony_ci
289cb93a386Sopenharmony_ci    auto check_quadratic_log2_with_transform = [&](const SkPoint* pts, const SkMatrix& m) {
290cb93a386Sopenharmony_ci        SkPoint ptsXformed[3];
291cb93a386Sopenharmony_ci        m.mapPoints(ptsXformed, pts, 3);
292cb93a386Sopenharmony_ci        int expected = wangs_formula::quadratic_log2(kPrecision, ptsXformed);
293cb93a386Sopenharmony_ci        int actual = wangs_formula::quadratic_log2(kPrecision, pts, wangs_formula::VectorXform(m));
294cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, actual == expected);
295cb93a386Sopenharmony_ci    };
296cb93a386Sopenharmony_ci
297cb93a386Sopenharmony_ci    SkRandom rand;
298cb93a386Sopenharmony_ci
299cb93a386Sopenharmony_ci    for_random_matrices(&rand, [&](const SkMatrix& m) {
300cb93a386Sopenharmony_ci        check_cubic_log2_with_transform(kSerp, m);
301cb93a386Sopenharmony_ci        check_cubic_log2_with_transform(kLoop, m);
302cb93a386Sopenharmony_ci        check_quadratic_log2_with_transform(kQuad, m);
303cb93a386Sopenharmony_ci
304cb93a386Sopenharmony_ci        for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
305cb93a386Sopenharmony_ci            check_cubic_log2_with_transform(pts, m);
306cb93a386Sopenharmony_ci        });
307cb93a386Sopenharmony_ci
308cb93a386Sopenharmony_ci        for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
309cb93a386Sopenharmony_ci            check_quadratic_log2_with_transform(pts, m);
310cb93a386Sopenharmony_ci        });
311cb93a386Sopenharmony_ci    });
312cb93a386Sopenharmony_ci}
313cb93a386Sopenharmony_ci
314cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_worst_case_cubic, r) {
315cb93a386Sopenharmony_ci    {
316cb93a386Sopenharmony_ci        SkPoint worstP[] = {{0,0}, {100,100}, {0,0}, {0,0}};
317cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, 100, 100) ==
318cb93a386Sopenharmony_ci                           wangs_formula_cubic_reference_impl(kPrecision, worstP));
319cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_log2(kPrecision, 100, 100) ==
320cb93a386Sopenharmony_ci                           wangs_formula::cubic_log2(kPrecision, worstP));
321cb93a386Sopenharmony_ci    }
322cb93a386Sopenharmony_ci    {
323cb93a386Sopenharmony_ci        SkPoint worstP[] = {{100,100}, {100,100}, {200,200}, {100,100}};
324cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, 100, 100) ==
325cb93a386Sopenharmony_ci                           wangs_formula_cubic_reference_impl(kPrecision, worstP));
326cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_log2(kPrecision, 100, 100) ==
327cb93a386Sopenharmony_ci                           wangs_formula::cubic_log2(kPrecision, worstP));
328cb93a386Sopenharmony_ci    }
329cb93a386Sopenharmony_ci    auto check_worst_case_cubic = [&](const SkPoint* pts) {
330cb93a386Sopenharmony_ci        SkRect bbox;
331cb93a386Sopenharmony_ci        bbox.setBoundsNoCheck(pts, 4);
332cb93a386Sopenharmony_ci        float worst = wangs_formula::worst_case_cubic(kPrecision, bbox.width(), bbox.height());
333cb93a386Sopenharmony_ci        int worst_log2 = wangs_formula::worst_case_cubic_log2(kPrecision, bbox.width(),
334cb93a386Sopenharmony_ci                                                               bbox.height());
335cb93a386Sopenharmony_ci        float actual = wangs_formula_cubic_reference_impl(kPrecision, pts);
336cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, worst >= actual);
337cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, std::ceil(std::log2(std::max(1.f, worst))) == worst_log2);
338cb93a386Sopenharmony_ci    };
339cb93a386Sopenharmony_ci    SkRandom rand;
340cb93a386Sopenharmony_ci    for (int i = 0; i < 100; ++i) {
341cb93a386Sopenharmony_ci        for_random_beziers(4, &rand, [&](const SkPoint pts[]) {
342cb93a386Sopenharmony_ci            check_worst_case_cubic(pts);
343cb93a386Sopenharmony_ci        });
344cb93a386Sopenharmony_ci    }
345cb93a386Sopenharmony_ci    // Make sure overflow saturates at infinity (not NaN).
346cb93a386Sopenharmony_ci    constexpr static float inf = std::numeric_limits<float>::infinity();
347cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, wangs_formula::worst_case_cubic_pow4(kPrecision, inf, inf) == inf);
348cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, wangs_formula::worst_case_cubic(kPrecision, inf, inf) == inf);
349cb93a386Sopenharmony_ci}
350cb93a386Sopenharmony_ci
351cb93a386Sopenharmony_ci// Ensure Wang's formula for quads produces max error within tolerance.
352cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_quad_within_tol, r) {
353cb93a386Sopenharmony_ci    // Wang's formula and the quad math starts to lose precision with very large
354cb93a386Sopenharmony_ci    // coordinate values, so limit the magnitude a bit to prevent test failures
355cb93a386Sopenharmony_ci    // due to loss of precision.
356cb93a386Sopenharmony_ci    constexpr int maxExponent = 15;
357cb93a386Sopenharmony_ci    SkRandom rand;
358cb93a386Sopenharmony_ci    for_random_beziers(3, &rand, [&r](const SkPoint pts[]) {
359cb93a386Sopenharmony_ci        const int nsegs = static_cast<int>(
360cb93a386Sopenharmony_ci                std::ceil(wangs_formula_quadratic_reference_impl(kPrecision, pts)));
361cb93a386Sopenharmony_ci
362cb93a386Sopenharmony_ci        const float tdelta = 1.f / nsegs;
363cb93a386Sopenharmony_ci        for (int j = 0; j < nsegs; ++j) {
364cb93a386Sopenharmony_ci            const float tmin = j * tdelta, tmax = (j + 1) * tdelta;
365cb93a386Sopenharmony_ci
366cb93a386Sopenharmony_ci            // Get section of quad in [tmin,tmax]
367cb93a386Sopenharmony_ci            const SkPoint* sectionPts;
368cb93a386Sopenharmony_ci            SkPoint tmp0[5];
369cb93a386Sopenharmony_ci            SkPoint tmp1[5];
370cb93a386Sopenharmony_ci            if (tmin == 0) {
371cb93a386Sopenharmony_ci                if (tmax == 1) {
372cb93a386Sopenharmony_ci                    sectionPts = pts;
373cb93a386Sopenharmony_ci                } else {
374cb93a386Sopenharmony_ci                    SkChopQuadAt(pts, tmp0, tmax);
375cb93a386Sopenharmony_ci                    sectionPts = tmp0;
376cb93a386Sopenharmony_ci                }
377cb93a386Sopenharmony_ci            } else {
378cb93a386Sopenharmony_ci                SkChopQuadAt(pts, tmp0, tmin);
379cb93a386Sopenharmony_ci                if (tmax == 1) {
380cb93a386Sopenharmony_ci                    sectionPts = tmp0 + 2;
381cb93a386Sopenharmony_ci                } else {
382cb93a386Sopenharmony_ci                    SkChopQuadAt(tmp0 + 2, tmp1, (tmax - tmin) / (1 - tmin));
383cb93a386Sopenharmony_ci                    sectionPts = tmp1;
384cb93a386Sopenharmony_ci                }
385cb93a386Sopenharmony_ci            }
386cb93a386Sopenharmony_ci
387cb93a386Sopenharmony_ci            // For quads, max distance from baseline is always at t=0.5.
388cb93a386Sopenharmony_ci            SkPoint p;
389cb93a386Sopenharmony_ci            p = SkEvalQuadAt(sectionPts, 0.5f);
390cb93a386Sopenharmony_ci
391cb93a386Sopenharmony_ci            // Get distance of p to baseline
392cb93a386Sopenharmony_ci            const SkPoint n = {sectionPts[2].fY - sectionPts[0].fY,
393cb93a386Sopenharmony_ci                               sectionPts[0].fX - sectionPts[2].fX};
394cb93a386Sopenharmony_ci            const float d = std::abs((p - sectionPts[0]).dot(n)) / n.length();
395cb93a386Sopenharmony_ci
396cb93a386Sopenharmony_ci            // Check distance is within specified tolerance
397cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, d <= (1.f / kPrecision) + SK_ScalarNearlyZero);
398cb93a386Sopenharmony_ci        }
399cb93a386Sopenharmony_ci    }, maxExponent);
400cb93a386Sopenharmony_ci}
401cb93a386Sopenharmony_ci
402cb93a386Sopenharmony_ci// Ensure the specialized version for rational quads reduces to regular Wang's
403cb93a386Sopenharmony_ci// formula when all weights are equal to one
404cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_rational_quad_reduces, r) {
405cb93a386Sopenharmony_ci    constexpr static float kTessellationTolerance = 1 / 128.f;
406cb93a386Sopenharmony_ci
407cb93a386Sopenharmony_ci    SkRandom rand;
408cb93a386Sopenharmony_ci    for (int i = 0; i < 100; ++i) {
409cb93a386Sopenharmony_ci        for_random_beziers(3, &rand, [&r](const SkPoint pts[]) {
410cb93a386Sopenharmony_ci            const float rational_nsegs = wangs_formula::conic(kPrecision, pts, 1.f);
411cb93a386Sopenharmony_ci            const float integral_nsegs = wangs_formula_quadratic_reference_impl(kPrecision, pts);
412cb93a386Sopenharmony_ci            REPORTER_ASSERT(
413cb93a386Sopenharmony_ci                    r, SkScalarNearlyEqual(rational_nsegs, integral_nsegs, kTessellationTolerance));
414cb93a386Sopenharmony_ci        });
415cb93a386Sopenharmony_ci    }
416cb93a386Sopenharmony_ci}
417cb93a386Sopenharmony_ci
418cb93a386Sopenharmony_ci// Ensure the rational quad version (used for conics) produces max error within tolerance.
419cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_conic_within_tol, r) {
420cb93a386Sopenharmony_ci    constexpr int maxExponent = 24;
421cb93a386Sopenharmony_ci
422cb93a386Sopenharmony_ci    // Single-precision functions in SkConic/SkGeometry lose too much accuracy with
423cb93a386Sopenharmony_ci    // large-magnitude curves and large weights for this test to pass.
424cb93a386Sopenharmony_ci    using Sk2d = skvx::Vec<2, double>;
425cb93a386Sopenharmony_ci    const auto eval_conic = [](const SkPoint pts[3], float w, float t) -> Sk2d {
426cb93a386Sopenharmony_ci        const auto eval = [](Sk2d A, Sk2d B, Sk2d C, float t) -> Sk2d {
427cb93a386Sopenharmony_ci            return (A * t + B) * t + C;
428cb93a386Sopenharmony_ci        };
429cb93a386Sopenharmony_ci
430cb93a386Sopenharmony_ci        const Sk2d p0 = {pts[0].fX, pts[0].fY};
431cb93a386Sopenharmony_ci        const Sk2d p1 = {pts[1].fX, pts[1].fY};
432cb93a386Sopenharmony_ci        const Sk2d p1w = p1 * w;
433cb93a386Sopenharmony_ci        const Sk2d p2 = {pts[2].fX, pts[2].fY};
434cb93a386Sopenharmony_ci        Sk2d numer = eval(p2 - p1w * 2 + p0, (p1w - p0) * 2, p0, t);
435cb93a386Sopenharmony_ci
436cb93a386Sopenharmony_ci        Sk2d denomC = {1, 1};
437cb93a386Sopenharmony_ci        Sk2d denomB = {2 * (w - 1), 2 * (w - 1)};
438cb93a386Sopenharmony_ci        Sk2d denomA = {-2 * (w - 1), -2 * (w - 1)};
439cb93a386Sopenharmony_ci        Sk2d denom = eval(denomA, denomB, denomC, t);
440cb93a386Sopenharmony_ci        return numer / denom;
441cb93a386Sopenharmony_ci    };
442cb93a386Sopenharmony_ci
443cb93a386Sopenharmony_ci    const auto dot = [](const Sk2d& a, const Sk2d& b) -> double {
444cb93a386Sopenharmony_ci        return a[0] * b[0] + a[1] * b[1];
445cb93a386Sopenharmony_ci    };
446cb93a386Sopenharmony_ci
447cb93a386Sopenharmony_ci    const auto length = [](const Sk2d& p) -> double { return sqrt(p[0] * p[0] + p[1] * p[1]); };
448cb93a386Sopenharmony_ci
449cb93a386Sopenharmony_ci    SkRandom rand;
450cb93a386Sopenharmony_ci    for (int i = -10; i <= 10; ++i) {
451cb93a386Sopenharmony_ci        const float w = std::ldexp(1 + rand.nextF(), i);
452cb93a386Sopenharmony_ci        for_random_beziers(
453cb93a386Sopenharmony_ci                3, &rand,
454cb93a386Sopenharmony_ci                [&](const SkPoint pts[]) {
455cb93a386Sopenharmony_ci                    const int nsegs = SkScalarCeilToInt(wangs_formula::conic(kPrecision, pts, w));
456cb93a386Sopenharmony_ci
457cb93a386Sopenharmony_ci                    const float tdelta = 1.f / nsegs;
458cb93a386Sopenharmony_ci                    for (int j = 0; j < nsegs; ++j) {
459cb93a386Sopenharmony_ci                        const float tmin = j * tdelta, tmax = (j + 1) * tdelta,
460cb93a386Sopenharmony_ci                                    tmid = 0.5f * (tmin + tmax);
461cb93a386Sopenharmony_ci
462cb93a386Sopenharmony_ci                        Sk2d p0, p1, p2;
463cb93a386Sopenharmony_ci                        p0 = eval_conic(pts, w, tmin);
464cb93a386Sopenharmony_ci                        p1 = eval_conic(pts, w, tmid);
465cb93a386Sopenharmony_ci                        p2 = eval_conic(pts, w, tmax);
466cb93a386Sopenharmony_ci
467cb93a386Sopenharmony_ci                        // Get distance of p1 to baseline (p0, p2).
468cb93a386Sopenharmony_ci                        const Sk2d n = {p2[1] - p0[1], p0[0] - p2[0]};
469cb93a386Sopenharmony_ci                        SkASSERT(length(n) != 0);
470cb93a386Sopenharmony_ci                        const double d = std::abs(dot(p1 - p0, n)) / length(n);
471cb93a386Sopenharmony_ci
472cb93a386Sopenharmony_ci                        // Check distance is within tolerance
473cb93a386Sopenharmony_ci                        REPORTER_ASSERT(r, d <= (1.0 / kPrecision) + SK_ScalarNearlyZero);
474cb93a386Sopenharmony_ci                    }
475cb93a386Sopenharmony_ci                },
476cb93a386Sopenharmony_ci                maxExponent);
477cb93a386Sopenharmony_ci    }
478cb93a386Sopenharmony_ci}
479cb93a386Sopenharmony_ci
480cb93a386Sopenharmony_ci// Ensure the vectorized conic version equals the reference implementation
481cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_conic_matches_reference, r) {
482cb93a386Sopenharmony_ci    SkRandom rand;
483cb93a386Sopenharmony_ci    for (int i = -10; i <= 10; ++i) {
484cb93a386Sopenharmony_ci        const float w = std::ldexp(1 + rand.nextF(), i);
485cb93a386Sopenharmony_ci        for_random_beziers(3, &rand, [&r, w](const SkPoint pts[]) {
486cb93a386Sopenharmony_ci            const float ref_nsegs = wangs_formula_conic_reference_impl(kPrecision, pts, w);
487cb93a386Sopenharmony_ci            const float nsegs = wangs_formula::conic(kPrecision, pts, w);
488cb93a386Sopenharmony_ci
489cb93a386Sopenharmony_ci            // Because the Gr version may implement the math differently for performance,
490cb93a386Sopenharmony_ci            // allow different slack in the comparison based on the rough scale of the answer.
491cb93a386Sopenharmony_ci            const float cmpThresh = ref_nsegs * (1.f / (1 << 20));
492cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(ref_nsegs, nsegs, cmpThresh));
493cb93a386Sopenharmony_ci        });
494cb93a386Sopenharmony_ci    }
495cb93a386Sopenharmony_ci}
496cb93a386Sopenharmony_ci
497cb93a386Sopenharmony_ci// Ensure using transformations gives the same result as pre-transforming all points.
498cb93a386Sopenharmony_ciDEF_TEST(wangs_formula_conic_vectorXforms, r) {
499cb93a386Sopenharmony_ci    auto check_conic_with_transform = [&](const SkPoint* pts, float w, const SkMatrix& m) {
500cb93a386Sopenharmony_ci        SkPoint ptsXformed[3];
501cb93a386Sopenharmony_ci        m.mapPoints(ptsXformed, pts, 3);
502cb93a386Sopenharmony_ci        float expected = wangs_formula::conic(kPrecision, ptsXformed, w);
503cb93a386Sopenharmony_ci        float actual = wangs_formula::conic(kPrecision, pts, w, wangs_formula::VectorXform(m));
504cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, SkScalarNearlyEqual(actual, expected));
505cb93a386Sopenharmony_ci    };
506cb93a386Sopenharmony_ci
507cb93a386Sopenharmony_ci    SkRandom rand;
508cb93a386Sopenharmony_ci    for (int i = -10; i <= 10; ++i) {
509cb93a386Sopenharmony_ci        const float w = std::ldexp(1 + rand.nextF(), i);
510cb93a386Sopenharmony_ci        for_random_beziers(3, &rand, [&](const SkPoint pts[]) {
511cb93a386Sopenharmony_ci            check_conic_with_transform(pts, w, SkMatrix::I());
512cb93a386Sopenharmony_ci            check_conic_with_transform(
513cb93a386Sopenharmony_ci                    pts, w, SkMatrix::Scale(rand.nextRangeF(-10, 10), rand.nextRangeF(-10, 10)));
514cb93a386Sopenharmony_ci
515cb93a386Sopenharmony_ci            // Random 2x2 matrix
516cb93a386Sopenharmony_ci            SkMatrix m;
517cb93a386Sopenharmony_ci            m.setScaleX(rand.nextRangeF(-10, 10));
518cb93a386Sopenharmony_ci            m.setSkewX(rand.nextRangeF(-10, 10));
519cb93a386Sopenharmony_ci            m.setSkewY(rand.nextRangeF(-10, 10));
520cb93a386Sopenharmony_ci            m.setScaleY(rand.nextRangeF(-10, 10));
521cb93a386Sopenharmony_ci            check_conic_with_transform(pts, w, m);
522cb93a386Sopenharmony_ci        });
523cb93a386Sopenharmony_ci    }
524cb93a386Sopenharmony_ci}
525cb93a386Sopenharmony_ci
526cb93a386Sopenharmony_ci}  // namespace skgpu
527