Make GrWangsFormula::conic take "precision" instead of "tolerance"

Rearranges the algebra to use "precision". This makes it consistent
with the other formulas and removes the need for the caller to think
about inverting its precision value.

Bug: skia:10419
Change-Id: I186d03393952983e86b0bef69e1f89f86cdbb423
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/414616
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
This commit is contained in:
Chris Dalton 2021-06-02 12:00:01 -06:00 committed by Skia Commit-Bot
parent 2da6c9cefc
commit e6f45318ef
7 changed files with 25 additions and 34 deletions

View File

@ -161,13 +161,11 @@ DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(18),
}
static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath& path) {
// Conic version expects tolerance, not "precision"
constexpr float kTolerance = 4;
int sum = 0;
GrVectorXform xform(matrix);
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
if (verb == SkPathVerb::kConic) {
sum += GrWangsFormula::conic(kTolerance, pts, *w, xform);
sum += GrWangsFormula::conic(4, pts, *w, xform);
}
}
// Don't let the compiler optimize away GrWangsFormula::conic.
@ -177,13 +175,11 @@ static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath&
}
static void benchmark_wangs_formula_conic_log2(const SkMatrix& matrix, const SkPath& path) {
// Conic version expects tolerance, not "precision"
constexpr float kTolerance = 4;
int sum = 0;
GrVectorXform xform(matrix);
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
if (verb == SkPathVerb::kConic) {
sum += GrWangsFormula::conic_log2(kTolerance, pts, *w, xform);
sum += GrWangsFormula::conic_log2(4, pts, *w, xform);
}
}
// Don't let the compiler optimize away GrWangsFormula::conic.

View File

@ -126,12 +126,12 @@ SK_ALWAYS_INLINE static int worst_case_cubic_log2(float precision, float devWidt
}
// Returns Wang's formula specialized for a conic curve, raised to the second power.
// Input points should be in projected space, and note tolerance parameter is not "precision".
// Input points should be in projected space.
//
// This is not actually due to Wang, but is an analogue from (Theorem 3, corollary 1):
// J. Zheng, T. Sederberg. "Estimating Tessellation Parameter Intervals for
// Rational Curves and Surfaces." ACM Transactions on Graphics 19(1). 2000.
SK_ALWAYS_INLINE static float conic_pow2(float tolerance, const SkPoint pts[], float w,
SK_ALWAYS_INLINE static float conic_pow2(float precision, const SkPoint pts[], float w,
const GrVectorXform& vectorXform = GrVectorXform()) {
using grvx::dot, grvx::float2, grvx::float4, skvx::bit_pun;
float2 p0 = vectorXform(bit_pun<float2>(pts[0]));
@ -152,13 +152,13 @@ SK_ALWAYS_INLINE static float conic_pow2(float tolerance, const SkPoint pts[], f
// Compute forward differences
const float2 dp = grvx::fast_madd<2>(-2 * w, p1, p0) + p2;
const float dw = fabsf(1 - 2 * w + 1);
const float dw = fabsf(-2 * w + 2);
// Compute numerator and denominator for parametric step size of linearization
const float r_minus_eps = std::max(0.f, max_len - tolerance);
const float min_w = std::min(w, 1.f);
const float numer = sqrtf(grvx::dot(dp, dp)) + r_minus_eps * dw;
const float denom = 4 * min_w * tolerance;
// Compute numerator and denominator for parametric step size of linearization. Here, the
// epsilon referenced from the cited paper is 1/precision.
const float rp_minus_1 = std::max(0.f, max_len * precision - 1);
const float numer = sqrtf(grvx::dot(dp, dp)) * precision + rp_minus_1 * dw;
const float denom = 4 * std::min(w, 1.f);
// Number of segments = sqrt(numer / denom).
// This assumes parametric interval of curve being linearized is [t0,t1] = [0, 1].

View File

@ -44,7 +44,7 @@ public:
}
SK_ALWAYS_INLINE void writeConic(GrVertexChunkBuilder* chunker, const SkPoint p[3], float w) {
if (GrWangsFormula::conic_pow2(1/kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
if (GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
this->chopAndWriteConic(chunker, {p, w});
return;
}

View File

@ -32,12 +32,12 @@ GrPathIndirectTessellator::GrPathIndirectTessellator(GrPathTessellationShader* s
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
int level;
switch (verb) {
case SkPathVerb::kConic:
level = GrWangsFormula::conic_log2(1/kPrecision, pts, *w, xform);
break;
case SkPathVerb::kQuad:
level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
break;
case SkPathVerb::kConic:
level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
break;
case SkPathVerb::kCubic:
level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
break;
@ -171,12 +171,12 @@ void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkRe
switch (verb) {
default:
continue;
case SkPathVerb::kConic:
level = GrWangsFormula::conic_log2(1/kPrecision, pts, *w, xform);
break;
case SkPathVerb::kQuad:
level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
break;
case SkPathVerb::kConic:
level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
break;
case SkPathVerb::kCubic:
level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
break;

View File

@ -140,7 +140,7 @@ public:
SK_ALWAYS_INLINE void writeConicWedge(GrVertexChunkBuilder* chunker, const SkPoint p[3],
float w, SkPoint midpoint) {
if (GrWangsFormula::conic_pow2(1/kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
if (GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
this->chopAndWriteConicWedges(chunker, {p, w}, midpoint);
return;
}

View File

@ -77,7 +77,7 @@ public:
}
SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
float n = GrWangsFormula::conic_pow2(1/fParametricPrecision, p, w);
float n = GrWangsFormula::conic_pow2(fParametricPrecision, p, w);
float numParametricSegments_pow4 = n*n;
if (numParametricSegments_pow4 > kMaxParametricSegments_pow4) {
this->chopConicTo({p, w});

View File

@ -10,7 +10,7 @@
#include "src/gpu/geometry/GrWangsFormula.h"
#include "tests/Test.h"
constexpr static int kPrecision = 4; // 1/4 pixel max error.
constexpr static float kPrecision = 4; // 1/4 pixel max error.
const SkPoint kSerp[4] = {
{285.625f, 499.687f}, {411.625f, 808.188f}, {1064.62f, 135.688f}, {1042.63f, 585.187f}};
@ -401,7 +401,7 @@ DEF_TEST(WangsFormula_rational_quad_reduces, r) {
SkRandom rand;
for (int i = 0; i < 100; ++i) {
for_random_beziers(3, &rand, [&r](const SkPoint pts[]) {
const float rational_nsegs = wangs_formula_conic_reference_impl(kPrecision, pts, 1.f);
const float rational_nsegs = GrWangsFormula::conic(kPrecision, pts, 1.f);
const float integral_nsegs = wangs_formula_quadratic_reference_impl(kPrecision, pts);
REPORTER_ASSERT(
r, SkScalarNearlyEqual(rational_nsegs, integral_nsegs, kTessellationTolerance));
@ -446,8 +446,7 @@ DEF_TEST(WangsFormula_conic_within_tol, r) {
for_random_beziers(
3, &rand,
[&](const SkPoint pts[]) {
const int nsegs = static_cast<int>(
std::ceil(wangs_formula_conic_reference_impl(kPrecision, pts, w)));
const int nsegs = SkScalarCeilToInt(GrWangsFormula::conic(kPrecision, pts, w));
const float tdelta = 1.f / nsegs;
for (int j = 0; j < nsegs; ++j) {
@ -474,14 +473,12 @@ DEF_TEST(WangsFormula_conic_within_tol, r) {
// Ensure the vectorized conic version equals the reference implementation
DEF_TEST(WangsFormula_conic_matches_reference, r) {
constexpr static float kTolerance = 1.f / kPrecision;
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 float ref_nsegs = wangs_formula_conic_reference_impl(kPrecision, pts, w);
const float nsegs = GrWangsFormula::conic(kTolerance, pts, w);
const float nsegs = GrWangsFormula::conic(kPrecision, 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.
@ -493,13 +490,11 @@ DEF_TEST(WangsFormula_conic_matches_reference, r) {
// Ensure using transformations gives the same result as pre-transforming all points.
DEF_TEST(WangsFormula_conic_vectorXforms, r) {
constexpr static float kTolerance = 1.f / kPrecision;
auto check_conic_with_transform = [&](const SkPoint* pts, float w, const SkMatrix& m) {
SkPoint ptsXformed[3];
m.mapPoints(ptsXformed, pts, 3);
float expected = GrWangsFormula::conic(kTolerance, ptsXformed, w);
float actual = GrWangsFormula::conic(kTolerance, pts, w, GrVectorXform(m));
float expected = GrWangsFormula::conic(kPrecision, ptsXformed, w);
float actual = GrWangsFormula::conic(kPrecision, pts, w, GrVectorXform(m));
REPORTER_ASSERT(r, SkScalarNearlyEqual(actual, expected));
};