mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-29 17:58:24 -04:00
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:
+625
-69
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user