From a910dee09f1e7d17897ad499b50e0c7c581a2f0f Mon Sep 17 00:00:00 2001 From: Daan Dehing Date: Mon, 12 Oct 2026 17:14:40 +0200 Subject: [PATCH] fix(agc): wait for recorded writes over a sampled texture's DCC keys before reading them textureClearKeys reads the keys through a raw pointer, past the flush hook, so writes the recorder holds for the key range are not in the bytes yet. A title that clears the keys of a target with a kernel and samples it in the same batch had its keys read from the memory's previous contents: in Palworld (PPSA23126) these were the float data of a reused heap, so the keys read as mixed and the texels were read as stored. When the keys read do not decode (not uncompressed, not a clear the format can fill), WaitForKeyWriters answers from the pending fills and stores it knows, or waits for the batch that writes the keys, and the keys are read again. Keys that decode are used as before. RunSampledKeysBehindKernel (agc_driver_dcc_keys_first_write): key memory holding float bytes, then the 0000 clear kernel recorded without a wait; the sampled texture's keys read Clear0000. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/DccMetadata.cpp | 4 +- .../tests/execution/DccKeysFirstWrite.cpp | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index 03e76d5c0..e1b0ba009 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -468,7 +468,9 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t guestBytes, bool& memoized) { memoized = false; if (resource.dccAddress == 0) return DccKeys::Uncompressed; - const auto keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized); + DccKeys keys; + if (const auto known = WaitForKeyWriters(resource, guestBytes)) keys = *known; + else keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized); if (keys == DccKeys::Uncompressed || (keys == DccKeys::ClearSingle && SingleKeysReadable(resource))) return keys; if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented"); std::byte probe[16]{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp index 96e56da31..eae20eb5a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeysFirstWrite.cpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" @@ -500,6 +501,63 @@ void RunSingleKeysOverPendingImage(AgcDriver::VulkanDevice& device, std::uint8_t } } +void RunSampledKeysBehindKernel(AgcDriver::VulkanDevice& device, std::uint8_t* block) { + auto* texels = block; + auto* keys = block + KeysOffset; + std::memset(texels, 0x55, SurfaceBytes); + for (std::size_t i = 0; i < KeyBytes; i += 4) { + keys[i] = 0x00; + keys[i + 1] = 0x00; + keys[i + 2] = 0x80; + keys[i + 3] = 0x3f; + } + AgcDriver::GuestMemory::BumpCollectEpoch(); + const auto texture = TextureDescriptor(texels, keys); + const auto resource = AgcDriver::Graphics::DecodeTextureResource(texture); + std::vector clearData(16, 0u); + const auto head = BufferDescriptor(keys, KeyBytes / 2); + std::copy(head.begin(), head.end(), clearData.begin()); + std::vector clearTailData(16, 0u); + const auto tail = BufferDescriptor(keys + KeyBytes / 2, KeyBytes / 2); + std::copy(tail.begin(), tail.end(), clearTailData.begin()); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(AgcDriver::Graphics::TextureClearKeys(resource, SurfaceBytes) == DccKeys::Uncompressed, "sampled keys behind a kernel: key bytes that are no keys were not read as stored"); + Dispatch(device, ClearKeysCode, clearData, false); + Dispatch(device, ClearKeysCode, clearTailData, false); + const auto read = AgcDriver::Graphics::TextureClearKeys(resource, SurfaceBytes); + char message[256]; + std::snprintf(message, sizeof(message), "a sampled texture whose keys a recorded kernel stores 0000 over read %s before the kernel ran", AgcDriver::Graphics::DccKeysName(read)); + Require(read == DccKeys::Clear0000, message); + device.WaitIdle(); + RequireKeys(keys, 0x00, "sampled keys behind a kernel: the keys after the kernel"); +} + +void RunUniformKeysBehindKernel(AgcDriver::VulkanDevice& device, std::uint8_t* block) { + auto* texels = block; + auto* keys = block + KeysOffset; + std::memset(texels, 0x55, SurfaceBytes); + std::memset(keys, 0xff, KeyBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + const auto texture = TextureDescriptor(texels, keys); + const auto resource = AgcDriver::Graphics::DecodeTextureResource(texture); + std::vector clearData(16, 0u); + const auto head = BufferDescriptor(keys, KeyBytes / 2); + std::copy(head.begin(), head.end(), clearData.begin()); + std::vector clearTailData(16, 0u); + const auto tail = BufferDescriptor(keys + KeyBytes / 2, KeyBytes / 2); + std::copy(tail.begin(), tail.end(), clearTailData.begin()); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(AgcDriver::Graphics::TextureClearKeys(resource, SurfaceBytes) == DccKeys::Uncompressed, "uniform sampled keys behind a kernel: the keys before the kernel were not read uncompressed"); + Dispatch(device, ClearKeysCode, clearData, false); + Dispatch(device, ClearKeysCode, clearTailData, false); + const auto read = AgcDriver::Graphics::TextureClearKeys(resource, SurfaceBytes); + char message[256]; + std::snprintf(message, sizeof(message), "a sampled texture whose uniform keys a recorded kernel stores 0000 over read %s before the kernel ran", AgcDriver::Graphics::DccKeysName(read)); + Require(read == DccKeys::Clear0000, message); + device.WaitIdle(); + RequireKeys(keys, 0x00, "uniform sampled keys behind a kernel: the keys after the kernel"); +} + void RunUntrackedReuse(AgcDriver::VulkanDevice& device, std::uint8_t* block) { const auto address = AddressOf(block); std::memset(block, 0x55, BlockBytes); @@ -650,6 +708,12 @@ int main() { std::optional sampledOutput; sampledOutput.emplace(false); RunSingleKeysSampled(*device, sampled->Data(), sampledOutput->Data(), "unwatched"); + std::optional behind; + behind.emplace(false); + RunSampledKeysBehindKernel(*device, behind->Data()); + std::optional uniform; + uniform.emplace(false); + RunUniformKeysBehindKernel(*device, uniform->Data()); #ifdef _WIN32 shared.emplace(true, true); RunSharedImport(*device, shared->Data());