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 <noreply@anthropic.com>
This commit is contained in:
Cesar Rodriguez 2026-10-10 20:05:51 -07:00
parent 961b25fe24
commit 8d96b44917
2 changed files with 16 additions and 1 deletions

View File

@ -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<std::uintptr_t>(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);

View File

@ -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<std::uintptr_t>(arguments);
const auto* bound = sceAgcDcbSetBaseIndirectArgs(&storage.buffer, 0, arguments);
check(bound[1] == 1 && bound[2] == static_cast<std::uint32_t>(address) && bound[3] == static_cast<std::uint32_t>(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<const volatile void*>(address + 4)); });
check(storage.words == before, "misaligned indirect base modified packet memory");
}
void testContextState() {
const std::array<std::array<std::uint32_t, 6>, 4> sizes{{{5}, {5, 8, 9, 3, 2}, {3, 5, 8, 9, 2}, {5, 8, 9, 3, 2, 5}}};
const std::array<std::array<std::uint32_t, 4>, 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();