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 <noreply@anthropic.com>
This commit is contained in:
Daan Dehing 2026-10-12 17:14:40 +02:00 committed by Adrià Franch
parent 759c66564f
commit a910dee09f
2 changed files with 67 additions and 1 deletions

View File

@ -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]{};

View File

@ -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<std::uint32_t> clearData(16, 0u);
const auto head = BufferDescriptor(keys, KeyBytes / 2);
std::copy(head.begin(), head.end(), clearData.begin());
std::vector<std::uint32_t> 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<std::uint32_t> clearData(16, 0u);
const auto head = BufferDescriptor(keys, KeyBytes / 2);
std::copy(head.begin(), head.end(), clearData.begin());
std::vector<std::uint32_t> 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<GuestBlock> sampledOutput;
sampledOutput.emplace(false);
RunSingleKeysSampled(*device, sampled->Data(), sampledOutput->Data(), "unwatched");
std::optional<GuestBlock> behind;
behind.emplace(false);
RunSampledKeysBehindKernel(*device, behind->Data());
std::optional<GuestBlock> uniform;
uniform.emplace(false);
RunUniformKeysBehindKernel(*device, uniform->Data());
#ifdef _WIN32
shared.emplace(true, true);
RunSharedImport(*device, shared->Data());