refactor: refactor VU1 (#191)

* feat: implement fix and changes based on dark cloud report
fix: fix GS AFAIL for RGB/alpha/Z, ZMSK
fix: fix VU1 flags mask and pipeline
fix: small VU1 cache fix
feat: __ct__, __sinit_ are not sillent stubs anymore

* feat: fix song JP pulling

* feat: sound update for lotR

* feat: prevent guest execution to be very slow

* fix: small gs size bug

* feat: refactor VU
fix: fix cliping and other issues on gs
fix: fix wrong vu0 register on recompiler

* fix fix ACC scheduler stall
feat: remove unused test
fix: .fix overflow e underflow on FMAC

* feat: small setting  for windows test
This commit is contained in:
Ranieri
2026-08-05 14:50:24 -03:00
committed by GitHub
parent 61300792a0
commit f49ca4edbc
34 changed files with 5506 additions and 1015 deletions
+625 -69
View File
@@ -5,6 +5,7 @@
#include "ps2_syscalls.h"
#include "runtime/ps2_gs_gpu.h"
#include "runtime/ps2_gs_memory.h"
#include "runtime/ps2_gs_rasterizer.h"
#include "runtime/ps2_gs_psmct32.h"
#include "runtime/ps2_gs_psmt4.h"
#include "runtime/ps2_gs_psmt8.h"
@@ -297,6 +298,57 @@ namespace
t.Equals(probe, expectedBase, message);
runtime.guestFree(probe);
}
struct GsPixelTestResult
{
uint32_t framebuffer = 0u;
uint32_t depth = 0u;
};
GsPixelTestResult drawGsPixelForTests(uint8_t framePsm,
uint64_t testReg,
bool zmask,
uint32_t initialFramebuffer,
uint32_t initialDepth,
uint8_t sourceAlpha)
{
constexpr uint32_t kFrameBlock = 0u;
constexpr uint32_t kDepthBlock = 32u;
constexpr uint32_t kSourceDepth = 0x22222222u;
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
gs.WriteVram(framePsm, kFrameBlock, 1u, 0u, 0u, initialFramebuffer);
gs.WriteVram(GS_PSM_Z32, kDepthBlock, 1u, 0u, 0u, initialDepth);
const uint64_t frame =
(1ull << 16) |
(static_cast<uint64_t>(framePsm) << 24);
const uint64_t zbuf =
1ull |
(static_cast<uint64_t>(zmask ? 1u : 0u) << 32);
const uint64_t rgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(static_cast<uint64_t>(sourceAlpha) << 24) |
(0x3F800000ull << 32);
gs.writeRegister(GS_REG_FRAME_1, frame);
gs.writeRegister(GS_REG_ZBUF_1, zbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, testReg);
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_POINT));
gs.writeRegister(GS_REG_RGBAQ, rgbaq);
gs.writeRegister(GS_REG_XYZ2, static_cast<uint64_t>(kSourceDepth) << 32);
return {
gs.ReadVram(framePsm, kFrameBlock, 1u, 0u, 0u),
gs.ReadVram(GS_PSM_Z32, kDepthBlock, 1u, 0u, 0u),
};
}
}
void register_ps2_gs_tests()
@@ -623,6 +675,126 @@ void register_ps2_gs_tests()
"context-targeted clear should leave the other context framebuffer untouched");
});
tc.Run("XYZ3 culls a triangle strip primitive without desynchronizing the vertex queue", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kColor = 0xFF0000FFu;
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(6ull << 16) |
(6ull << 48);
auto xyz = [](uint32_t x, uint32_t y) -> uint64_t
{
return static_cast<uint64_t>(x * 16u) |
(static_cast<uint64_t>(y * 16u) << 16);
};
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_TRISTRIP));
gs.writeRegister(GS_REG_RGBAQ, kColor);
// ABC is rejected by XYZ3. D must then draw BCD, not stale ABC.
gs.writeRegister(GS_REG_XYZ2, xyz(0u, 0u));
gs.writeRegister(GS_REG_XYZ2, xyz(6u, 0u));
gs.writeRegister(GS_REG_XYZ3, xyz(0u, 6u));
gs.writeRegister(GS_REG_XYZ2, xyz(6u, 6u));
t.Equals(readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u), 0u,
"XYZ3 should suppress the completed ABC triangle");
t.Equals(readReferencePSMCT32Pixel(vram, 0u, 1u, 4u, 4u), kColor,
"the next XYZ2 should draw BCD from the advanced strip queue");
});
tc.Run("GS fog blends the shaded color toward FOGCOL before framebuffer blending", [](TestCase &t)
{
auto renderFoggedPoint = [](bool fogEnabled, uint8_t fog, uint32_t fogColor = 0u) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kWhite = 0x80FFFFFFull;
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_FOGCOL, fogColor);
gs.writeRegister(
GS_REG_PRIM,
static_cast<uint64_t>(GS_PRIM_POINT) |
(static_cast<uint64_t>(fogEnabled ? 1u : 0u) << 5));
gs.writeRegister(GS_REG_RGBAQ, kWhite);
gs.writeRegister(GS_REG_FOG, static_cast<uint64_t>(fog) << 56);
gs.writeRegister(GS_REG_XYZ2, 0ull);
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
t.Equals(renderFoggedPoint(false, 0x80u), 0x80FFFFFFu,
"FOG and FOGCOL must not affect primitives with FGE disabled");
t.Equals(renderFoggedPoint(true, 0x80u), 0x807F7F7Fu,
"F=0x80 over black FOGCOL should halve the point RGB and preserve alpha");
t.Equals(renderFoggedPoint(true, 0x00u), 0x80000000u,
"F=0 should replace the point RGB with black FOGCOL");
t.Equals(renderFoggedPoint(true, 0x00u, 0x00302010u), 0x802F1F0Fu,
"F=0 should replace point RGB with the programmed FOGCOL");
});
tc.Run("PRMODE supplies primitive attributes while PRMODECONT AC is clear", [](TestCase &t)
{
auto renderPoint = [](bool usePrmodeAttributes) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_FOGCOL, 0ull);
gs.writeRegister(GS_REG_RGBAQ, 0x80FFFFFFull);
gs.writeRegister(GS_REG_FOG, 0ull);
gs.writeRegister(GS_REG_PRMODE, 1ull << 5);
gs.writeRegister(GS_REG_PRMODECONT, usePrmodeAttributes ? 0ull : 1ull);
// FGE is clear in PRIM. AC decides whether that clear bit or
// PRMODE's set bit supplies the effective fog enable.
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_POINT));
gs.writeRegister(GS_REG_XYZ2, 0ull);
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
t.Equals(renderPoint(true), 0x80000000u,
"AC=0 should retain FGE from PRMODE across a PRIM write");
t.Equals(renderPoint(false), 0x80FFFFFFu,
"AC=1 should source FGE from PRIM instead of PRMODE");
});
tc.Run("PABE bypasses alpha blend for low-alpha source pixels", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -1557,6 +1729,20 @@ void register_ps2_gs_tests()
t.Equals(regs.display2, display2, "A+D should write GS DISPLAY2");
});
tc.Run("reserved PSM 0x3F uses null VRAM handlers", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0xA5u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
t.Equals(gs.ReadVram(0x3Fu, 0u, 1u, 0u, 0u), 0u,
"reserved PSM reads should use the null handler");
gs.WriteVram(0x3Fu, 0u, 1u, 0u, 0u, 0x0005180Bu);
t.Equals(static_cast<uint32_t>(vram[0]), 0xA5u,
"reserved PSM writes should leave VRAM unchanged");
});
tc.Run("PSMT4 address mapping matches GS manual layout", [](TestCase &t)
{
constexpr uint32_t kBaseBlock = 0u;
@@ -2488,6 +2674,161 @@ void register_ps2_gs_tests()
"T8 CSM1 CLUT sampling should read CT32-uploaded palette entries through GS swizzled addressing");
});
tc.Run("GS T8 CSM1 applies CSA and masks CSA bit 4 for CT32 CLUTs", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kClutCbp = 128u;
constexpr uint64_t kFrameReg =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_T8) << 20) |
(0ull << 26) |
(0ull << 30) |
(1ull << 34) |
(1ull << 35) |
(static_cast<uint64_t>(kClutCbp) << 37) |
(static_cast<uint64_t>(GS_PSM_CT32) << 51) |
(17ull << 56);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_SPRITE) |
(1ull << 4) |
(1ull << 8);
constexpr uint32_t kExpectedColor = 0xFF204080u;
constexpr uint32_t kWrongNoCsaColor = 0xFF00FF00u;
constexpr uint32_t kWrongBit4Color = 0xFFFF0000u;
const uint32_t texOff = GSPSMT8::addrPSMT8(kTexTbp, 1u, 0u, 0u);
vram[texOff] = 0u;
// CSA=17 is CSA=1 for a CT32 CLUT. Logical entry 16 is at
// physical CSM1 entry 8 after address bits 3 and 4 are swapped.
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 0u, 0u, kWrongNoCsaColor);
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 8u, 0u, kExpectedColor);
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 8u, 16u, kWrongBit4Color);
gs.writeRegister(GS_REG_FRAME_1, kFrameReg);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_ALPHA_1, 0ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, kExpectedColor,
"T8 CSM1 should offset by CSA while CT32 ignores the fifth CSA bit");
});
tc.Run("GS T4 CSM1 preserves CSA bit 4 for CT16 CLUTs", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kClutCbp = 128u;
constexpr uint64_t kFrameReg =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_T4) << 20) |
(0ull << 26) |
(0ull << 30) |
(1ull << 34) |
(1ull << 35) |
(static_cast<uint64_t>(kClutCbp) << 37) |
(static_cast<uint64_t>(GS_PSM_CT16) << 51) |
(16ull << 56);
constexpr uint64_t kTexa = (0x80ull << 32);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_SPRITE) |
(1ull << 4) |
(1ull << 8);
constexpr uint16_t kExpectedRed = 0x801Fu;
constexpr uint16_t kWrongGreen = 0x83E0u;
constexpr uint32_t kExpectedColor = 0x800000F8u;
writePSMT4Texel(vram, kTexTbp, 1u, 0u, 0u, 1u);
// CSA=16 selects the upper half of a CT16 CLUT. CSM1 swaps bits
// 3 and 4 but must preserve address bit 8.
gs.WriteVram(GS_PSM_CT16, kClutCbp, 1u, 1u, 0u, kWrongGreen);
gs.WriteVram(GS_PSM_CT16, kClutCbp, 1u, 1u, 16u, kExpectedRed);
gs.writeRegister(GS_REG_FRAME_1, kFrameReg);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_ALPHA_1, 0ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_TEXA, kTexa);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, kExpectedColor,
"CT16 CSM1 should retain CSA[4] instead of aliasing the upper palette onto the lower one");
});
tc.Run("GS TEX0 dimensions saturate at 1024 pixels", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
GSRasterizer rasterizer;
constexpr uint32_t kTexTbp = 64u;
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(16ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(15ull << 26) |
(15ull << 30) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4);
constexpr uint32_t kExpectedColor = 0xFF3366CCu;
constexpr uint32_t kUnsaturatedColor = 0xFF00FF00u;
gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 1u, 0u, kExpectedColor);
gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 32u, 0u, kUnsaturatedColor);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_PRIM, kPrim);
const uint32_t sampled =
rasterizer.sampleTexture(&gs, 1.0f / 1024.0f, 0.0f, 1.0f, 0u, 0u);
t.Equals(sampled, kExpectedColor,
"TW/TH values above 10 should address a 1024-pixel texture instead of growing beyond GS limits");
});
tc.Run("GS TEX2 updates CLUT state independently from TEX0", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -2975,97 +3316,312 @@ void register_ps2_gs_tests()
"linear filtering should preserve the shared opaque alpha from the CLUT entries");
});
tc.Run("GS alpha test AFAIL framebuffer-only still writes the pixel", [](TestCase &t)
tc.Run("GS CLAMP modes transform texture coordinates before sampling", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
auto renderConstantUv = [](uint64_t clampReg,
uint16_t fixedU,
uint16_t fixedV) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(1ull << 12) | // AFAIL = FB_ONLY
(1ull << 17); // ZTST = ALWAYS
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kTexel0 = 0x800000FFu;
constexpr uint32_t kTexel1 = 0x8000FF00u;
constexpr uint32_t kTexel2 = 0x80FF0000u;
constexpr uint32_t kTexel3 = 0x80FFFFFFu;
constexpr uint32_t kTexelV3 = 0x80FFFF00u;
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(2ull << 26) |
(2ull << 30) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4) |
(1ull << 8);
constexpr uint64_t kRgbaq = 0x3F80000080808080ull;
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 0u, 0u, kTexel0);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 1u, 0u, kTexel1);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 2u, 0u, kTexel2);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 3u, 0u, kTexel3);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 0u, 3u, kTexelV3);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0x00563412u,
"AFAIL=FB_ONLY should still update the framebuffer when the alpha test fails");
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, (3ull << 16) | (3ull << 48));
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_CLAMP_1, clampReg);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
const uint64_t uv =
static_cast<uint64_t>(fixedU) |
(static_cast<uint64_t>(fixedV) << 16);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, 32ull);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, (32ull << 16));
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
constexpr uint64_t kClamp = 1ull;
constexpr uint64_t kRegionClamp =
2ull |
(1ull << 4) |
(2ull << 14);
constexpr uint64_t kRegionRepeat =
3ull |
(1ull << 4) |
(2ull << 14);
t.Equals(renderConstantUv(0ull, 4u * 16u, 0u), 0x800000FFu,
"REPEAT should wrap texel 4 to texel 0 for a four-wide texture");
t.Equals(renderConstantUv(0ull, 0u, 4u * 16u), 0x800000FFu,
"REPEAT should wrap texel row 4 to row 0 for a four-high texture");
t.Equals(renderConstantUv(kClamp, 4u * 16u, 0u), 0x80FFFFFFu,
"CLAMP should hold texel 4 at the last texel");
t.Equals(renderConstantUv(kRegionClamp, 3u * 16u, 0u), 0x80FF0000u,
"REGION_CLAMP should hold texel 3 at MAXU=2");
t.Equals(renderConstantUv(kRegionRepeat, 4u * 16u, 0u), 0x80FF0000u,
"REGION_REPEAT should calculate (U & UMSK) | UFIX");
});
tc.Run("GS alpha test AFAIL RGB-only preserves destination alpha", [](TestCase &t)
tc.Run("GS STQ triangle interpolation divides homogeneous coordinates after DDA", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kTexTbp = 64u;
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(3ull << 12) | // AFAIL = RGB_ONLY
(1ull << 17); // ZTST = ALWAYS
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(2ull << 26) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
constexpr uint32_t kExisting = 0xAB030201u;
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4);
constexpr uint32_t kAffineTexel = 0x800000FFu;
constexpr uint32_t kHomogeneousTexel = 0x8000FF00u;
std::memcpy(vram.data(), &kExisting, sizeof(kExisting));
auto packFloat = [](float value) -> uint32_t
{
uint32_t bits = 0u;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
};
auto packSt = [&](float s, float tVal) -> uint64_t
{
return static_cast<uint64_t>(packFloat(s)) |
(static_cast<uint64_t>(packFloat(tVal)) << 32);
};
auto packRgbaq = [&](float q) -> uint64_t
{
return 0x80808080ull |
(static_cast<uint64_t>(packFloat(q)) << 32);
};
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 1u, 0u, kAffineTexel);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 2u, 0u, kHomogeneousTexel);
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_SCISSOR_1, (4ull << 16) | (4ull << 48));
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_CLAMP_1, 1ull);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0xAB563412u,
"AFAIL=RGB_ONLY should update RGB while preserving destination alpha");
gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(1.0f));
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_ST, packSt(2.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(2.0f));
gs.writeRegister(GS_REG_XYZ2, 64ull);
gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(1.0f));
gs.writeRegister(GS_REG_XYZ2, (64ull << 16));
const uint32_t pixel =
readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u);
t.Equals(pixel, kHomogeneousTexel,
"the DDA should interpolate S=0.75 and Q=1.375, selecting texel 2 after S/Q");
});
tc.Run("GS alpha-test AFAIL independently masks framebuffer and depth", [](TestCase &t)
{
constexpr uint32_t kInitialFramebuffer = 0xAB030201u;
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTestBase =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(1ull << 16) | // ZTE
(1ull << 17); // ZTST = ALWAYS
const GsPixelTestResult keep =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (0ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(keep.framebuffer, kInitialFramebuffer,
"AFAIL=KEEP should preserve the framebuffer");
t.Equals(keep.depth, kInitialDepth,
"AFAIL=KEEP should preserve depth");
const GsPixelTestResult framebufferOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (1ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(framebufferOnly.framebuffer, 0x00563412u,
"AFAIL=FB_ONLY should update RGBA");
t.Equals(framebufferOnly.depth, kInitialDepth,
"AFAIL=FB_ONLY should preserve depth");
const GsPixelTestResult depthOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (2ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(depthOnly.framebuffer, kInitialFramebuffer,
"AFAIL=ZB_ONLY should preserve the framebuffer");
t.Equals(depthOnly.depth, 0x22222222u,
"AFAIL=ZB_ONLY should update depth");
const GsPixelTestResult rgbOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (3ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(rgbOnly.framebuffer, 0xAB563412u,
"AFAIL=RGB_ONLY should preserve destination alpha on CT32");
t.Equals(rgbOnly.depth, kInitialDepth,
"AFAIL=RGB_ONLY should preserve depth");
});
tc.Run("GS RGB_ONLY falls back to FB_ONLY outside CT32", [](TestCase &t)
{
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTest =
1ull |
(5ull << 1) |
(0x80ull << 4) |
(3ull << 12) |
(1ull << 16) |
(1ull << 17);
const GsPixelTestResult ct24 =
drawGsPixelForTests(GS_PSM_CT24, kTest, false,
0x00030201u, kInitialDepth, 0x00u);
t.Equals(ct24.framebuffer, 0x00563412u,
"RGB_ONLY should write the full CT24 framebuffer pixel");
t.Equals(ct24.depth, kInitialDepth,
"RGB_ONLY-as-FB_ONLY should preserve CT24 depth");
const GsPixelTestResult ct16 =
drawGsPixelForTests(GS_PSM_CT16, kTest, false,
0x8001u, kInitialDepth, 0x00u);
t.Equals(ct16.framebuffer, 0x28C2u,
"RGB_ONLY should write RGB and alpha for CT16");
t.Equals(ct16.depth, kInitialDepth,
"RGB_ONLY-as-FB_ONLY should preserve CT16 depth");
});
tc.Run("GS ZMSK suppresses depth without suppressing framebuffer writes", [](TestCase &t)
{
constexpr uint64_t kTest =
1ull |
(5ull << 1) |
(0x80ull << 4) |
(1ull << 16) |
(1ull << 17);
const GsPixelTestResult result =
drawGsPixelForTests(GS_PSM_CT32, kTest, true,
0xAB030201u, 0x11111111u, 0x80u);
t.Equals(result.framebuffer, 0x80563412u,
"a passing alpha test should write the framebuffer");
t.Equals(result.depth, 0x11111111u,
"ZMSK should preserve depth");
});
tc.Run("GS DATE and DATM inspect the framebuffer-format alpha bit", [](TestCase &t)
{
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTestBase =
(1ull << 14) | // DATE
(1ull << 16) | // ZTE
(1ull << 17); // ZTST = ALWAYS
const GsPixelTestResult ct32ZeroPass =
drawGsPixelForTests(GS_PSM_CT32, kTestBase, false,
0x00030201u, kInitialDepth, 0x80u);
t.Equals(ct32ZeroPass.framebuffer, 0x80563412u,
"DATM=0 should accept a clear CT32 alpha bit");
t.Equals(ct32ZeroPass.depth, 0x22222222u,
"a passing CT32 DATE should allow depth");
const GsPixelTestResult ct32OneFail =
drawGsPixelForTests(GS_PSM_CT32, kTestBase, false,
0x80030201u, kInitialDepth, 0x80u);
t.Equals(ct32OneFail.framebuffer, 0x80030201u,
"DATM=0 should reject a set CT32 alpha bit");
t.Equals(ct32OneFail.depth, kInitialDepth,
"a failing CT32 DATE should reject depth");
const GsPixelTestResult ct32OnePass =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (1ull << 15), false,
0x80030201u, kInitialDepth, 0x80u);
t.Equals(ct32OnePass.framebuffer, 0x80563412u,
"DATM=1 should accept a set CT32 alpha bit");
const GsPixelTestResult ct16ZeroPass =
drawGsPixelForTests(GS_PSM_CT16, kTestBase, false,
0x0001u, kInitialDepth, 0x80u);
t.Equals(ct16ZeroPass.framebuffer, 0xA8C2u,
"DATM=0 should accept a clear CT16 alpha bit");
const GsPixelTestResult ct16OneFail =
drawGsPixelForTests(GS_PSM_CT16, kTestBase, false,
0x8001u, kInitialDepth, 0x80u);
t.Equals(ct16OneFail.framebuffer, 0x8001u,
"DATM=0 should reject a set CT16 alpha bit");
t.Equals(ct16OneFail.depth, kInitialDepth,
"a failing CT16 DATE should reject depth");
const GsPixelTestResult ct16OnePass =
drawGsPixelForTests(GS_PSM_CT16, kTestBase | (1ull << 15), false,
0x8001u, kInitialDepth, 0x80u);
t.Equals(ct16OnePass.framebuffer, 0xA8C2u,
"DATM=1 should accept a set CT16 alpha bit");
const GsPixelTestResult ct24DatmZero =
drawGsPixelForTests(GS_PSM_CT24, kTestBase, false,
0x00030201u, kInitialDepth, 0x80u);
const GsPixelTestResult ct24DatmOne =
drawGsPixelForTests(GS_PSM_CT24, kTestBase | (1ull << 15), false,
0x00030201u, kInitialDepth, 0x80u);
t.Equals(ct24DatmZero.framebuffer, 0x00563412u,
"CT24 DATE should pass for DATM=0");
t.Equals(ct24DatmOne.framebuffer, 0x00563412u,
"CT24 DATE should pass for DATM=1");
t.Equals(ct24DatmOne.depth, 0x22222222u,
"CT24 DATE should not block depth");
});
tc.Run("GS triangle fan subpixel quad fills rows without interior holes", [](TestCase &t)