Improve conic Wang's formula correctness tests
- Treat randomly generated control points as pre-projected - Use double-precision conic evaluation for "within tolerance" test. This allows us to test with larger magnitude control points (the SkConic/SkGeometry single-precision functions lose too much accuracy). Bug: skia:10419 Change-Id: Iba0915dccb50131e1a1b28a7d556863497f636e9 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/367057 Commit-Queue: Tyler Denniston <tdenniston@google.com> Reviewed-by: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
parent
c740d93628
commit
4c0a35f9d6
@ -411,36 +411,61 @@ DEF_TEST(WangsFormula_rational_quad_reduces, r) {
|
||||
|
||||
// Ensure the rational quad version (used for conics) produces max error within tolerance.
|
||||
DEF_TEST(WangsFormula_conic_within_tol, r) {
|
||||
constexpr int maxExponent = 15;
|
||||
constexpr int maxExponent = 24;
|
||||
|
||||
// Single-precision functions in SkConic/SkGeometry lose too much accuracy with
|
||||
// large-magnitude curves and large weights for this test to pass.
|
||||
using Sk2d = skvx::Vec<2, double>;
|
||||
const auto eval_conic = [](const SkPoint pts[3], float w, float t) -> Sk2d {
|
||||
const auto eval = [](Sk2d A, Sk2d B, Sk2d C, float t) -> Sk2d {
|
||||
return (A * t + B) * t + C;
|
||||
};
|
||||
|
||||
const Sk2d p0 = {pts[0].fX, pts[0].fY};
|
||||
const Sk2d p1 = {pts[1].fX, pts[1].fY};
|
||||
const Sk2d p1w = p1 * w;
|
||||
const Sk2d p2 = {pts[2].fX, pts[2].fY};
|
||||
Sk2d numer = eval(p2 - p1w * 2 + p0, (p1w - p0) * 2, p0, t);
|
||||
|
||||
Sk2d denomC = {1, 1};
|
||||
Sk2d denomB = {2 * (w - 1), 2 * (w - 1)};
|
||||
Sk2d denomA = {-2 * (w - 1), -2 * (w - 1)};
|
||||
Sk2d denom = eval(denomA, denomB, denomC, t);
|
||||
return numer / denom;
|
||||
};
|
||||
|
||||
const auto dot = [](const Sk2d& a, const Sk2d& b) -> double {
|
||||
return a[0] * b[0] + a[1] * b[1];
|
||||
};
|
||||
|
||||
const auto length = [](const Sk2d& p) -> double { return sqrt(p[0] * p[0] + p[1] * p[1]); };
|
||||
|
||||
SkRandom rand;
|
||||
for (int i = -10; i <= 10; ++i) {
|
||||
const float w = std::ldexp(1 + rand.nextF(), i);
|
||||
for_random_beziers(
|
||||
3, &rand,
|
||||
[&r, w](const SkPoint pts[]) {
|
||||
const SkPoint projPts[3] = {pts[0], pts[1] * (1.f / w), pts[2]};
|
||||
const int nsegs = static_cast<int>(std::ceil(
|
||||
wangs_formula_conic_reference_impl(kIntolerance, projPts, w)));
|
||||
[&](const SkPoint pts[]) {
|
||||
const int nsegs = static_cast<int>(
|
||||
std::ceil(wangs_formula_conic_reference_impl(kIntolerance, pts, w)));
|
||||
|
||||
const SkConic conic(projPts[0], projPts[1], projPts[2], w);
|
||||
const float tdelta = 1.f / nsegs;
|
||||
for (int j = 0; j < nsegs; ++j) {
|
||||
const float tmin = j * tdelta, tmax = (j + 1) * tdelta,
|
||||
tmid = 0.5f * (tmin + tmax);
|
||||
|
||||
SkPoint p0, p1, p2;
|
||||
conic.evalAt(tmin, &p0);
|
||||
conic.evalAt(tmid, &p1);
|
||||
conic.evalAt(tmax, &p2);
|
||||
Sk2d p0, p1, p2;
|
||||
p0 = eval_conic(pts, w, tmin);
|
||||
p1 = eval_conic(pts, w, tmid);
|
||||
p2 = eval_conic(pts, w, tmax);
|
||||
|
||||
// Get distance of p1 to baseline (p0, p2).
|
||||
const SkPoint n = {p2.fY - p0.fY, p0.fX - p2.fX};
|
||||
SkASSERT(n.length() != 0);
|
||||
const float d = std::abs((p1 - p0).dot(n)) / n.length();
|
||||
const Sk2d n = {p2[1] - p0[1], p0[0] - p2[0]};
|
||||
SkASSERT(length(n) != 0);
|
||||
const double d = std::abs(dot(p1 - p0, n)) / length(n);
|
||||
|
||||
// Check distance is within tolerance
|
||||
REPORTER_ASSERT(r, d <= (1.f / kIntolerance) + SK_ScalarNearlyZero);
|
||||
REPORTER_ASSERT(r, d <= (1.0 / kIntolerance) + SK_ScalarNearlyZero);
|
||||
}
|
||||
},
|
||||
maxExponent);
|
||||
@ -455,9 +480,8 @@ DEF_TEST(WangsFormula_conic_matches_reference, r) {
|
||||
for (int i = -10; i <= 10; ++i) {
|
||||
const float w = std::ldexp(1 + rand.nextF(), i);
|
||||
for_random_beziers(3, &rand, [&r, w](const SkPoint pts[]) {
|
||||
const SkPoint projPts[3] = {pts[0], pts[1] * (1.f / w), pts[2]};
|
||||
const float ref_nsegs = wangs_formula_conic_reference_impl(kIntolerance, projPts, w);
|
||||
const float nsegs = GrWangsFormula::conic(kTolerance, projPts, w);
|
||||
const float ref_nsegs = wangs_formula_conic_reference_impl(kIntolerance, pts, w);
|
||||
const float nsegs = GrWangsFormula::conic(kTolerance, pts, w);
|
||||
|
||||
// Because the Gr version may implement the math differently for performance,
|
||||
// allow different slack in the comparison based on the rough scale of the answer.
|
||||
@ -472,11 +496,10 @@ DEF_TEST(WangsFormula_conic_vectorXforms, r) {
|
||||
constexpr static float kTolerance = 1.f / kIntolerance;
|
||||
|
||||
auto check_conic_with_transform = [&](const SkPoint* pts, float w, const SkMatrix& m) {
|
||||
const SkPoint projPts[3] = {pts[0], pts[1] * (1.f / w), pts[2]};
|
||||
SkPoint ptsXformed[3];
|
||||
m.mapPoints(ptsXformed, projPts, 3);
|
||||
m.mapPoints(ptsXformed, pts, 3);
|
||||
float expected = GrWangsFormula::conic(kTolerance, ptsXformed, w);
|
||||
float actual = GrWangsFormula::conic(kTolerance, projPts, w, GrVectorXform(m));
|
||||
float actual = GrWangsFormula::conic(kTolerance, pts, w, GrVectorXform(m));
|
||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(actual, expected));
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user