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 <noreply@anthropic.com>
This commit is contained in:
alfalfa-star 2026-10-10 13:05:34 +02:00
parent b5f5362d3d
commit 4f9ddffce0
3 changed files with 6 additions and 1 deletions

View File

@ -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;

View File

@ -1686,7 +1686,9 @@ void StorageTexture::upload(const std::vector<bool>* 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<void>(HostImportFor(context, descriptor.baseAddress, static_cast<std::size_t>(guestBytes)));
if (GuestMemory::Watched(descriptor.baseAddress, static_cast<std::size_t>(guestBytes))) originalValid = false;
else captureGuestBytes(nullptr);
forgetBorrowed(0, trackedLayers);
stampLayers(false);
static const bool recordClear = std::getenv("APS5_NO_RECORDED_CLEAR") == nullptr;

View File

@ -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");