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 <noreply@anthropic.com>
This commit is contained in:
andrey 2026-10-08 18:34:04 +05:00 committed by Adrià Franch
parent 71ea4d5ee0
commit dfe147feaa
2 changed files with 23 additions and 2 deletions

View File

@ -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<bool>& 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<std::byte> original;
mutable std::array<std::uint64_t, 4> comparedGuestBytes{};
std::vector<std::byte> 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;

View File

@ -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<bool>* layers) {
CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(generation), uploadReason, layers != nullptr);
const bool profile = LookupOutcomes::Profiled();
@ -1563,8 +1565,18 @@ void StorageTexture::upload(const std::vector<bool>* 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<std::size_t>(guestBytes));
const auto stampLayers = [&](bool selectedOnly) {
const bool whole = !selectedOnly || layers == nullptr;
if (guestBytes > GenerationBaselineBytes) generationBaseline.clear();
else if (whole) {
generationBaseline.resize(static_cast<std::size_t>(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<std::size_t>(layerBegin(layer) - descriptor.baseAddress);
GuestMemory::ReadCommitted(layerBegin(layer), std::span<std::byte>(generationBaseline).subspan(offset, static_cast<std::size_t>(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<std::size_t>(from - descriptor.baseAddress);
return GuestMemory::EqualsCommittedUnsynced(from, std::span<const std::byte>(baseline).subspan(offset, static_cast<std::size_t>(to - from)));
}
void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
CaptureTrace::Log("writeback image=%llx generation=%llu reason=%s units=%zu", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(generation), flushReason, static_cast<std::size_t>(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<bool>& 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<std::size_t>((at - spanBegin) / block)] == GuestMemory::BlockWritten && (compared || !edge || GuestMemory::StoredOver(from, static_cast<std::size_t>(to - from), layerGeneration[layer]))) {
if (changedBlocks[static_cast<std::size_t>((at - spanBegin) / block)] == GuestMemory::BlockWritten && (compared || !edge || GuestMemory::StoredOver(from, static_cast<std::size_t>(to - from), layerGeneration[layer])) && !unchangedSinceBaseline(from, to)) {
skippedAny = true;
skippedLayer[layer] = true;
++skipped;