From 3221ae8acd75435954e31d9276df2a48ca616389 Mon Sep 17 00:00:00 2001 From: Sasha Papo Date: Fri, 9 Oct 2026 08:33:21 -0400 Subject: [PATCH] fix(agc): accept only 1 for APS5_STORE_AT_FLIP and test the switch instead of FlushAllPending Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp | 2 +- .../libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp | 1 + core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp | 6 ++++++ core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 8 ++++++++ core/libs/prx/libSceAgcDriver/tests/Recorder.cpp | 2 -- docs/user/USAGE.md | 2 +- 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index ae0a6c67f..b0c899b46 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2079,7 +2079,7 @@ FrameDumps& Dumps() { } bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { - static const bool storeAtFlip = std::getenv("APS5_STORE_AT_FLIP") != nullptr; + static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP")); if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip"); if (buffer.tilingMode == 1) { const auto pixels = ReadDisplayBuffer(buffer); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 80f87bd66..0f6efa20f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -152,6 +152,7 @@ public: // overlapping pending image, as before). static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr); static void FlushAllPending(const char* reason); + static bool StoreAtFlipRequested(const char* value); // See PendingSerial: a change of a surface's source outside the registry (a unit shadow // retile) moves it too. static void BumpPendingSerial(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 135c06137..cbf7cdfa1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -2520,6 +2520,12 @@ void StorageTexture::FlushAllPending(const char* reason) { static_cast(FlushPending(0, std::numeric_limits::max(), nullptr, reason)); } +bool StorageTexture::StoreAtFlipRequested(const char* value) { + if (value == nullptr) return false; + if (std::strcmp(value, "1") == 0) return true; + throw std::runtime_error(std::string("APS5_STORE_AT_FLIP=") + value + ": expected 1"); +} + std::shared_ptr StorageTexture::FindPending(std::uint64_t address, std::uint64_t bytes) { auto& pending = Pending(); std::lock_guard lock(pending.mutex); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index b3ad04b7c..f1fe40818 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3461,6 +3461,13 @@ void highestDrawIndexTests() { expectFailure([&] { HighestDrawIndex(bytesOf(narrow), 1, false); }, "unsupported index size"); } +void storeAtFlipTests() { + using AgcDriver::Graphics::StorageTexture; + Require(!StorageTexture::StoreAtFlipRequested(nullptr), "an unset APS5_STORE_AT_FLIP stored at each flip"); + Require(StorageTexture::StoreAtFlipRequested("1"), "APS5_STORE_AT_FLIP=1 did not store at each flip"); + for (const char* value : {"0", "", "true", "11"}) expectFailure([&] { StorageTexture::StoreAtFlipRequested(value); }, "expected 1"); +} + void vertexCopyTests() { using AgcDriver::Graphics::PlanVertexCopies; using AgcDriver::Graphics::VertexFetch; @@ -3586,6 +3593,7 @@ int main() { validationTests(); vertexCopyTests(); highestDrawIndexTests(); + storeAtFlipTests(); pixelParameterSlotTests(); rectListTests(); floatControlsModeTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index ae603a368..47871020c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -3002,8 +3002,6 @@ void metadataPassTests(const Device& device, Recorder& recorder) { std::memset(keys, 0x20, extent); RunColorMetadataPass(context, pass); Require(StorageTexture::FindPending(address, surfaceBytes) != nullptr && texels[0] == 0x55, "the register fast clear eliminate did not stay in the resident image"); - StorageTexture::FlushAllPending("test"); - Require(StorageTexture::FindPending(address, surfaceBytes) == nullptr, "storing every pending image left the eliminated target pending"); Require(keysUncompressed(), "the register fast clear eliminate left the keys compressed"); Require(CurrentDccKeys(color.dccAddress, surfaceBytes, extent) == DccKeys::Uncompressed, "the register fast clear eliminate did not store uncompressed keys over the target's console DCC extent"); Require(memoryHolds({0x10, 0x20, 0x40, 0x80}), "the register fast clear eliminate did not store CB_COLOR_CLEAR_WORD"); diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index d22dcc07d..f640c074f 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -110,7 +110,7 @@ The game runs on the first Vulkan 1.1 device with graphics and compute queues an ### Storing GPU results at each flip -GPU results in storage images and render targets stay on the GPU until something reads their memory. A title that frees such an image after waiting for its GPU work and reuses the memory can have its new data overwritten when the results are stored later ([TechnicalDebt](../dev/TechnicalDebt.md)). `APS5_STORE_AT_FLIP=1` stores every pending result when a display buffer is flipped, at the cost of one write-back per pending image and frame. +GPU results in storage images and render targets stay on the GPU until something reads their memory. A title that frees such an image after waiting for its GPU work and reuses the memory can have its new data overwritten when the results are stored later ([TechnicalDebt](../dev/TechnicalDebt.md)). `APS5_STORE_AT_FLIP=1` stores every pending result when a display buffer is flipped, at the cost of one write-back per pending image and frame. Any other value stops the title at its first flip. ## Exit codes