blob: f94b4fd562a500146ddea1b75ed9cfdeb131051c [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));
}
// Borrowed from:
// https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion
float half_to_float(uint16_t x)
{
// IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15,
// +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
const uint32_t e = (x & 0x7C00) >> 10; // exponent
const uint32_t m = (x & 0x03FF) << 13; // mantissa
// evil log2 bit hack to count leading zeros in denormalized format
const uint32_t v = math::bit_cast<uint32_t>(static_cast<float>(m)) >> 23;
return math::bit_cast<float>(
(x & 0x8000) << 16 | (e != 0) * ((e + 112) << 23 | m) |
((e == 0) & (m != 0)) *
((v - 37) << 23 |
((m << (150 - v)) &
0x007FE000))); // sign : normalized : denormalized
}
TEST_CASE("gaussian_integral_table", "[gpu]")
{
float gaussianTable[gpu::GAUSSIAN_TABLE_SIZE];
for (int i = 0; i < gpu::GAUSSIAN_TABLE_SIZE; ++i)
{
gaussianTable[i] = half_to_float(gpu::g_gaussianIntegralTableF16[i]);
}
CHECK(gaussianTable[0] >= 0);
CHECK(gaussianTable[0] <= expf(-.5f * FEATHER_TEXTURE_STDDEVS));
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] <= 1);
CHECK(gaussianTable[gpu::GAUSSIAN_TABLE_SIZE - 1] >=
1 - expf(-.5f * FEATHER_TEXTURE_STDDEVS));
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));
}
}
TEST_CASE("inverse_gaussian_integral_table", "[gpu]")
{
CHECK(gpu::g_inverseGaussianIntegralTableF32[0] == 0);
CHECK(
gpu::g_inverseGaussianIntegralTableF32[gpu::GAUSSIAN_TABLE_SIZE - 1] ==
1);
if (gpu::GAUSSIAN_TABLE_SIZE & 1)
{
CHECK(gpu::g_inverseGaussianIntegralTableF32[gpu::GAUSSIAN_TABLE_SIZE /
2] == .5f);
}
else
{
CHECK((gpu::g_inverseGaussianIntegralTableF32
[gpu::GAUSSIAN_TABLE_SIZE / 2 - 1] +
gpu::g_inverseGaussianIntegralTableF32[gpu::GAUSSIAN_TABLE_SIZE /
2]) /
2 ==
Approx(.5f).margin(1e-4f));
}
for (int i = 1; i < gpu::GAUSSIAN_TABLE_SIZE; ++i)
{
CHECK(gpu::g_inverseGaussianIntegralTableF32[i - 1] <=
gpu::g_inverseGaussianIntegralTableF32[i]);
}
for (int i = 0; i < (gpu::GAUSSIAN_TABLE_SIZE + 1) / 2 - 4; ++i)
{
CHECK(gpu::g_inverseGaussianIntegralTableF32[i] +
gpu::g_inverseGaussianIntegralTableF32
[gpu::GAUSSIAN_TABLE_SIZE - 1 - i] ==
Approx(1).margin(i > 100 ? 1e-4f
: i > 4 ? 1e-3f
: 1e-2f));
}
// Check that the inverse table is actually an inverse of the gaussian
// integral.
float gaussianTable[gpu::GAUSSIAN_TABLE_SIZE];
for (int i = 0; i < gpu::GAUSSIAN_TABLE_SIZE; ++i)
{
gaussianTable[i] = half_to_float(gpu::g_gaussianIntegralTableF16[i]);
}
float M = 21;
for (float x = 0; x <= 1; x += 1.f / (gpu::GAUSSIAN_TABLE_SIZE * M))
{
float y = gpu::gaussian_table_lookup(gaussianTable, x);
float inverseY = gpu::inverse_gaussian_integral(y);
// 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(inverseY == Approx(x).margin(margin));
}
// Check inverse_gaussian_integral edge cases.
CHECK(gpu::inverse_gaussian_integral(-1) == 0);
CHECK(gpu::inverse_gaussian_integral(2) == 1);
CHECK(gpu::inverse_gaussian_integral(
-std::numeric_limits<float>::infinity()) == 0);
CHECK(gpu::inverse_gaussian_integral(
std::numeric_limits<float>::infinity()) == 1);
CHECK(gpu::inverse_gaussian_integral(
std::numeric_limits<float>::quiet_NaN()) == 0);
}
} // namespace rive