From 4f9ddffce0ebaa5f9b96afce7cfc970dfabd77b4 Mon Sep 17 00:00:00 2001 From: alfalfa-star Date: Sat, 10 Oct 2026 13:05:34 +0200 Subject: [PATCH] perf(agc): read a fast-cleared surface's guest bytes only when the import is compared #2096 keeps a copy of a storage image's guest bytes so that Refresh can compare memory the write watch does not cover. The host-import upload path takes that copy only when the range is unwatched, but the DCC fast-clear path took it on every upload. Reading the whole surface goes through the flush hook, so each 64 KiB block of a surface that a unit shadow covers recorded a publish and waited for the GPU. The clear path now resolves the surface's host import first (the import can unwatch the range, as a compared host-pointer import does) and takes the copy only when the range is then unwatched, like the import path. Write-watched memory, such as direct memory behind a dma-buf import, skips it; a range that is unwatched later re-uploads instead of comparing, as before #2096. agc_driver_dcc_keys_first_write checks that a fast-cleared upload keeps the copy exactly when its range is unwatched at that point. PPSA14632 (LEGO Horizon Adventures) on Linux, RX 9070 XT: the flush hook waited ~60,000 times per 10 s for these publishes (6.4 s), now ~3,500, and presents rise from ~24 to ~100 per 10 s. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp | 1 + core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp | 4 +++- .../prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 2fa7b67e5..a9c0e59bf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -288,6 +288,7 @@ public: // GuestMemory::GpuMutex only, as Refresh is; never from a build's stage A. DccKeys UploadedKeys() const { return uploadedKeys; } DccKeys FilledKeys() const { return filledKeys; } + bool GuestSnapshotValid() const { return originalValid; } DccKeyProof& KeyProof() const { return keyProof; } DccKeys ProvedKeys() const; bool ServesKeysAt(std::uint64_t dccAddress) const; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 71720b8b5..5a3f5eac4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1686,7 +1686,9 @@ void StorageTexture::upload(const std::vector* layers) { // The clear is recorded into the open batch like a direct upload (the image kept by it): a // batch of its own submitted the recorder's work first and waited for all of it, 25-40 ms // under the GPU mutex at the movie stage. APS5_NO_RECORDED_CLEAR=1 waits as before. - captureGuestBytes(nullptr); + static_cast(HostImportFor(context, descriptor.baseAddress, static_cast(guestBytes))); + if (GuestMemory::Watched(descriptor.baseAddress, static_cast(guestBytes))) originalValid = false; + else captureGuestBytes(nullptr); forgetBorrowed(0, trackedLayers); stampLayers(false); static const bool recordClear = std::getenv("APS5_NO_RECORDED_CLEAR") == nullptr; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp index 47547e67b..20e68d438 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp @@ -209,6 +209,8 @@ void Run(AgcDriver::VulkanDevice& device, std::uint8_t* block) { Require(image != nullptr && image->Descriptor().dccAddress == AddressOf(keys), "the written surface has no pending image under its keys"); Require(AgcDriver::Graphics::StorageImageServesKeys(*image, AddressOf(copied)), "the written image does not serve the copied keys"); Require(image->UploadedKeys() == DccKeys::Uncompressed && image->FilledKeys() == DccKeys::Uncompressed, "the written image still holds the clear it was uploaded under"); + const bool stillWatched = AgcDriver::GuestMemory::Watched(surface, SurfaceBytes); + Require(image->GuestSnapshotValid() != stillWatched, stillWatched ? "a fast-cleared upload of write-watched memory read its guest bytes" : "a fast-cleared upload of untracked memory kept no guest bytes to compare"); const auto pendingVersion = image->Version(); for (int repeat = 0; repeat < 3; ++repeat) { Require(image->Refresh(), "an unchanged pending image was uploaded again");