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