mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
Add GEMath unit tests
The GE arithmetic in GEMath.h against values measured on a PSP: the reciprocal for sampled mantissas (gpu/probe exp15), the triangle setup reciprocal (exp38-41), antialiased line alphas (exp112), and the cases the other probes pinned down (float24 truncation, the adder and row sums, rsqrt, the mip log2, texture coordinate precision, the lighting pow and color scaling). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
32c41a3417
commit
87f50b9609
6 files changed
+219
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#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<int> white = Vec4<int>::AssignToAll(2 * 255 + 1);
|
||||
EXPECT_EQ_INT(GELightColorProduct(white, white).x, 255);
|
||||
EXPECT_EQ_INT(GELightColorProduct(Vec4<int>::AssignToAll(2 * 128 + 1), Vec4<int>::AssignToAll(2 * 200 + 1)).x, 100);
|
||||
EXPECT_EQ_INT(GELightColorProduct(Vec4<int>::AssignToAll(1), Vec4<int>::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<int>::AssignToAll(200), 1.0f).x, 200);
|
||||
EXPECT_EQ_INT(GELightColorScale(Vec4<int>::AssignToAll(200), 0.5f).x, 100);
|
||||
EXPECT_EQ_INT(GELightColorScale(Vec4<int>::AssignToAll(255), 0.999f).x, 255);
|
||||
EXPECT_EQ_INT(GELightColorScale(Vec4<int>::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;
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -297,6 +297,7 @@
|
||||
<ClCompile Include="TestLzrc.cpp" />
|
||||
<ClCompile Include="TestMpegCsc.cpp" />
|
||||
<ClCompile Include="TestSplineTessellation.cpp" />
|
||||
<ClCompile Include="TestGEMath.cpp" />
|
||||
<ClCompile Include="TestThreadManager.cpp" />
|
||||
<ClCompile Include="TestTextureReplacer.cpp" />
|
||||
<ClCompile Include="TestVertexJit.cpp" />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ClCompile Include="TestLzrc.cpp" />
|
||||
<ClCompile Include="TestMpegCsc.cpp" />
|
||||
<ClCompile Include="TestSplineTessellation.cpp" />
|
||||
<ClCompile Include="TestGEMath.cpp" />
|
||||
<ClCompile Include="TestTextureReplacer.cpp" />
|
||||
<ClCompile Include="TestRiscVEmitter.cpp" />
|
||||
<ClCompile Include="TestVFS.cpp" />
|
||||
|
||||
Reference in new issue
Block a user