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
19 changes: 18 additions & 1 deletion ps2xRuntime/src/lib/gs/gs_cpu_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
Expand Down Expand Up @@ -1224,6 +1225,12 @@ void GSCpuBackend::DrawTriangle(const GSPrimitiveBatch &batch)
const float winding = (denom < 0.0f) ? -1.0f : 1.0f;
const float invAbsDenom = 1.0f / std::fabs(denom);
constexpr float kEdgeEpsilon = 1.0e-4f;
static const bool disableEarlyDepth = std::getenv("PS2X_GS_DISABLE_EARLY_DEPTH") != nullptr;
const uint32_t depthMethod = static_cast<uint32_t>((ctx.test >> 17u) & 3u);
const bool earlyDepth = !disableEarlyDepth && ((ctx.test >> 16u) & 1u) != 0u && depthMethod >= 2u;
const uint32_t depthBase = GSInternal::framePageBaseToBlock(ctx.zbuf.zbp);
const uint32_t depthWidth = std::max<uint32_t>(ctx.frame.fbw, 1u);
const auto &depthReader = m_readVramFuncs[ctx.zbuf.psm & 0x3Fu];

for (int y = minY; y <= maxY; ++y)
{
Expand All @@ -1240,6 +1247,16 @@ void GSCpuBackend::DrawTriangle(const GSPrimitiveBatch &batch)
continue;

double z = v0.z * w0 + v1.z * w1 + v2.z * w2;
const uint32_t pixelZ = static_cast<uint32_t>(z + 0.5);

// A failing depth test cannot store color or depth, regardless of
// alpha-test mode. Passing pixels retain the existing write path.
if (earlyDepth)
{
const uint32_t storedZ = depthReader(m_vram, depthBase, depthWidth, x, y);
if (pixelZ < storedZ || (depthMethod == 3u && pixelZ == storedZ))
continue;
}

uint8_t r, g, b, a;
if (state.prim.iip)
Expand Down Expand Up @@ -1302,7 +1319,7 @@ void GSCpuBackend::DrawTriangle(const GSPrimitiveBatch &batch)
}

const uint8_t fog = clampU8(static_cast<int>(v0.fog * w0 + v1.fog * w1 + v2.fog * w2));
WritePixel(state, x, y, static_cast<u32>(z + 0.5), r, g, b, a, fog);
WritePixel(state, x, y, pixelZ, r, g, b, a, fog);
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions ps2xTest/cmake/CompareGsEarlyDepth.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Usage: cmake -DTEST_EXECUTABLE=/path/to/ps2x_tests -P CompareGsEarlyDepth.cmake
if(NOT DEFINED TEST_EXECUTABLE OR NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR "TEST_EXECUTABLE must name the built ps2x_tests executable")
endif()
get_filename_component(source_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)

foreach(mode IN ITEMS reference optimized)
if(mode STREQUAL "reference")
set(depth_setting "PS2X_GS_DISABLE_EARLY_DEPTH=1")
else()
set(depth_setting "--unset=PS2X_GS_DISABLE_EARLY_DEPTH")
endif()
execute_process(
COMMAND "${CMAKE_COMMAND}" -E env
--unset=PS2X_SKIP_CPU_RASTER
--unset=PS2X_SKIP_CPU_RASTER_BEFORE_PRESENT
--unset=PS2X_DEBUG_WHITE_WIREFRAME
--unset=PS2X_GS_VERIFY_EARLY_DEPTH_PRESENT
--unset=MINITEST_FILTER
"${depth_setting}" "${TEST_EXECUTABLE}"
WORKING_DIRECTORY "${source_root}"
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE errors
TIMEOUT 60)
if(NOT result STREQUAL "0")
message(FATAL_ERROR "${mode} depth tests failed (${result}):\n${output}\n${errors}")
endif()
if(NOT output MATCHES "\\[gs-depth-differential\\] cases=128 hash=([0-9a-f]+)")
message(FATAL_ERROR "Missing ${mode} differential digest:\n${output}")
endif()
set(${mode}_hash "${CMAKE_MATCH_1}")
message(STATUS "${mode}: ${${mode}_hash}")
endforeach()

if(NOT reference_hash STREQUAL optimized_hash)
message(FATAL_ERROR "Early depth rejection changed graphics memory")
endif()
message(STATUS "Early depth rejection preserves all 128 complete-VRAM results")
149 changes: 149 additions & 0 deletions ps2xTest/src/ps2_gs_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "ps2_stubs.h"
#include "ps2_syscalls.h"
#include "runtime/gs/gs_frontend.h"
#include "runtime/gs/gs_cpu_backend.h"
#include "runtime/ee_scheduler.h"
#include "runtime/gs/ps2_gs_memory.h"
#include "runtime/gs/ps2_gs_psmct32.h"
Expand Down Expand Up @@ -3499,6 +3500,154 @@ void register_ps2_gs_tests()
"REGION_REPEAT should calculate (U & UMSK) | UFIX");
});

tc.Run("GS early depth preserves GEQUAL and GREATER stores across Z formats", [](TestCase &t)
{
for (uint8_t format : {GS_PSM_Z32, GS_PSM_Z24, GS_PSM_Z16, GS_PSM_Z16S})
for (uint32_t method = 0u; method < 4u; ++method)
for (bool masked : {false, true})
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GSCpuBackend backend;
backend.Initialize(vram.data(), static_cast<uint32_t>(vram.size()));
GSPrimitiveBatch batch{};
batch.vertexCount = 3u;
batch.state.prim.type = GS_PRIM_TRIANGLE;
batch.state.context.frame = {0u, 1u, GS_PSM_CT32, 0u};
batch.state.context.zbuf = {1u, format, masked};
batch.state.context.scissor = {0u, 7u, 0u, 7u};
batch.state.context.test = (1ull << 16u) | (static_cast<uint64_t>(method) << 17u);
batch.vertices[0].x = 0.0f;
batch.vertices[0].y = 0.0f;
batch.vertices[1].x = 8.0f;
batch.vertices[1].y = 0.0f;
batch.vertices[2].x = 0.0f;
batch.vertices[2].y = 8.0f;
for (auto &vertex : batch.vertices)
{
vertex.z = 256.0;
vertex.r = 0x12u;
vertex.g = 0x34u;
vertex.b = 0x56u;
vertex.a = 0x80u;
}
for (uint32_t x = 1u; x <= 3u; ++x)
{
backend.WriteVram(GS_PSM_CT32, 0u, 1u, x, 1u, 0xAABBCCDDu);
backend.WriteVram(format, 32u, 1u, x, 1u, 254u + x);
}
backend.Submit(batch);
for (uint32_t x = 1u; x <= 3u; ++x)
{
const uint32_t stored = 254u + x;
const bool passes = method == 1u || (method == 2u && 256u >= stored) ||
(method == 3u && 256u > stored);
t.Equals(backend.ReadVram(GS_PSM_CT32, 0u, 1u, x, 1u),
passes ? 0x80563412u : 0xAABBCCDDu,
"depth comparison must preserve the expected color store");
t.Equals(backend.ReadVram(format, 32u, 1u, x, 1u),
passes && !masked ? 256u : stored,
"depth rejection or ZMSK must preserve stored depth");
}
}
});

tc.Run("GS early depth differential workload covers texture alpha fog and masks", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GSCpuBackend backend;
backend.Initialize(vram.data(), static_cast<uint32_t>(vram.size()));
constexpr std::array<uint8_t, 4> frameFormats{
GS_PSM_CT32, GS_PSM_CT24, GS_PSM_CT16, GS_PSM_CT16S};
constexpr std::array<uint8_t, 4> depthFormats{
GS_PSM_Z32, GS_PSM_Z24, GS_PSM_Z16, GS_PSM_Z16S};
uint32_t random = 0xB16821F3u;
auto next = [&]() { random = random * 1664525u + 1013904223u; return random; };
uint64_t hash = 14695981039346656037ull;
uint32_t changedCases = 0u;
for (uint32_t index = 0u; index < 128u; ++index)
{
std::fill(vram.begin(), vram.end(), 0x3Du);
GSPrimitiveBatch batch{};
batch.vertexCount = 3u;
auto &state = batch.state;
auto &ctx = state.context;
state.prim.type = GS_PRIM_TRIANGLE;
state.prim.tme = true;
state.prim.iip = (index & 1u) != 0u;
state.prim.fst = (index & 2u) != 0u;
state.prim.abe = (index & 4u) != 0u;
state.prim.fge = (index & 8u) != 0u;
state.pabe = (index & 16u) != 0u;
state.colclamp = 1u;
state.fogR = 0x34u;
state.fogG = 0x56u;
state.fogB = 0x78u;
state.textureWidth = state.textureHeight = 16u;
state.linearFilter = (index & 32u) != 0u;
ctx.frame = {0u, 1u, frameFormats[index & 3u], (index & 16u) ? 0x005A005Au : 0u};
ctx.zbuf = {32u, depthFormats[(index >> 2u) & 3u], (index & 8u) != 0u};
ctx.scissor = {2u, 29u, 1u, 30u};
ctx.tex0.tbp0 = 2048u;
ctx.tex0.tbw = 1u;
ctx.tex0.psm = GS_PSM_T8;
ctx.tex0.tw = ctx.tex0.th = 4u;
ctx.tex0.tcc = 1u;
ctx.tex0.tfx = static_cast<uint8_t>(index & 3u);
ctx.tex0.cbp = 4096u;
ctx.tex0.cpsm = GS_PSM_CT32;
ctx.tex0.cld = 1u;
ctx.clamp = static_cast<uint64_t>(index & 15u) | (15ull << 14u) | (15ull << 34u);
ctx.test = 1ull | ((index & 7ull) << 1u) | (0x70ull << 4u) |
(((index >> 3u) & 3ull) << 12u) | (1ull << 16u) |
(((index >> 5u) & 3ull) << 17u);
if ((index & 16u) != 0u)
ctx.test |= (1ull << 14u) | ((index & 1ull) << 15u);
ctx.alpha = 0x44ull | (0x60ull << 32u);
ctx.fba = index & 1u;
for (uint32_t y = 0u; y < 32u; ++y)
for (uint32_t x = 0u; x < 32u; ++x)
{
backend.WriteVram(ctx.frame.psm, 0u, 1u, x, y, next());
backend.WriteVram(ctx.zbuf.psm, 1024u, 1u, x, y, 32766u + (next() % 5u));
}
for (uint32_t y = 0u; y < 16u; ++y)
for (uint32_t x = 0u; x < 16u; ++x)
{
backend.WriteVram(GS_PSM_T8, 2048u, 1u, x, y, next() & 255u);
backend.WriteVram(GS_PSM_CT32, 4096u, 1u, x, y, next());
}
const float xs[] = {-2.0f, 34.0f, 5.0f};
const float ys[] = {2.0f, 7.0f, 35.0f};
for (uint32_t vertexIndex = 0u; vertexIndex < 3u; ++vertexIndex)
{
auto &vertex = batch.vertices[vertexIndex];
vertex.x = xs[vertexIndex];
vertex.y = ys[vertexIndex];
vertex.z = 32768.0 + (static_cast<int>(vertexIndex) - 1) * 2.0;
vertex.q = 0.5f + static_cast<float>(vertexIndex);
vertex.s = static_cast<float>(vertexIndex);
vertex.t = 2.0f - vertex.s;
vertex.u = static_cast<uint16_t>(vertexIndex * 8u * 16u);
vertex.v = static_cast<uint16_t>((2u - vertexIndex) * 8u * 16u);
vertex.r = static_cast<uint8_t>(next() >> 24u);
vertex.g = static_cast<uint8_t>(next() >> 24u);
vertex.b = static_cast<uint8_t>(next() >> 24u);
vertex.a = static_cast<uint8_t>(next() >> 24u);
vertex.fog = static_cast<uint8_t>(next() >> 24u);
}
const auto before = vram;
backend.Submit(batch);
if (before != vram)
++changedCases;
for (uint8_t byte : vram)
hash = (hash ^ byte) * 1099511628211ull;
}
std::printf("[gs-depth-differential] cases=128 hash=%016llx\n",
static_cast<unsigned long long>(hash));
t.IsTrue(changedCases > 0u && changedCases < 128u,
"the workload must exercise both stores and rejected draws");
});

tc.Run("GS STQ triangle interpolation divides homogeneous coordinates after DDA", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
Expand Down