From 8d96b44917ef0593eec4ab459e87ce675a09c3a9 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez <11452991+Tamalero@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:05:51 -0700 Subject: [PATCH] fix(libSceAgc): accept a null base in sceAgcDcbSetBaseIndirectArgs The builder threw on a null indirect base, while the PM4 executor accepts SET_BASE with address 0 (it only requires the alignment and address bits) and an indirect draw that later reads the base is checked there. Titles clear the base with 0, as with sceAgcDcbSetIndexBuffer, which already accepts 0 to unbind. A non-null base is still checked for 8-byte alignment. Co-Authored-By: Claude Opus 5.5 --- .../libs/prx/libSceAgc/DcbDraw/src/Instancing.cpp | 2 +- core/libs/prx/libSceAgc/tests/Commands.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgc/DcbDraw/src/Instancing.cpp b/core/libs/prx/libSceAgc/DcbDraw/src/Instancing.cpp index 35a5fcba8..21ec62d11 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/src/Instancing.cpp +++ b/core/libs/prx/libSceAgc/DcbDraw/src/Instancing.cpp @@ -20,7 +20,7 @@ uint32_t* APS5_VABI sceAgcDcbSetBaseIndirectArgs(CommandBuffer* buf, uint32_t sh // SET_BASE with base index 1; header bit 1 selects the dispatch (compute) base over the draw base. Agc::Command::CheckBits(shader_type, 1, __func__); const auto address = reinterpret_cast(indirect_base_addr); - Agc::Command::CheckAddress(address, 8, __func__); + if (address != 0) Agc::Command::CheckAddress(address, 8, __func__); Agc::Command::CheckBits(address, 0xffffffffffffull, __func__); auto* packet = Agc::Command::Allocate(buf, 4, __func__); packet[0] = Agc::Command::Header(0x11u, 4, shader_type != 0 ? 2u : 0u); diff --git a/core/libs/prx/libSceAgc/tests/Commands.cpp b/core/libs/prx/libSceAgc/tests/Commands.cpp index 1f02cc134..c2ea940d6 100644 --- a/core/libs/prx/libSceAgc/tests/Commands.cpp +++ b/core/libs/prx/libSceAgc/tests/Commands.cpp @@ -34,6 +34,7 @@ extern "C" std::uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, cons extern "C" std::uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf); extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color); extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64_t indexAddress); +extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetBaseIndirectArgs(CommandBuffer* buf, std::uint32_t shaderType, const volatile void* indirectBaseAddr); extern "C" std::uint32_t* APS5_VABI sceAgcSetNop(CommandBuffer* buf, std::uint32_t sizeDw); namespace { @@ -221,6 +222,19 @@ void testIndexBuffer() { throw std::runtime_error("misaligned index buffer was accepted"); } +void testIndirectBase() { + Storage storage; + alignas(8) std::uint32_t arguments[4]{}; + const auto address = reinterpret_cast(arguments); + const auto* bound = sceAgcDcbSetBaseIndirectArgs(&storage.buffer, 0, arguments); + check(bound[1] == 1 && bound[2] == static_cast(address) && bound[3] == static_cast(address >> 32u), "indirect base address mismatch"); + const auto* cleared = sceAgcDcbSetBaseIndirectArgs(&storage.buffer, 1, nullptr); + check(cleared == bound + 4 && (cleared[0] & 2u) != 0 && cleared[1] == 1 && cleared[2] == 0 && cleared[3] == 0, "indirect base was not cleared"); + const auto before = storage.words; + expectFailure([&] { sceAgcDcbSetBaseIndirectArgs(&storage.buffer, 0, reinterpret_cast(address + 4)); }); + check(storage.words == before, "misaligned indirect base modified packet memory"); +} + void testContextState() { const std::array, 4> sizes{{{5}, {5, 8, 9, 3, 2}, {3, 5, 8, 9, 2}, {5, 8, 9, 3, 2, 5}}}; const std::array, 4> reservations{{{5}, {22, 3, 2}, {3, 22, 2}, {22, 3, 2, 5}}}; @@ -425,6 +439,7 @@ int main(int argc, char** argv) { testIndexedIndirectDraws(); testMarkers(); testIndexBuffer(); + testIndirectBase(); testContextState(); testFlip(); testRegisters();