Fix underflow/overflow issues in skvm::approx_pow2.

The skvm implementation of pow2 uses a clever bit-twiddling trick to
generate a floating-point value that can be cast to integer, then bit-
punned to float, to generate its result. However, the bit trick fails
for large inputs, and the bit-punning step generates a nonsense result.
This is now fixed by using a well-positioned clamp.

Change-Id: I55143a98324f5f518d0875149a0b6ce6d734ded0
Bug: skia:12847
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/497283
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
John Stiles 2022-01-20 19:50:13 -05:00 committed by SkCQ
parent 39dc2a02fd
commit f6bb619a16
2 changed files with 10 additions and 4 deletions

View File

@ -847,12 +847,16 @@ namespace skvm {
} }
F32 Builder::approx_pow2(F32 x) { F32 Builder::approx_pow2(F32 x) {
constexpr float kInfinityBits = 0x7f800000;
F32 f = fract(x); F32 f = fract(x);
F32 approx = add(x, 121.274057500f); F32 approx = add(x, 121.274057500f);
approx = sub(approx, mul( 1.490129070f, f)); approx = sub(approx, mul( 1.490129070f, f));
approx = add(approx, div(27.728023300f, sub(4.84252568f, f))); approx = add(approx, div(27.728023300f, sub(4.84252568f, f)));
approx = mul(1.0f * (1<<23), approx);
approx = clamp(approx, 0, kInfinityBits); // guard against underflow/overflow
return pun_to_F32(round(mul(1.0f * (1<<23), approx))); return pun_to_F32(round(approx));
} }
F32 Builder::approx_powf(F32 x, F32 y) { F32 Builder::approx_powf(F32 x, F32 y) {

View File

@ -2134,7 +2134,9 @@ DEF_TEST(SkVM_approx_math, r) {
auto compare = [r](int N, const float values[], const float expected[]) { auto compare = [r](int N, const float values[], const float expected[]) {
for (int i = 0; i < N; ++i) { for (int i = 0; i < N; ++i) {
REPORTER_ASSERT(r, SkScalarNearlyEqual(values[i], expected[i], 0.001f)); REPORTER_ASSERT(r, (values[i] == expected[i]) ||
SkScalarNearlyEqual(values[i], expected[i], 0.001f),
"evaluated to %g, but expected %g", values[i], expected[i]);
} }
}; };
@ -2151,12 +2153,12 @@ DEF_TEST(SkVM_approx_math, r) {
// pow2 // pow2
{ {
float values[] = {-2, -1, 0, 1, 2, 3}; float values[] = {-80, -5, -2, -1, 0, 1, 2, 3, 5, 160};
constexpr int N = SK_ARRAY_COUNT(values); constexpr int N = SK_ARRAY_COUNT(values);
eval(N, values, [](skvm::Builder* b, skvm::F32 v) { eval(N, values, [](skvm::Builder* b, skvm::F32 v) {
return b->approx_pow2(v); return b->approx_pow2(v);
}); });
const float expected[] = {0.25f, 0.5f, 1, 2, 4, 8}; const float expected[] = {0, 0.03125f, 0.25f, 0.5f, 1, 2, 4, 8, 32, INFINITY};
compare(N, values, expected); compare(N, values, expected);
} }