diff --git a/CMakeLists.txt b/CMakeLists.txt index 590e82f13a..291b570fe6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1374,6 +1374,7 @@ if(UNITTEST) unittest/TestLzrc.cpp unittest/TestMpegCsc.cpp unittest/TestSplineTessellation.cpp + unittest/TestGEMath.cpp unittest/TestDemangle.cpp unittest/TestTextureReplacer.cpp unittest/TestRiscVEmitter.cpp diff --git a/android/jni/Android.mk b/android/jni/Android.mk index c6b6c6c911..7147d4b93f 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -1060,6 +1060,7 @@ ifeq ($(UNITTEST),1) $(SRC)/unittest/TestLzrc.cpp \ $(SRC)/unittest/TestMpegCsc.cpp \ $(SRC)/unittest/TestSplineTessellation.cpp \ + $(SRC)/unittest/TestGEMath.cpp \ $(SRC)/unittest/TestZipSlip.cpp \ $(SRC)/unittest/UnitTest.cpp diff --git a/unittest/TestGEMath.cpp b/unittest/TestGEMath.cpp new file mode 100644 index 0000000000..d76f98c88c --- /dev/null +++ b/unittest/TestGEMath.cpp @@ -0,0 +1,213 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +// The GE arithmetic in GPU/Software/GEMath.h against values measured on a PSP. The probes are in +// pspautotests/tests/gpu/probe (expNN.py), with notes in the ppsspp-re repository: reciprocals from +// exp15 and exp38-41, the AA line pixels from exp112. The rest are cases those probes pinned down. + +#include +#include +#include + +#include "GPU/Software/GEMath.h" + +#include "unittest/UnitTest.h" + +static float FromBits(uint32_t u) { + float f; + memcpy(&f, &u, sizeof(f)); + return f; +} + +static uint32_t ToBits(float f) { + uint32_t u; + memcpy(&u, &f, sizeof(u)); + return u; +} + +static bool TestFloat24() { + // 15 stored mantissa bits, truncated toward zero. + EXPECT_EQ_FLOAT(TruncateToFloat24(1.0f + ldexpf(1.0f, -16)), 1.0f); + EXPECT_EQ_FLOAT(TruncateToFloat24(1.0f + ldexpf(1.0f, -15)), 1.0f + ldexpf(1.0f, -15)); + EXPECT_EQ_FLOAT(TruncateToFloat24(-1.0f - ldexpf(3.0f, -17)), -1.0f); + EXPECT_EQ_FLOAT(TruncateToFloat24(-1.0f - ldexpf(5.0f, -17)), -1.0f - ldexpf(1.0f, -15)); + // The product is truncated in double, not rounded to a float first. + EXPECT_EQ_FLOAT(ProductToFloat24(1.0 + ldexp(1.0, -15) + ldexp(1.0, -16) - ldexp(1.0, -40)), 1.0f + ldexpf(1.0f, -15)); + return true; +} + +static bool TestGERecip() { + // 1 / (1 + i / 32768) in units of 2^-16, measured on a PSP for every mantissa (exp15). + static const int measured[][2] = { + { 0, 65536 }, { 1, 65534 }, { 255, 65030 }, { 256, 65027 }, { 909, 63768 }, { 1149, 63316 }, { 2816, 60350 }, { 4727, 57274 }, + { 8168, 52459 }, { 8423, 52135 }, { 8953, 51472 }, { 10218, 49957 }, { 10809, 49280 }, { 13408, 46507 }, { 13869, 46047 }, { 14837, 45110 }, + { 15497, 44494 }, { 16762, 43357 }, { 17635, 42606 }, { 18007, 42294 }, { 21093, 39871 }, { 24689, 37376 }, { 24729, 37350 }, { 26789, 36057 }, + { 26951, 35960 }, { 27112, 35863 }, { 27453, 35660 }, { 28359, 35131 }, { 28463, 35072 }, { 31067, 33641 }, { 31123, 33612 }, { 32767, 32769 }, + }; + for (const auto &m : measured) { + const float w = FromBits(0x3F800000 | (m[0] << 8)); + const float expected = ldexpf((float)m[1], -16); + if (GERecip(w) != expected) { + printf("GERecip(1 + %d / 32768) = %.9f, PSP %.9f\n", m[0], GERecip(w), expected); + return false; + } + } + // Sign and exponent are carried over. + EXPECT_EQ_FLOAT(GERecip(-2.0f), -0.5f); + EXPECT_EQ_FLOAT(GERecip(0.25f), 4.0f); + return true; +} + +static bool TestGERsqrt() { + // The table for odd exponents stores 1 / sqrt(2 * 1.i) (exp69). + EXPECT_EQ_FLOAT(GERsqrt(1.0f), 1.0f); + EXPECT_EQ_FLOAT(GERsqrt(4.0f), 0.5f); + EXPECT_EQ_FLOAT(GERsqrt(0.25f), 2.0f); + EXPECT_EQ_FLOAT(GERsqrt(2.0f), ldexpf(46340.0f, -16)); + return true; +} + +static bool TestGEAdd() { + // No guard bits: the smaller term is truncated (toward zero) to the larger one's last bit. + EXPECT_EQ_FLOAT(GEAddFloat24(1.0f, ldexpf(3.0f, -17)), 1.0f); + EXPECT_EQ_FLOAT(GEAddFloat24(1.0f, ldexpf(5.0f, -17)), 1.0f + ldexpf(1.0f, -15)); + EXPECT_EQ_FLOAT(GEAddFloat24(1.0f, -ldexpf(5.0f, -17)), 1.0f - ldexpf(1.0f, -15)); + EXPECT_EQ_FLOAT(GEAddFloat24(0.0f, 1.0f + ldexpf(1.0f, -20)), 1.0f); + return true; +} + +static bool TestGEDot() { + // A row is summed in one go: every product truncated at the largest term's last bit, so two small + // terms that would carry together in pairwise adds are both dropped. + EXPECT_EQ_FLOAT(GEDot(Vec3f(1.0f, 1.0f, 1.0f), Vec3f(1.0f, ldexpf(1.0f, -16), ldexpf(1.0f, -16))), 1.0f); + EXPECT_EQ_FLOAT(GEDot(Vec3f(2.0f, 3.0f, 0.0f), Vec3f(0.5f, 0.25f, 9.0f)), 1.75f); + // Normalization goes through GERsqrt, and returns the length the same way. + Vec3f v(3.0f, 4.0f, 0.0f); + const float len = GENormalize(v); + EXPECT_EQ_FLOAT(len, ProductToFloat24(25.0 * GERsqrt(25.0f))); + EXPECT_EQ_FLOAT(v.x, ProductToFloat24(3.0 * GERsqrt(25.0f))); + EXPECT_EQ_FLOAT(v.y, ProductToFloat24(4.0 * GERsqrt(25.0f))); + EXPECT_TRUE(fabsf(len - 5.0f) < 0.001f); + return true; +} + +static bool TestGESetupRecip() { + // The triangle setup's reciprocal for 16-bit significand indices, measured on a PSP (exp38-41). + static const int measured[][2] = { + { 0, 65536 }, { 2, 65534 }, { 254, 65282 }, { 256, 65280 }, { 2594, 63040 }, { 4086, 61689 }, { 7588, 58735 }, { 10520, 56471 }, + { 18132, 51333 }, { 19190, 50692 }, { 21126, 49560 }, { 23770, 48092 }, { 24120, 47905 }, { 27582, 46123 }, { 27812, 46010 }, { 27998, 45918 }, + { 29846, 45029 }, { 30100, 44909 }, { 33802, 43235 }, { 34636, 42876 }, { 35440, 42534 }, { 40160, 40635 }, { 40842, 40374 }, { 47082, 38137 }, + { 47536, 37984 }, { 50630, 36973 }, { 50882, 36892 }, { 57148, 35008 }, { 60100, 34186 }, { 60570, 34058 }, { 63526, 33278 }, { 65534, 32768 }, + }; + for (const auto &m : measured) { + int e = 0; + const int64_t q = GESetupRecip(65536 + m[0], &e); + if (q != m[1] || e != 16) { + printf("GESetupRecip(index %d) = %lld (e %d), PSP %d\n", m[0], (long long)q, e, m[1]); + return false; + } + // Only the top 16 bits count. + const int64_t q2 = GESetupRecip((uint64_t)(65536 + m[0]) << 5 | 31, &e); + EXPECT_EQ_INT(q2, m[1]); + EXPECT_EQ_INT(e, 21); + } + return true; +} + +static bool TestGELog16() { + // The exponent and the top 4 mantissa bits, in 1/16 (exp58-60). + EXPECT_EQ_INT(GELog16(1.0f), 0); + EXPECT_EQ_INT(GELog16(2.0f), 16); + EXPECT_EQ_INT(GELog16(0.5f), -16); + EXPECT_EQ_INT(GELog16(1.5f), 8); + EXPECT_EQ_INT(GELog16(0.75f), -8); + EXPECT_EQ_INT(GELog16(1.0625f), 1); + EXPECT_EQ_INT(GELog16(1.0624f), 0); + return true; +} + +static bool TestTexCoordPrecision() { + // A UV plane keeps 15 significant bits of a lone value (exp83). + EXPECT_EQ_FLOAT(GETruncateTexCoord(300015.75f), 300000.0f); + EXPECT_EQ_FLOAT(GETruncateTexCoord(-300015.75f), -300000.0f); + EXPECT_EQ_FLOAT(GETruncateTexCoord(100.75f), 100.75f); + // u = s * R(q) keeps 24 significant bits, truncated (exp82). + EXPECT_EQ_FLOAT(GEUVProduct(1.0 + ldexp(1.0, -23) + ldexp(1.0, -30)), 1.0f + ldexpf(1.0f, -23)); + EXPECT_EQ_FLOAT(GEUVProduct(1.0 + ldexp(1.0, -24)), 1.0f); + return true; +} + +static bool TestGELightPow() { + // Mitchell's approximation: straight lines between powers of two (exp98, exp102). + EXPECT_EQ_FLOAT(GELightPow(0.5f, 2.0f), 0.25f); + EXPECT_EQ_FLOAT(GELightPow(0.75f, 2.0f), 0.5f); + EXPECT_EQ_FLOAT(GELightPow(0.3f, 0.0f), 1.0f); + EXPECT_EQ_FLOAT(GELightPow(-0.5f, 2.0f), -0.5f); + // The exponent times the bits is exact and floored; a rounded float multiply gives 0x3DA64400. + EXPECT_EQ_HEX(ToBits(GELightPow(FromBits(0x3DC22200), 1.0625f)), 0x3DA64420U); + return true; +} + +static bool TestGELightColor() { + // 8-bit color products and scaling, ((2a + 1) * (2b + 1)) >> 10 (exp61-63; exp62 all 65536 pairs). + // GELightColorProduct takes the colors already expanded as 2c + 1. + const Vec4 white = Vec4::AssignToAll(2 * 255 + 1); + EXPECT_EQ_INT(GELightColorProduct(white, white).x, 255); + EXPECT_EQ_INT(GELightColorProduct(Vec4::AssignToAll(2 * 128 + 1), Vec4::AssignToAll(2 * 200 + 1)).x, 100); + EXPECT_EQ_INT(GELightColorProduct(Vec4::AssignToAll(1), Vec4::AssignToAll(1)).x, 0); + // A factor becomes s = floor(256 f), expanded like a color; 1 leaves the color alone. + EXPECT_EQ_INT(GELightColorScale(Vec4::AssignToAll(200), 1.0f).x, 200); + EXPECT_EQ_INT(GELightColorScale(Vec4::AssignToAll(200), 0.5f).x, 100); + EXPECT_EQ_INT(GELightColorScale(Vec4::AssignToAll(255), 0.999f).x, 255); + EXPECT_EQ_INT(GELightColorScale(Vec4::AssignToAll(255), 0.99f).x, 253); + return true; +} + +static bool TestGELineCoverageAlpha() { + // Antialiased line pixels measured on a PSP (exp112): line ends in subpixels, the pixel, its alpha. + // The first four have the offset exactly on a step. + static const int measured[][7] = { + { 831, 146, 567, 227, 48, 10, 47 }, { 831, 146, 567, 227, 37, 13, 113 }, { 2470, 191, 2544, 154, 158, 9, 31 }, { 2470, 191, 2544, 154, 156, 10, 31 }, + { 328, 240, 198, 171, 13, 11, 73 }, { 7310, 112, 7502, 157, 461, 8, 22 }, { 5529, 766, 5559, 569, 347, 37, 37 }, { 4038, 929, 4232, 1136, 253, 59, 71 }, + { 2801, 1456, 2632, 1657, 172, 94, 31 }, { 1616, 2013, 1699, 1796, 101, 124, 127 }, { 2279, 2535, 2310, 2229, 144, 141, 40 }, { 7629, 2356, 7550, 2350, 474, 147, 20 }, + { 6299, 2652, 6609, 2836, 403, 171, 109 }, { 4407, 3243, 4531, 3239, 275, 202, 80 }, + }; + for (const auto &m : measured) { + const int a = GELineCoverageAlpha(m[0], m[1], m[2], m[3], m[4], m[5]); + if (a != m[6]) { + printf("GELineCoverageAlpha((%d, %d) - (%d, %d), pixel %d, %d) = %d, PSP %d\n", m[0], m[1], m[2], m[3], m[4], m[5], a, m[6]); + return false; + } + } + return true; +} + +bool TestGEMath() { + bool ok = true; + ok = TestFloat24() && ok; + ok = TestGERecip() && ok; + ok = TestGERsqrt() && ok; + ok = TestGEAdd() && ok; + ok = TestGEDot() && ok; + ok = TestGESetupRecip() && ok; + ok = TestGELog16() && ok; + ok = TestTexCoordPrecision() && ok; + ok = TestGELightPow() && ok; + ok = TestGELightColor() && ok; + ok = TestGELineCoverageAlpha() && ok; + return ok; +} diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 8831ccf636..32c0108a1b 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -2943,6 +2943,7 @@ bool TestZipSlip(); bool TestLzrc(); bool TestMpegCsc(); bool TestSplineTessellation(); +bool TestGEMath(); bool TestDemangle(); // The 8.3 short names games read out of d_private. These aren't verified against hardware yet (no @@ -3165,6 +3166,7 @@ TestItem availableTests[] = { TEST_ITEM(Lzrc), TEST_ITEM(MpegCsc), TEST_ITEM(SplineTessellation), + TEST_ITEM(GEMath), TEST_ITEM(Demangle), TEST_ITEM(TextureReplacer), TEST_ITEM(UITabOrder), diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index 3602781f19..094d9a6951 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -297,6 +297,7 @@ + diff --git a/unittest/UnitTests.vcxproj.filters b/unittest/UnitTests.vcxproj.filters index 2bd5c4dd27..17dbb60231 100644 --- a/unittest/UnitTests.vcxproj.filters +++ b/unittest/UnitTests.vcxproj.filters @@ -20,6 +20,7 @@ +