Add an epsilon to GrPathUtils::findCubicConvex180Chops
Cubic tangents become unstable if we chop too close to 0 or 1. This adds an epsilon to simply not chop them if it's too close. Bug: skia:10419 Change-Id: I99e98c5d433e2cb59c767ee564e015d7ec4f7765 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/336280 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
4ce110b8bd
commit
9458c8d44a
@ -585,14 +585,21 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool is_ieee_float_inside_0_1_exclusive(float x) {
|
|
||||||
constexpr static uint32_t kIEEE_one = 127 << 23;
|
|
||||||
return sk_bit_cast<uint32_t>(x) - 1 < kIEEE_one - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
||||||
using grvx::float2;
|
using grvx::float2;
|
||||||
|
|
||||||
|
// If a chop falls within a distance of "kEpsilon" from 0 or 1, throw it out. Tangents become
|
||||||
|
// unstable when we chop too close to the boundary. This works out because the tessellation
|
||||||
|
// shaders don't allow more than 2^10 parametric segments, and they snap the beginning and
|
||||||
|
// ending edges at 0 and 1. So if we overstep an inflection or point of 180-degree rotation by a
|
||||||
|
// fraction of a tessellation segment, it just gets snapped.
|
||||||
|
constexpr static float kEpsilon = 1.f / (1 << 12);
|
||||||
|
// Floating-point representation of "1 - 2*kEpsilon".
|
||||||
|
constexpr static uint32_t kIEEE_one_minus_2_epsilon = (127 << 23) - 2*(1 << 12);
|
||||||
|
// Unfortunately we don't have a way to static_assert this, but we can runtime assert that the
|
||||||
|
// kIEEE_one_minus_2_epsilon bits are correct.
|
||||||
|
SkASSERT(sk_bit_cast<float>(kIEEE_one_minus_2_epsilon) == 1 - 2*kEpsilon);
|
||||||
|
|
||||||
float2 p0 = skvx::bit_pun<float2>(pts[0]);
|
float2 p0 = skvx::bit_pun<float2>(pts[0]);
|
||||||
float2 p1 = skvx::bit_pun<float2>(pts[1]);
|
float2 p1 = skvx::bit_pun<float2>(pts[1]);
|
||||||
float2 p2 = skvx::bit_pun<float2>(pts[2]);
|
float2 p2 = skvx::bit_pun<float2>(pts[2]);
|
||||||
@ -642,7 +649,8 @@ int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
|||||||
// convex-180 if any points are colocated, and T[0] will equal NaN which returns 0
|
// convex-180 if any points are colocated, and T[0] will equal NaN which returns 0
|
||||||
// chops.
|
// chops.
|
||||||
float root = sk_ieee_float_divide(c, b_over_minus_2);
|
float root = sk_ieee_float_divide(c, b_over_minus_2);
|
||||||
if (is_ieee_float_inside_0_1_exclusive(root)) {
|
// Is "root" inside the range [epsilon, 1 - epsilon)?
|
||||||
|
if (sk_bit_cast<uint32_t>(root - kEpsilon) < kIEEE_one_minus_2_epsilon) {
|
||||||
T[0] = root;
|
T[0] = root;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -673,7 +681,7 @@ int GrPathUtils::findCubicConvex180Chops(const SkPoint pts[], float T[2]) {
|
|||||||
q = q + b_over_minus_2;
|
q = q + b_over_minus_2;
|
||||||
float2 roots = float2{q,c} / float2{a,q};
|
float2 roots = float2{q,c} / float2{a,q};
|
||||||
|
|
||||||
auto inside = (roots > 0) & (roots < 1);
|
auto inside = (roots > kEpsilon) & (roots < (1 - kEpsilon));
|
||||||
if (inside[0]) {
|
if (inside[0]) {
|
||||||
if (inside[1] && roots[0] != roots[1]) {
|
if (inside[1] && roots[0] != roots[1]) {
|
||||||
if (roots[0] > roots[1]) {
|
if (roots[0] > roots[1]) {
|
||||||
|
@ -18,50 +18,63 @@ static bool is_linear(const SkPoint p[4]) {
|
|||||||
return is_linear(p[0],p[1],p[2]) && is_linear(p[0],p[2],p[3]) && is_linear(p[1],p[2],p[3]);
|
return is_linear(p[0],p[1],p[2]) && is_linear(p[0],p[2],p[3]) && is_linear(p[1],p[2],p[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void check_cubic_convex_180(skiatest::Reporter* r, const SkPoint p[4]) {
|
||||||
|
float inflectT[2], convex180T[2];
|
||||||
|
if (int inflectN = SkFindCubicInflections(p, inflectT)) {
|
||||||
|
// The curve has inflections. findCubicConvex180Chops should return the inflection
|
||||||
|
// points.
|
||||||
|
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
||||||
|
REPORTER_ASSERT(r, inflectN == convex180N);
|
||||||
|
for (int i = 0; i < convex180N; ++i) {
|
||||||
|
REPORTER_ASSERT(r, SkScalarNearlyEqual(inflectT[i], convex180T[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float totalRotation = SkMeasureNonInflectCubicRotation(p);
|
||||||
|
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
||||||
|
SkPoint chops[10];
|
||||||
|
SkChopCubicAt(p, chops, convex180T, convex180N);
|
||||||
|
float radsSum = 0;
|
||||||
|
for (int i = 0; i <= convex180N; ++i) {
|
||||||
|
float rads = SkMeasureNonInflectCubicRotation(chops + i*3);
|
||||||
|
SkASSERT(rads < SK_ScalarPI + SK_ScalarNearlyZero);
|
||||||
|
radsSum += rads;
|
||||||
|
}
|
||||||
|
if (totalRotation < SK_ScalarPI - SK_ScalarNearlyZero) {
|
||||||
|
// The curve should never chop if rotation is <180 degrees.
|
||||||
|
REPORTER_ASSERT(r, convex180N == 0);
|
||||||
|
} else if (!is_linear(p)) {
|
||||||
|
REPORTER_ASSERT(r, SkScalarNearlyEqual(radsSum, totalRotation));
|
||||||
|
if (totalRotation > SK_ScalarPI + SK_ScalarNearlyZero) {
|
||||||
|
REPORTER_ASSERT(r, convex180N == 1);
|
||||||
|
// This works because cusps take the "inflection" path above, so we don't get
|
||||||
|
// non-lilnear curves that lose rotation when chopped.
|
||||||
|
REPORTER_ASSERT(r, SkScalarNearlyEqual(
|
||||||
|
SkMeasureNonInflectCubicRotation(chops), SK_ScalarPI));
|
||||||
|
REPORTER_ASSERT(r, SkScalarNearlyEqual(
|
||||||
|
SkMeasureNonInflectCubicRotation(chops + 3), totalRotation - SK_ScalarPI));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DEF_TEST(GrPathUtils_findCubicConvex180Chops, r) {
|
DEF_TEST(GrPathUtils_findCubicConvex180Chops, r) {
|
||||||
// Test all combinations of corners from the square [0,0,1,1]. This gives us all kinds of
|
// Test all combinations of corners from the square [0,0,1,1]. This covers every cubic type as
|
||||||
// special cases for cusps, lines, loops, and inflections.
|
// well as a wide variety of special cases for cusps, lines, loops, and inflections.
|
||||||
for (int i = 0; i < (1 << 8); ++i) {
|
for (int i = 0; i < (1 << 8); ++i) {
|
||||||
SkPoint p[4] = {SkPoint::Make((i>>0)&1, (i>>1)&1),
|
SkPoint p[4] = {SkPoint::Make((i>>0)&1, (i>>1)&1),
|
||||||
SkPoint::Make((i>>2)&1, (i>>3)&1),
|
SkPoint::Make((i>>2)&1, (i>>3)&1),
|
||||||
SkPoint::Make((i>>4)&1, (i>>5)&1),
|
SkPoint::Make((i>>4)&1, (i>>5)&1),
|
||||||
SkPoint::Make((i>>6)&1, (i>>7)&1)};
|
SkPoint::Make((i>>6)&1, (i>>7)&1)};
|
||||||
float inflectT[2], convex180T[2];
|
check_cubic_convex_180(r, p);
|
||||||
if (int inflectN = SkFindCubicInflections(p, inflectT)) {
|
}
|
||||||
// The curve has inflections. findCubicConvex180Chops should return the inflection
|
|
||||||
// points.
|
{
|
||||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
// This cubic has a convex-180 chop at T=1-"epsilon"
|
||||||
REPORTER_ASSERT(r, inflectN == convex180N);
|
static const uint32_t hexPts[] = {0x3ee0ac74, 0x3f1e061a, 0x3e0fc408, 0x3f457230,
|
||||||
for (int i = 0; i < convex180N; ++i) {
|
0x3f42ac7c, 0x3f70d76c, 0x3f4e6520, 0x3f6acafa};
|
||||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(inflectT[i], convex180T[i]));
|
SkPoint p[4];
|
||||||
}
|
memcpy(p, hexPts, sizeof(p));
|
||||||
} else {
|
check_cubic_convex_180(r, p);
|
||||||
float totalRotation = SkMeasureNonInflectCubicRotation(p);
|
|
||||||
int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T);
|
|
||||||
SkPoint chops[10];
|
|
||||||
SkChopCubicAt(p, chops, convex180T, convex180N);
|
|
||||||
float radsSum = 0;
|
|
||||||
for (int i = 0; i <= convex180N; ++i) {
|
|
||||||
float rads = SkMeasureNonInflectCubicRotation(chops + i*3);
|
|
||||||
SkASSERT(rads < SK_ScalarPI + SK_ScalarNearlyZero);
|
|
||||||
radsSum += rads;
|
|
||||||
}
|
|
||||||
if (totalRotation < SK_ScalarPI - SK_ScalarNearlyZero) {
|
|
||||||
// The curve should never chop if rotation is <180 degrees.
|
|
||||||
REPORTER_ASSERT(r, convex180N == 0);
|
|
||||||
} else if (!is_linear(p)) {
|
|
||||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(radsSum, totalRotation));
|
|
||||||
if (totalRotation > SK_ScalarPI + SK_ScalarNearlyZero) {
|
|
||||||
REPORTER_ASSERT(r, convex180N == 1);
|
|
||||||
// This works because cusps take the "inflection" path above, so we don't get
|
|
||||||
// non-lilnear curves that lose rotation when chopped.
|
|
||||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(
|
|
||||||
SkMeasureNonInflectCubicRotation(chops), SK_ScalarPI));
|
|
||||||
REPORTER_ASSERT(r, SkScalarNearlyEqual(
|
|
||||||
SkMeasureNonInflectCubicRotation(chops + 3), totalRotation - SK_ScalarPI));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now test an exact quadratic.
|
// Now test an exact quadratic.
|
||||||
|
Loading…
Reference in New Issue
Block a user