From c1287d73c5dba2b09ab1dc5b0dd9752a5b40dd7a Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 12 Oct 2026 02:49:47 +1300 Subject: [PATCH] fix(agc): leave the depth surface out when no enabled test has its plane DecodeState bound the depth surface whenever DB_DEPTH_CONTROL enabled a depth, stencil or depth bounds test and either plane was valid. A stencil test beside a depth-only surface (DB_STENCIL_INFO format invalid, depth test off) tests nothing, but the surface was still attached and the render extent was clamped to it. A draw into a 1920x1080 target with a 960x540 depth surface left bound then covered only the top-left quarter. A test whose plane is present but invalid no longer binds the surface. Such a draw renders without a depth attachment, as it already does when both planes are invalid. DrawRejection follows the same rule. --- .../libSceAgcDriver/Graphics/src/State.cpp | 18 +++++++++++---- .../prx/libSceAgcDriver/tests/Graphics.cpp | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index cf0d6b1ea..189a01c25 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -138,6 +138,14 @@ bool depthSurfaceBound(const Registers& cx) { return (z != cx.end() && (z->second & 3u) != 0) || (stencil != cx.end() && (stencil->second & 1u) != 0); } +std::uint32_t planeTests(const Registers& cx, std::uint32_t depthControl) { + const auto z = find(cx, 0x010); + const auto stencil = find(cx, 0x011); + if (z != cx.end() && (z->second & 3u) == 0) depthControl &= ~2u; + if (stencil != cx.end() && (stencil->second & 1u) == 0) depthControl &= ~1u; + return depthControl & 0xbu; +} + bool depthPlanesAbsent(const Registers& cx) { const auto z = find(cx, 0x010); const auto stencil = find(cx, 0x011); @@ -583,9 +591,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { const auto depthControl = read(cx, 0x200); const auto renderControl = find(cx, 0x000); const bool clear = renderControl != cx.end() && (renderControl->second & 3u) != 0; - if (clear || ((depthControl & 0xbu) != 0 && depthSurfaceBound(cx))) { + const auto tests = planeTests(cx, depthControl); + if (clear || (tests != 0 && depthSurfaceBound(cx))) { decodeDepth(cx, depthControl, result); - } else if (depthPassThrough(depthControl) || ((depthControl & 0xbu) != 0 && depthPlanesAbsent(cx))) { + } else if (depthPassThrough(depthControl) || ((depthControl & 0xbu) != 0 && (tests == 0 || depthPlanesAbsent(cx)))) { static bool reported = false; if (!reported) { reported = true; @@ -1008,8 +1017,9 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { std::uint32_t z = 0, view = 0; if ((value(cx, 0x010, z) && (z & 3u) == 0) || (value(cx, 0x002, view) && (view & 0x01000000u) != 0)) return require(false, "depth clear requires a writable depth plane"); } - const bool surface = ((word & 0xbu) != 0 || depthClear || stencilClear) && depthSurfaceBound(cx); - if (!surface && !((word & 0xbu) != 0 && depthPlanesAbsent(cx)) && !depthPassThrough(word) && !IgnoreDepthTest() && (effectiveDepthControl(word) & DepthControlMask) != 0) return zeroMessage(0x200, word, "depth, stencil or conditional color writes"); + const auto tests = planeTests(cx, word); + const bool surface = (tests != 0 || depthClear || stencilClear) && depthSurfaceBound(cx); + if (!surface && !((word & 0xbu) != 0 && (tests == 0 || depthPlanesAbsent(cx))) && !depthPassThrough(word) && !IgnoreDepthTest() && (effectiveDepthControl(word) & DepthControlMask) != 0) return zeroMessage(0x200, word, "depth, stencil or conditional color writes"); if (auto reason = require((word & 8u) == 0 || surface || depthPlanesAbsent(cx), "depth bounds without a depth surface"); !reason.empty()) return reason; if (auto reason = require((word & 0xc0000000u) == 0, "depth-conditional color writes are unsupported"); !reason.empty()) return reason; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 78a2e9a03..d9a9eb7fc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -275,6 +275,29 @@ void stateTests() { queue.context[0x200] = 0x007007bbu; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "depth bounds without a depth plane"); queue = makeState(); + queue.context[0x010] = 0x80000183; + queue.context[0x011] = 0x20000180; + queue.context[0x012] = 0x00001000; + queue.context[0x014] = 0x00001000; + queue.context[0x007] = 0x0001001f; + queue.context[0x000] = 0; + queue.context[0x002] = 0; + queue.context[0x00a] = 0; + queue.context[0x00b] = 0x3f800000; + queue.context[0x200] = 0x007007b1; + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.depth.has_value() && !state.stencilTest && state.renderExtent.width == state.color.extent.width && state.renderExtent.height == state.color.extent.height, "a stencil test without a stencil plane bound the depth plane it does not test"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "a stencil test without a stencil plane beside an untested depth plane was rejected"); + queue.context[0x200] = 0x00700231; + state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.depth.has_value() && !state.stencilTest && state.renderExtent.width == state.color.extent.width && state.renderExtent.height == state.color.extent.height, "a failing stencil test without a stencil plane bound the depth plane it does not test"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "a failing stencil test without a stencil plane beside an untested depth plane was rejected"); + queue.context[0x200] = 0x007007b3; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.depth.has_value() && state.depthTest && !state.stencilTest && state.renderExtent.width == 32 && state.renderExtent.height == 2, "a depth test on a bound depth plane lost its surface"); + queue = makeState(); queue.context[0x10f] = 0x7fc00000; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "non-finite"); // The register facade: every register the decoders read is in DrawKeyRegisters (a wrong hit of