Optimize SkVM approx_powf for simple cases.

Our SkVM code generator will always convert `pow(x, y)` into an
`approx_powf` call. However, in many cases, `x` or `y` might boil down
to an immediate value, and for some immediates, we can reduce the
operation into something much simpler:

- `pow(1, y)` is always 1
- `pow(2, y)` can be converted to `approx_pow2(y)`
- `pow(x, 0.5)` can be converted into `sqrt(x)`.
- `pow(x, 1)` can be converted to `x`
- `pow(x, 2)` can be converted to `x * x`

We could go further if there is a need, e.g. `pow(x, 4)` can become
`a = x * x; return a * a`, but these seemed like the best places to
start.

Change-Id: I02b9d4d3f5067581ebad5e53b2d1d7befa308300
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/502308
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
This commit is contained in:
John Stiles 2022-01-31 17:44:10 -05:00 committed by SkCQ
parent 1d0d1e9f16
commit 260d925cb3
2 changed files with 53 additions and 8 deletions

View File

@ -863,6 +863,12 @@ namespace skvm {
// TODO: assert this instead? Sometimes x is very slightly negative. See skia:10210.
x = max(0.0f, x);
if (this->isImm(x.id, 1.0f)) { return x; } // 1^y is one
if (this->isImm(x.id, 2.0f)) { return this->approx_pow2(y); } // 2^y is pow2(y)
if (this->isImm(y.id, 0.5f)) { return this->sqrt(x); } // x^0.5 is sqrt(x)
if (this->isImm(y.id, 1.0f)) { return x; } // x^1 is x
if (this->isImm(y.id, 2.0f)) { return x * x; } // x^2 is x*x
auto is_x = bit_or(eq(x, 0.0f),
eq(x, 1.0f));
return select(is_x, x, approx_pow2(mul(approx_log2(x), y)));

View File

@ -2161,16 +2161,25 @@ DEF_TEST(SkVM_approx_math, r) {
const float expected[] = {0, 0.03125f, 0.25f, 0.5f, 1, 2, 4, 8, 32, INFINITY};
compare(N, values, expected);
}
// powf -- x^0.5
// powf -- 1^x
{
float bases[] = {0, 1, 4, 9, 16};
constexpr int N = SK_ARRAY_COUNT(bases);
eval(N, bases, [](skvm::Builder* b, skvm::F32 base) {
return b->approx_powf(base, b->splat(0.5f));
float exps[] = {-2, -1, 0, 1, 2};
constexpr int N = SK_ARRAY_COUNT(exps);
eval(N, exps, [](skvm::Builder* b, skvm::F32 exp) {
return b->approx_powf(b->splat(1.0f), exp);
});
const float expected[] = {0, 1, 2, 3, 4};
compare(N, bases, expected);
const float expected[] = {1, 1, 1, 1, 1};
compare(N, exps, expected);
}
// powf -- 2^x
{
float exps[] = {-80, -5, -2, -1, 0, 1, 2, 3, 5, 160};
constexpr int N = SK_ARRAY_COUNT(exps);
eval(N, exps, [](skvm::Builder* b, skvm::F32 exp) {
return b->approx_powf(2.0, exp);
});
const float expected[] = {0, 0.03125f, 0.25f, 0.5f, 1, 2, 4, 8, 32, INFINITY};
compare(N, exps, expected);
}
// powf -- 3^x
{
@ -2182,6 +2191,36 @@ DEF_TEST(SkVM_approx_math, r) {
const float expected[] = {1/9.0f, 1/3.0f, 1, 3, 9};
compare(N, exps, expected);
}
// powf -- x^0.5
{
float bases[] = {0, 1, 4, 9, 16};
constexpr int N = SK_ARRAY_COUNT(bases);
eval(N, bases, [](skvm::Builder* b, skvm::F32 base) {
return b->approx_powf(base, b->splat(0.5f));
});
const float expected[] = {0, 1, 2, 3, 4};
compare(N, bases, expected);
}
// powf -- x^1
{
float bases[] = {0, 1, 2, 3, 4};
constexpr int N = SK_ARRAY_COUNT(bases);
eval(N, bases, [](skvm::Builder* b, skvm::F32 base) {
return b->approx_powf(base, b->splat(1.0f));
});
const float expected[] = {0, 1, 2, 3, 4};
compare(N, bases, expected);
}
// powf -- x^2
{
float bases[] = {0, 1, 2, 3, 4};
constexpr int N = SK_ARRAY_COUNT(bases);
eval(N, bases, [](skvm::Builder* b, skvm::F32 base) {
return b->approx_powf(base, b->splat(2.0f));
});
const float expected[] = {0, 1, 4, 9, 16};
compare(N, bases, expected);
}
auto test = [r](float arg, float expected, float tolerance, auto prog) {
skvm::Builder b;