blob: a7e8977c123f9eff729011e382fba20b871082ab [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#include "rive/math/math_types.hpp"
#include "rive/renderer/gpu.hpp"
#include "shaders/constants.glsl"
#include <catch.hpp>
namespace rive
{
TEST_CASE("find_transformed_area", "[gpu]")
{
AABB unitSquare{0, 0, 1, 1};
CHECK(gpu::find_transformed_area(unitSquare, Mat2D()) == 1);
CHECK(gpu::find_transformed_area(unitSquare, Mat2D::fromScale(2, 2)) == 4);
CHECK(gpu::find_transformed_area(unitSquare, Mat2D::fromScale(2, 1)) == 2);
CHECK(gpu::find_transformed_area(unitSquare, Mat2D::fromScale(0, 1)) == 0);
CHECK(gpu::find_transformed_area(unitSquare,
Mat2D::fromRotation(math::PI / 4)) ==
Approx(1.f));
CHECK(gpu::find_transformed_area(
unitSquare,
Mat2D::fromRotation(math::PI / 8).scale({2, 2})) == Approx(4.f));
CHECK(gpu::find_transformed_area(
unitSquare,
Mat2D::fromRotation(math::PI / 16).scale({2, 1})) == Approx(2.f));
CHECK(
gpu::find_transformed_area(unitSquare, {1, .87f, 8, 8 * .87f, 0, 0}) ==
Approx(0.f).margin(math::EPSILON));
}
TEST_CASE("gaussian_integral_table", "[gpu]")
{
float gaussianTable[gpu::GAUSSIAN_TABLE_SIZE];
for (int i = 0; i < gpu::GAUSSIAN_TABLE_SIZE; i += 4)
{
float4 f32s = gpu::cast_f16_to_f32(
simd::load<uint16_t, 4>(gpu::g_gaussianIntegralTableF16 + i));
simd::store(gaussianTable + i, f32s);
};
CHECK(gaussianTable[0] >= 0);
CHECK(gaussianTable[0] <= expf(-.5f * FEATHER_TEXTURE_STDDEVS));
CHECK(gaussianTable[0] == MIN_FEATHER);
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] <= 1);
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] >=
1 - expf(-.5f * FEATHER_TEXTURE_STDDEVS));
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] == MAX_FEATHER);
if (gpu::GAUSSIAN_TABLE_SIZE & 1)
{
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2] == .5f);
}
else
{
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2 - 1] <= .5f);
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2] >= .5f);
CHECK((gaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2 - 1] +
gaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2]) /
2 ==
Approx(.5f).margin(1e-3f));
}
for (int i = 1; i < gpu::GAUSSIAN_TABLE_SIZE; ++i)
{
CHECK(gaussianTable[i - 1] <= gaussianTable[i]);
}
for (int i = 0; i < (gpu::GAUSSIAN_TABLE_SIZE + 1) / 2; ++i)
{
CHECK(gaussianTable[i] +
gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1 - i] ==
Approx(1).margin(1e-3f));
}
}
// Looks up the value of "x" in the given function table, with linear filtering.
static float function_table_lookup(float x, const float* table, float tableSize)
{
x = fminf(fmaxf(0, x), 1);
float sampleRegionLeft = x * tableSize - .5f;
int rightIdx = static_cast<int>(fminf(sampleRegionLeft + 1, tableSize - 1));
int leftIdx = std::max(rightIdx - 1, 0);
float t = fminf(fmaxf(0, sampleRegionLeft - leftIdx), 1);
return lerp(table[leftIdx], table[rightIdx], t);
}
TEST_CASE("inverse_gaussian_integral_table", "[gpu]")
{
float gaussianTable[gpu::GAUSSIAN_TABLE_SIZE];
for (int i = 0; i < gpu::GAUSSIAN_TABLE_SIZE; i += 4)
{
float4 f32s = gpu::cast_f16_to_f32(
simd::load<uint16_t, 4>(gpu::g_gaussianIntegralTableF16 + i));
simd::store(gaussianTable + i, f32s);
};
auto gaussianIntegral = [&gaussianTable](float x) {
return function_table_lookup(x,
gaussianTable,
std::size(gaussianTable));
};
float inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE];
for (int i = 0; i < gpu::GAUSSIAN_TABLE_SIZE; i += 4)
{
float4 f32s = gpu::cast_f16_to_f32(simd::load<uint16_t, 4>(
gpu::g_inverseGaussianIntegralTableF16 + i));
simd::store(inverseGaussianTable + i, f32s);
};
auto inverseGaussianIntegral = [&inverseGaussianTable](float y) {
return function_table_lookup(y,
inverseGaussianTable,
std::size(inverseGaussianTable));
};
CHECK(inverseGaussianTable[0] == 0);
CHECK(inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] == 1);
if (gpu::GAUSSIAN_TABLE_SIZE & 1)
{
CHECK(inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2] == .5f);
}
else
{
CHECK((inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2 - 1] +
inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE / 2]) /
2 ==
Approx(.5f).margin(1e-4f));
}
for (int i = 1; i < gpu::GAUSSIAN_TABLE_SIZE; ++i)
{
CHECK(inverseGaussianTable[i - 1] <= inverseGaussianTable[i]);
}
for (int i = 0; i < (gpu::GAUSSIAN_TABLE_SIZE + 1) / 2 - 4; ++i)
{
CHECK(inverseGaussianTable[i] +
inverseGaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1 - i] ==
Approx(1).margin(i > 100 ? 5e-4f
: i > 4 ? 1e-3f
: 1e-2f));
}
// Check that the inverse table is actually an inverse of the gaussian
// integral.
float M = 21;
for (float x = 0; x <= 1; x += 1.f / (gpu::GAUSSIAN_TABLE_SIZE * M))
{
float y = gaussianIntegral(x);
// The inverse table loses precision at the inner and outer cells.
float margin = x > .125f && x < .875f ? 1.f / 512
: x > .04f && x < .96f ? 1.f / 256
: x > .02f && x < .98f ? 1.f / 128
: 1.f / 95;
CHECK(inverseGaussianIntegral(y) == Approx(x).margin(margin));
}
// Check inverse_gaussian_integral edge cases.
CHECK(inverseGaussianIntegral(-1) == 0);
CHECK(inverseGaussianIntegral(2) == 1);
CHECK(inverseGaussianIntegral(-std::numeric_limits<float>::infinity()) ==
0);
CHECK(inverseGaussianIntegral(std::numeric_limits<float>::infinity()) == 1);
CHECK(inverseGaussianIntegral(std::numeric_limits<float>::quiet_NaN()) ==
0);
}
} // namespace rive