Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,10 @@ void GSCpuBackend::WritePixel(const GSDrawState &state, int x, int y, int z, uin
return;
}

const uint32_t ztestMethod = static_cast<uint32_t>((ctx.test >> 17) & 3u);
// Disabling TEST.ZTE bypasses depth testing and also prevents depth writes.
// The stored ZTST value does not apply while the test is disabled.
const bool ztestEnabled = ((ctx.test >> 16) & 1u) != 0u;
const uint32_t ztestMethod = ztestEnabled ? static_cast<uint32_t>((ctx.test >> 17) & 3u) : 1u;
const bool alphaBlendEnabled = state.prim.abe;
const bool preserveDestinationAlpha = writeMask.writeRgb && !writeMask.writeAlpha && fpsm == GS_PSM_CT32;
const bool destinationAlphaTestNeedsRead = ((ctx.test >> 14) & 0x1u) != 0u && (fpsm == GS_PSM_CT32 || fpsm == GS_PSM_CT16 || fpsm == GS_PSM_CT16S);
Expand Down Expand Up @@ -1006,7 +1009,7 @@ void GSCpuBackend::WritePixel(const GSDrawState &state, int x, int y, int z, uin
WriteVramUnlocked(fpsm, fbp, fbw, x, y, pixel);
}

if (writeMask.writeDepth && !ctx.zbuf.zmask)
if (ztestEnabled && writeMask.writeDepth && !ctx.zbuf.zmask)
{
WriteVramUnlocked(zpsm, zbp, fbw, x, y, z);
}
Expand Down
4 changes: 4 additions & 0 deletions ps2xTest/gs_cache/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ add_gs_cache_suite(ps2_gs_clut_cache_tests gs_clut_cache_tests.cpp clut
reserved_cld nonindexed_cld tex2_reload shared_contexts
csa_ct32 csa_ct16 csa_ct16s texa_without_reload palette_before_filtering high_planes)

add_gs_cache_suite(ps2_gs_depth_test_tests gs_depth_test_tests.cpp depth
movie_sprite disabled_methods enabled_never_always enabled_comparisons
depth_write_mask alpha_zonly)

if(PS2_GS_CACHE_BUILD_MEMORY_TESTS)
add_gs_cache_suite(ps2_gs_memory_cache_tests gs_memory_cache_tests.cpp memory
ct32 ct24 ct16 ct16s t8 t4 t8h t4hl t4hh z32 z24 z16 z16s
Expand Down
32 changes: 32 additions & 0 deletions ps2xTest/gs_cache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# GS backend tests

This standalone CMake package compiles the actual GS CPU backend, frontend and
memory implementation. It covers texture, CLUT and memory caches, plus the pixel
pipeline's depth-test behavior. It uses synthetic VRAM and public backend APIs;
no game assets, generated guest code or runtime substitutes are included.

```sh
cmake -S ps2xTest/gs_cache -B build/gs-cache
cmake --build build/gs-cache --config RelWithDebInfo
ctest --test-dir build/gs-cache -C RelWithDebInfo --output-on-failure
```

On Windows, configure with `-G "Visual Studio 17 2022" -A x64` and, to reproduce
the native MSVC check, add
`-DCMAKE_CXX_FLAGS_RELWITHDEBINFO="/O2 /Ob1 /DNDEBUG /Zi /fp:strict"`.
`PS2_GS_RUNTIME_DIR` can select another runtime source tree; the default is this
repository's `ps2xRuntime`. GCC/Clang sanitizers are optional through
`PS2_GS_CACHE_SANITIZERS=ON`.

The six `gs_cache.depth.*` cases cover:

- A synthetic 640x448 texture drawn with the observed opening-movie register and
field-sprite state: `TEST=0`, RGB DECAL, zero vertex color and two 224-line fields.
- Every stored ZTST method with ZTE disabled, including preservation of unmasked Z.
- Enabled NEVER, ALWAYS, GEQUAL and GREATER, including equality boundaries.
- ZBUF write masking and alpha-failure ZB_ONLY with disabled, rejected and enabled Z.

Filter these cases with `ctest --test-dir build/gs-cache -C RelWithDebInfo
-R gs_cache.depth --output-on-failure`. The package does not exercise the complete
runtime, movie decoding or presentation, and does not establish retail gameplay
or Linux validation.
148 changes: 148 additions & 0 deletions ps2xTest/gs_cache/gs_depth_test_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include "gs_test_support.h"

using namespace GSTest;

namespace
{
constexpr uint32_t kDepthPage = 300u;
constexpr uint32_t kStoredZ = 100u;

GSPrimitiveBatch solidSprite(uint64_t test, uint32_t z, bool maskDepth = false)
{
auto batch = sprite(texture(), 0, 0);
batch.state.prim.tme = false;
batch.state.context.test = test;
batch.state.context.zbuf.zbp = kDepthPage;
batch.state.context.zbuf.psm = GS_PSM_Z32;
batch.state.context.zbuf.zmask = maskDepth;
for (auto& vertex : batch.vertices)
{
vertex.r = 248;
vertex.g = vertex.b = 0;
vertex.a = 128;
vertex.z = static_cast<float>(z);
}
return batch;
}

void checkDraw(uint64_t test, uint32_t z, bool maskDepth,
bool writesColor, bool writesDepth)
{
BackendFixture f;
f.backend.WriteVram(GS_PSM_CT32, kOutputPage * 32u, 1, 0, 0, kGreen);
f.backend.WriteVram(GS_PSM_Z32, kDepthPage * 32u, 1, 0, 0, kStoredZ);
f.backend.Submit(solidSprite(test, z, maskDepth));
expectEqual(f.backend.ReadVram(GS_PSM_CT32, kOutputPage * 32u, 1, 0, 0),
writesColor ? kRed : kGreen, "framebuffer after depth test");
expectEqual(f.backend.ReadVram(GS_PSM_Z32, kDepthPage * 32u, 1, 0, 0),
writesDepth ? z : kStoredZ, "depth buffer after depth test");
}

void movieSprite()
{
BackendFixture f;
auto tex = texture(GS_PSM_CT32, 4480);
tex.tbw = 10;
tex.tw = tex.th = 10;
tex.tcc = 0;
tex.tfx = 1; // DECAL: RGB comes from the texture even with vertex RGB=0.
const uint32_t colors[] = {kRed, kGreen, kBlue};
for (uint32_t y = 0; y < 448; ++y)
for (uint32_t x = 0; x < 640; ++x)
f.backend.WriteVram(GS_PSM_CT32, tex.tbp0, tex.tbw, x, y,
colors[x < 200 ? 0 : (x < 400 ? 1 : 2)]);

// Register/vertex state observed in the retail opening movie. Both
// field sprites draw into one 640x448 framebuffer; pixels are synthetic.
auto batch = sprite(tex, 0, 0, true);
batch.state.context.frame.fbp = 0;
batch.state.context.frame.fbw = 10;
batch.state.context.scissor = {0, 639, 0, 447};
batch.state.context.test = 0;
batch.state.textureWidth = batch.state.textureHeight = 1024;
for (auto& vertex : batch.vertices)
vertex.r = vertex.g = vertex.b = vertex.a = 0;
batch.vertices[0].u = 8;
batch.vertices[0].v = 24;
batch.vertices[1].u = 10248;
batch.vertices[1].v = 7192;
batch.vertices[1].x = 640;
batch.vertices[1].y = 224;
f.backend.Submit(batch);
batch.vertices[0].y = 224;
batch.vertices[1].y = 448;
batch.vertices[0].v = 8;
batch.vertices[1].v = 7176;
f.backend.Submit(batch);

// Sample within the image: the captured half-pixel UV offsets and
// linear filter can blend the final edge with the unused texture area.
for (uint32_t y : {4u, 111u, 219u, 228u, 335u, 443u})
for (uint32_t x : {10u, 100u, 300u, 500u, 630u})
{
const uint32_t expected = colors[x < 200 ? 0 : (x < 400 ? 1 : 2)] & 0x00FFFFFFu;
expectEqual(f.backend.ReadVram(GS_PSM_CT32, 0, 10, x, y), expected,
"TEST=0 movie field must render textured RGB");
}
}

void disabledMethods()
{
for (uint32_t method = 0; method < 4; ++method)
{
// A stale NEVER/GEQUAL/GREATER selection cannot reject color or
// write depth while ZTE=0, even when ZBUF.ZMSK is clear.
checkDraw(uint64_t(method) << 17, 99, false, true, false);
checkDraw(uint64_t(method) << 17, 101, true, true, false);
}
}

void enabledNeverAlways()
{
for (uint32_t z : {99u, 100u, 101u})
{
checkDraw(1ull << 16, z, false, false, false);
checkDraw((1ull << 16) | (1ull << 17), z, false, true, true);
}
}

void enabledComparisons()
{
for (uint32_t z : {99u, 100u, 101u})
{
checkDraw((1ull << 16) | (2ull << 17), z, false, z >= kStoredZ, z >= kStoredZ);
checkDraw((1ull << 16) | (3ull << 17), z, false, z > kStoredZ, z > kStoredZ);
}
}

void depthWriteMask()
{
for (uint32_t method = 0; method < 4; ++method)
checkDraw((1ull << 16) | (uint64_t(method) << 17), 101, true,
method != 0, false);
}

void alphaZonly()
{
// ATE=1, ATST=NEVER, AFAIL=ZB_ONLY preserves the framebuffer. It
// cannot revive writes when depth testing is disabled or rejects Z.
constexpr uint64_t alphaFailZonly = 1ull | (2ull << 12);
for (uint32_t method = 0; method < 4; ++method)
{
checkDraw(alphaFailZonly | (uint64_t(method) << 17), 101, false, false, false);
checkDraw(alphaFailZonly | (1ull << 16) | (uint64_t(method) << 17),
101, false, false, method != 0);
}
checkDraw(alphaFailZonly | (1ull << 16) | (1ull << 17), 101, true, false, false);
checkDraw(alphaFailZonly | (1ull << 16) | (3ull << 17), 99, false, false, false);
}
}

int main(int argc, char** argv)
{
return run(argc, argv, {
{"movie_sprite", movieSprite}, {"disabled_methods", disabledMethods},
{"enabled_never_always", enabledNeverAlways}, {"enabled_comparisons", enabledComparisons},
{"depth_write_mask", depthWriteMask}, {"alpha_zonly", alphaZonly}
});
}