From dfe147feaa7b1b13d6c551da5bfc63c193df3870 Mon Sep 17 00:00:00 2001 From: andrey Date: Thu, 8 Oct 2026 18:34:04 +0500 Subject: [PATCH] fix(agc): store GPU results over CPU-stamped bytes the CPU left unchanged A write-back keeps the CPU's bytes for a block stamped since the layer's generation. Stamps can fall on bytes the CPU did not change (PPSA12544's font atlas: the stamped pages still hold the allocator fill the image was uploaded from), and the GPU results there were lost. Images up to 1 MiB keep the guest bytes of their generation point; a stamped range that still equals them stores the GPU results. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/include/Texture.hpp | 2 ++ .../libSceAgcDriver/Graphics/src/Texture.cpp | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index a14c015ef..5799dd101 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -310,6 +310,7 @@ private: // APS5_BLOCK_WRITEBACK_EACH=1 stores the touched units only). void writeBack(std::uint64_t address, std::size_t bytes); void writeBackLayers(const std::vector& layers); + bool unchangedSinceBaseline(std::uint64_t from, std::uint64_t to) const; // Tracked units as 64 KiB write-stamp blocks (`blockUnits`: a thin tiled surface at a 64 KiB // aligned base; APS5_NO_BLOCK_TRACKING=1 tracks array layers as above instead): a fill of one // layer, a CPU write or another image's store then costs the blocks it touched, moved through @@ -438,6 +439,7 @@ private: SurfaceGeometry geometry; std::vector original; mutable std::array comparedGuestBytes{}; + std::vector generationBaseline; // DCC keys the image content was uploaded under: a fast-cleared surface starts as its clear value. DccKeys uploadedKeys = DccKeys::Uncompressed; mutable DccKeys filledKeys = DccKeys::Uncompressed; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 6055e283c..fc5e17bb9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1551,6 +1551,8 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, return true; } +constexpr std::uint64_t GenerationBaselineBytes = 1ull << 20; + void StorageTexture::upload(const std::vector* layers) { CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast(descriptor.baseAddress), static_cast(guestBytes), static_cast(generation), uploadReason, layers != nullptr); const bool profile = LookupOutcomes::Profiled(); @@ -1563,8 +1565,18 @@ void StorageTexture::upload(const std::vector* layers) { if (layers != nullptr && ((!blockUnits && trackedLayers != arrayLayers) || std::all_of(layers->begin(), layers->end(), [](bool selected) { return selected; }))) layers = nullptr; const auto now = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast(guestBytes)); const auto stampLayers = [&](bool selectedOnly) { + const bool whole = !selectedOnly || layers == nullptr; + if (guestBytes > GenerationBaselineBytes) generationBaseline.clear(); + else if (whole) { + generationBaseline.resize(static_cast(guestBytes)); + GuestMemory::ReadCommitted(descriptor.baseAddress, generationBaseline); + } for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { - if (!selectedOnly || layers == nullptr || (*layers)[layer]) layerGeneration[layer] = now; + if (!whole && !(*layers)[layer]) continue; + layerGeneration[layer] = now; + if (whole || generationBaseline.size() != guestBytes) continue; + const auto offset = static_cast(layerBegin(layer) - descriptor.baseAddress); + GuestMemory::ReadCommitted(layerBegin(layer), std::span(generationBaseline).subspan(offset, static_cast(layerBytes(layer)))); } refreshGeneration(); }; @@ -3449,6 +3461,13 @@ void StorageTexture::writeBack(std::uint64_t address, std::size_t bytes) { writeBackLayers(layers); } +bool StorageTexture::unchangedSinceBaseline(std::uint64_t from, std::uint64_t to) const { + const auto& baseline = originalValid ? original : generationBaseline; + if (baseline.size() != guestBytes) return false; + const auto offset = static_cast(from - descriptor.baseAddress); + return GuestMemory::EqualsCommittedUnsynced(from, std::span(baseline).subspan(offset, static_cast(to - from))); +} + void StorageTexture::writeBackLayers(const std::vector& layers) { CaptureTrace::Log("writeback image=%llx generation=%llu reason=%s units=%zu", static_cast(descriptor.baseAddress), static_cast(generation), flushReason, static_cast(std::count(layers.begin(), layers.end(), true))); static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; @@ -3510,7 +3529,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { const auto to = std::min(at + block, end); if (from >= to) continue; const bool edge = from != at || to != at + block; - if (changedBlocks[static_cast((at - spanBegin) / block)] == GuestMemory::BlockWritten && (compared || !edge || GuestMemory::StoredOver(from, static_cast(to - from), layerGeneration[layer]))) { + if (changedBlocks[static_cast((at - spanBegin) / block)] == GuestMemory::BlockWritten && (compared || !edge || GuestMemory::StoredOver(from, static_cast(to - from), layerGeneration[layer])) && !unchangedSinceBaseline(from, to)) { skippedAny = true; skippedLayer[layer] = true; ++skipped;