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:
parent
759c66564f
commit
a910dee09f
|
|
@ -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]{};
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Reference in New Issue