From 540c3a10c670bdb9d06723edf6f0a7b8d938e77a Mon Sep 17 00:00:00 2001 From: Zenodeon Date: Thu, 15 Oct 2026 15:49:43 +0400 Subject: [PATCH] fix(agc): preserve registered array slices on CPU write-back --- .../libSceAgcDriver/Graphics/src/Texture.cpp | 11 +++- .../tests/execution/ArrayLayerTarget.cpp | 54 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index e71366c10b81..13b87fb8c026 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -4839,7 +4839,8 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { overhang = covered >= end; } } - if (unregisteredDrop && !overhang && !RegisteredReadableCovers(descriptor.baseAddress, static_cast(guestBytes))) { + const bool registered = std::all_of(keep.begin(), keep.end(), [](const auto& range) { return RegisteredReadableCovers(range.first, static_cast(range.second - range.first)); }); + if (unregisteredDrop && !overhang && !registered) { static std::atomic reports{0}; if (reports.fetch_add(1) < 4) std::fprintf(stderr, "[gpu] storage image 0x%llx+0x%llx: memory no longer registered; %zu pending ranges dropped\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), keep.size()); unregisteredDropped.fetch_add(1, std::memory_order_relaxed); @@ -4857,7 +4858,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { Require(!unsavedOutsideWatch, "storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented"); std::vector unsaved = layerOriginal; unsaved.flip(); - for (const auto& [begin, end] : unitRuns(unsaved)) GuestMemory::ReadCommitted(descriptor.baseAddress + begin, std::span(original).subspan(static_cast(begin), static_cast(end - begin))); + for (const auto& [begin, end] : unitRuns(unsaved)) { + for (const auto& [from, to] : keep) { + const auto first = std::max(descriptor.baseAddress + begin, from); + const auto last = std::min(descriptor.baseAddress + end, to); + if (first < last) GuestMemory::ReadCommitted(first, std::span(original).subspan(static_cast(first - descriptor.baseAddress), static_cast(last - first))); + } + } DeviceBuffer linear(context, static_cast(sliceLinearBytes * arrayLayers), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); DeviceBuffer tiled(context, original.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); Buffer host(context, original.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp index 18dcef360cc5..f9e388295ebc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp @@ -81,7 +81,7 @@ std::string Hex(std::uint32_t value) { class GuestBlock { public: - GuestBlock() { + explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0) : registeredOffset(registeredOffset) { #ifdef _WIN32 block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, LayerBytes)); if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); @@ -97,11 +97,11 @@ class GuestBlock { } #endif Require(block != nullptr, "array layer target: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, SurfaceBytes, true, true, true); + GuestAllocations::Mutation().Add(block + registeredOffset, registeredBytes, true, true, true); } ~GuestBlock() { - GuestAllocations::Mutation().Remove(block); + if (registered) GuestAllocations::Mutation().Remove(block + registeredOffset); #ifdef _WIN32 GuestArena::GuestArenaReset_nid_postfix(block, SurfaceBytes); GuestArena::GuestArenaRelease_nid_postfix(block, SurfaceBytes); @@ -115,9 +115,15 @@ class GuestBlock { GuestBlock& operator=(const GuestBlock&) = delete; std::uint8_t* Data() { return block; } + void Unregister() { + GuestAllocations::Mutation().Remove(block + registeredOffset); + registered = false; + } private: std::uint8_t* block = nullptr; + std::size_t registeredOffset = 0; + bool registered = true; }; std::array SurfaceDescriptor(std::uint64_t address, std::uint32_t swizzleMode, std::uint32_t type, std::uint32_t lastLayer) { @@ -309,6 +315,45 @@ void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std: AgcDriver::Graphics::ClearCachedTextures(device.Device()); } +void UnregisteredUnusedLayer(AgcDriver::VulkanDevice& device, std::uint32_t slice) { + GuestBlock block(LayerBytes, slice * LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + const auto active = surface + slice * LayerBytes; + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, slice, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the registered slice was not pending before its CPU write-back"); + StorageTexture::FlushPending(active, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const auto* first = reinterpret_cast(active); + const auto* last = reinterpret_cast(active + LayerBytes); + const auto unchanged = [](std::uint32_t value) { return value == Initial; }; + const auto drawn = std::count(first, last, std::bit_cast(Drawn)); + const bool untouched = std::all_of(reinterpret_cast(texels), first, unchanged) && std::all_of(last, reinterpret_cast(texels + SurfaceBytes), unchanged); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(drawn == ScissorWidth * Side, "CPU write-back discarded the registered slice because an unused array slice was unregistered"); + Require(untouched, "CPU write-back modified an unregistered unused slice"); +} + +void RetiredArrayLayer(AgcDriver::VulkanDevice& device) { + GuestBlock block(LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 0u, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the retired slice was not pending before it was unregistered"); + block.Unregister(); + StorageTexture::FlushPending(surface, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const bool untouched = std::all_of(reinterpret_cast(texels), reinterpret_cast(texels + SurfaceBytes), [](std::uint32_t value) { return value == Initial; }); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(untouched, "CPU write-back stored results over a retired allocation"); +} + } int main() { @@ -319,6 +364,9 @@ int main() { Run(*device, block.Data(), TileR64KBX); Run(*device, block.Data(), static_cast(AgcDriver::Graphics::ColorTileMode::D64KBX)); for (std::uint32_t slice = 1; slice < Layers; ++slice) Run(*device, block.Data(), TileR64KBX, true, slice); + UnregisteredUnusedLayer(*device, 0u); + UnregisteredUnusedLayer(*device, Layers - 1u); + RetiredArrayLayer(*device); std::puts("array layer target tests passed"); return 0; } catch (const std::exception& error) {