From 8b550b81fcf01519fe5ced264679a35ad25ff50d Mon Sep 17 00:00:00 2001 From: gookie <36005381+gookie-dev@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:45:23 +0200 Subject: [PATCH] perf(agc): allocate a queue's constant RAM on its first write QueueState held the 48 KiB constant RAM inline, so every QueueState cleared 48 KiB when constructed and copied it when copied. ResolveShaderAbi and ResolveGraphicsStagesAbi construct one on each call (sceAgcLinkShaders, sceAgcCreatePrimState, interpolant mapping), resolveGroupAhead copies the live state, and a queue reset builds a new one, although few command buffers write the constant RAM. The constant RAM is now a vector allocated when WRITE_CONST_RAM first writes it. ConstantRam returns a shared all-zero view until then, so DUMP_CONST_RAM of unwritten words still stores zeros, and the bounds use QueueState::ConstantRamWords. The store view ResolveStore returns is copied by the recorder within the packet, as before. testMemory dumps unwritten constant RAM and checks for zeros, and checks that a copied queue state keeps the words written before the copy. A QueueState is 336 bytes instead of 49464; constructing one takes 0.3 to 0.8 us instead of 1.5 to 2.1 us, and copying one 0.4 to 0.6 us instead of 1.9 to 3.8 us (GCC 13 -O3). On the title screen of PPSA21402 (Balatro) the clears from the two resolvers took 30 to 129 of 3459 to 7838 CPU samples per 10 s. --- .../Execution/include/QueueState.hpp | 15 ++++++++++++++- .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 8 ++++---- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 5 +++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 3671c3010..f19678899 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -183,11 +184,12 @@ struct Predication { }; struct QueueState { + static constexpr std::size_t ConstantRamWords = 0x3000; Registers shader; Registers context = InitialContextRegisters(); Registers userConfig{{0x24a, 0}, {0x24b, 0}}; std::optional savedContext; - std::array constantRam{}; + std::vector constantRam; std::uint64_t indexBase = 0; std::uint64_t drawIndirectBase = 0; std::uint64_t dispatchIndirectBase = 0; @@ -200,6 +202,17 @@ struct QueueState { void ClearContext() { context = InitialContextRegisters(); } + + std::span ConstantRam() const { + static const std::array unwritten{}; + if (constantRam.empty()) return unwritten; + return constantRam; + } + + std::span WritableConstantRam() { + if (constantRam.empty()) constantRam.resize(ConstantRamWords); + return constantRam; + } }; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 2b8fbede8..f3b0dea8f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -610,8 +610,8 @@ std::optional ResolveStore(std::span packet, co return StoreWrite{destination, {}, dmaSourceBytes(packet)}; } case 0x83: { - if (packet.size() < 5 || packet[1] / 4 > queue.constantRam.size() || packet[2] > queue.constantRam.size() - packet[1] / 4) return std::nullopt; - const auto words = std::span(queue.constantRam).subspan(packet[1] / 4, packet[2]); + if (packet.size() < 5 || packet[1] / 4 > QueueState::ConstantRamWords || packet[2] > QueueState::ConstantRamWords - packet[1] / 4) return std::nullopt; + const auto words = queue.ConstantRam().subspan(packet[1] / 4, packet[2]); if (words.size_bytes() > limit) return std::nullopt; // A view of the queue's constant RAM: valid until the next CONST_RAM write on this queue, // which is a later packet of the same worker. @@ -913,10 +913,10 @@ void Execute(std::span packet, QueueState& queue) { return; } case 0x81: - std::copy(packet.begin() + 2, packet.end(), queue.constantRam.begin() + packet[1] / 4); + std::copy(packet.begin() + 2, packet.end(), queue.WritableConstantRam().begin() + packet[1] / 4); return; case 0x83: - GuestMemory::Write(address(packet[3], packet[4]), std::as_bytes(std::span(queue.constantRam).subspan(packet[1] / 4, packet[2])), 4); + GuestMemory::Write(address(packet[3], packet[4]), std::as_bytes(queue.ConstantRam().subspan(packet[1] / 4, packet[2])), 4); return; case 0x45: { std::uint32_t value = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index c2d85c866..2cfe08a47 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -358,9 +358,14 @@ void testMemory() { check(data[0] == 81 && data[1] == 82, "WRITE_DATA to memory from the graphics queue failed"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x08000100, low(data.data()), high(data.data()), 71}), 0); }, "reserved"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x80000100, low(data.data()), high(data.data()), 51}), 0); }, "engine"); + execute(state, makePacket(0x83, {0xbff8, 2, low(data.data()), high(data.data())})); + check(data[0] == 0 && data[1] == 0, "unwritten constant RAM is not zero"); execute(state, makePacket(0x81, {4, 31, 32})); execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); check(data[0] == 31 && data[1] == 32, "constant RAM round trip failed"); + auto copied = state; + execute(copied, makePacket(0x83, {0, 3, low(data.data()), high(data.data())})); + check(data[0] == 0 && data[1] == 31 && data[2] == 32, "a copied queue state lost its constant RAM"); expectFailure([&] { execute(state, makePacket(0x81, {0xbffc, 1, 2})); }, "overflow"); expectFailure([&] { execute(state, makePacket(0x40, {0x10105, 0, 0, low(data.data()), high(data.data())})); }, "64-bit immediate"); }