diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp index 9c143559..85537e61 100644 --- a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp @@ -136,25 +136,27 @@ namespace { bool writeFramebuffer; bool preserveDestinationAlpha; + bool writeDepth; }; AlphaTestResult classifyAlphaTest(uint64_t testReg, uint8_t alpha) { const bool pass = passesAlphaTest(testReg, alpha); if (pass) - return {true, false}; + return {true, false, true}; // TEST.AFAIL controls what happens when the alpha comparison fails. switch (static_cast((testReg >> 12) & 0x3u)) { case 1: // FB_ONLY - return {true, false}; + return {true, false, false}; + case 2: // ZB_ONLY + return {false, false, true}; case 3: // RGB_ONLY - return {true, true}; + return {true, true, false}; case 0: // KEEP - case 2: // ZB_ONLY default: - return {false, false}; + return {false, false, false}; } } @@ -417,7 +419,7 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, const AlphaTestResult alphaTest = classifyAlphaTest(ctx.test, a); - if (!alphaTest.writeFramebuffer) + if (!alphaTest.writeFramebuffer && !alphaTest.writeDepth) return; u8* vram = gs->m_vram; @@ -544,9 +546,12 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, pixel = Rgba8888ToRgba5551(pixel); } - gs->WriteVram(fpsm, fbp, fbw, x, y, pixel); + if (alphaTest.writeFramebuffer) + { + gs->WriteVram(fpsm, fbp, fbw, x, y, pixel); + } - if (!zmask) + if (alphaTest.writeDepth && !zmask) { gs->WriteVram(zpsm, zbp, fbw, x, y, z); } diff --git a/ps2xTest/src/ps2_gs_tests.cpp b/ps2xTest/src/ps2_gs_tests.cpp index 4c3e8ec1..b501c188 100644 --- a/ps2xTest/src/ps2_gs_tests.cpp +++ b/ps2xTest/src/ps2_gs_tests.cpp @@ -3,6 +3,7 @@ #include "ps2_runtime.h" #include "ps2_stubs.h" #include "ps2_syscalls.h" +#include "runtime/ps2_gs_common.h" #include "runtime/ps2_gs_gpu.h" #include "runtime/ps2_gs_memory.h" #include "runtime/ps2_gs_psmct32.h" @@ -3068,6 +3069,189 @@ void register_ps2_gs_tests() "AFAIL=RGB_ONLY should update RGB while preserving destination alpha"); }); + tc.Run("GS alpha test AFAIL Z-buffer-only writes depth but not color", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kFbw = 1u; + constexpr uint32_t kZbp = 1u; // ZBUF page, distinct from the color page + const uint32_t zBlock = GSInternal::framePageBaseToBlock(kZbp); + + constexpr uint32_t kColorSentinel = 0xAB030201u; + constexpr uint32_t kZSentinel = 0x11111111u; + constexpr uint32_t kDrawnZ = 0x12345678u; + + std::memcpy(vram.data(), &kColorSentinel, sizeof(kColorSentinel)); + gs.WriteVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u, kZSentinel); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + const uint64_t kZbuf = static_cast(kZbp) << 0; // ZBP=1, PSM nibble=0 (Z32), ZMSK=0 + 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 + (2ull << 12) | // AFAIL = ZB_ONLY + (1ull << 17); // ZTST = ALWAYS + constexpr uint64_t kPrim = + static_cast(GS_PRIM_POINT); + constexpr uint64_t kRgbaq = + (0x12ull << 0) | + (0x34ull << 8) | + (0x56ull << 16) | + (0x00ull << 24) | + (0x3F800000ull << 32); // q = 1.0f + const uint64_t kXyz2 = static_cast(kDrawnZ) << 32; + + 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, kXyz2); + + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, kColorSentinel, + "AFAIL=ZB_ONLY must not write the framebuffer when the alpha test fails"); + + const uint32_t depthResult = gs.ReadVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u); + t.Equals(depthResult, kDrawnZ, + "AFAIL=ZB_ONLY must still write depth when the alpha test fails"); + }); + + tc.Run("GS alpha test AFAIL framebuffer-only leaves the depth buffer unwritten", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kFbw = 1u; + constexpr uint32_t kZbp = 1u; // ZBUF page, distinct from the color page + const uint32_t zBlock = GSInternal::framePageBaseToBlock(kZbp); + + constexpr uint32_t kColorSentinel = 0xAB030201u; + constexpr uint32_t kZSentinel = 0x11111111u; + constexpr uint32_t kDrawnZ = 0x12345678u; + + std::memcpy(vram.data(), &kColorSentinel, sizeof(kColorSentinel)); + gs.WriteVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u, kZSentinel); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + const uint64_t kZbuf = static_cast(kZbp) << 0; // ZBP=1, PSM nibble=0 (Z32), ZMSK=0 + 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(GS_PRIM_POINT); + constexpr uint64_t kRgbaq = + (0x12ull << 0) | + (0x34ull << 8) | + (0x56ull << 16) | + (0x00ull << 24) | + (0x3F800000ull << 32); // q = 1.0f + const uint64_t kXyz2 = static_cast(kDrawnZ) << 32; + + 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, kXyz2); + + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, 0x00563412u, + "AFAIL=FB_ONLY must write the framebuffer when the alpha test fails"); + + const uint32_t depthResult = gs.ReadVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u); + t.Equals(depthResult, kZSentinel, + "AFAIL=FB_ONLY must NOT write depth when the alpha test fails"); + }); + + tc.Run("GS alpha test AFAIL RGB-only leaves the depth buffer unwritten", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kFbw = 1u; + constexpr uint32_t kZbp = 1u; // ZBUF page, distinct from the color page + const uint32_t zBlock = GSInternal::framePageBaseToBlock(kZbp); + + constexpr uint32_t kColorSentinel = 0xAB030201u; + constexpr uint32_t kZSentinel = 0x11111111u; + constexpr uint32_t kDrawnZ = 0x12345678u; + + std::memcpy(vram.data(), &kColorSentinel, sizeof(kColorSentinel)); + gs.WriteVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u, kZSentinel); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + const uint64_t kZbuf = static_cast(kZbp) << 0; // ZBP=1, PSM nibble=0 (Z32), ZMSK=0 + 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 kPrim = + static_cast(GS_PRIM_POINT); + constexpr uint64_t kRgbaq = + (0x12ull << 0) | + (0x34ull << 8) | + (0x56ull << 16) | + (0x00ull << 24) | + (0x3F800000ull << 32); // q = 1.0f + const uint64_t kXyz2 = static_cast(kDrawnZ) << 32; + + 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, kXyz2); + + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, 0xAB563412u, + "AFAIL=RGB_ONLY must write RGB and preserve destination alpha when the alpha test fails"); + + const uint32_t depthResult = gs.ReadVram(GS_PSM_Z32, zBlock, kFbw, 0u, 0u); + t.Equals(depthResult, kZSentinel, + "AFAIL=RGB_ONLY must NOT write depth when the alpha test fails"); + }); + tc.Run("GS triangle fan subpixel quad fills rows without interior holes", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u);