fix(agc): remake a surface's storage image when its DCC keys move
The storage image cache keys a surface by memory, extent, layout and storage format, and the image keeps the DCC key range of the first descriptor that made it. When a later descriptor names other metadata (the title reallocated the keys, or the memory held another surface before), the image kept the old keys: fast clears of the new keys never reached it, and its write-backs marked keys the surface no longer owns. A descriptor whose DCC address differs from the cached image's now evicts that image (its pending results are flushed first) and a new one is made from memory under the named keys. A sampled view of another image's pending results is taken only under the same keys. A descriptor without metadata keeps whatever image the surface has.
This commit is contained in:
parent
fc0596240d
commit
f25695fac7
|
|
@ -196,6 +196,10 @@ void logLookup(const LookupRecord& record) {
|
|||
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0);
|
||||
|
||||
bool MetadataMoved(const StorageTexture& image, const GuestTextureResource& resource) {
|
||||
return resource.dccAddress != 0 && image.Descriptor().dccAddress != resource.dccAddress;
|
||||
}
|
||||
|
||||
// Whether a sampled texture over `resource` can be a view of the surface's cached storage image
|
||||
// instead of a CPU snapshot (see cachedTexture): the format has a storage form and is not block
|
||||
// compressed, and the surface lives in host-imported memory, where the image uploads and refreshes
|
||||
|
|
@ -287,7 +291,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
|||
// the GPU supplies the texture by a view of it; anything else needs those results in guest
|
||||
// memory first.
|
||||
auto source = StorageTexture::FindPending(address, guestBytes);
|
||||
if (source != nullptr && !Texture::CanCopyFrom(*source, resource)) source.reset();
|
||||
if (source != nullptr && (!Texture::CanCopyFrom(*source, resource) || MetadataMoved(*source, resource))) source.reset();
|
||||
// Otherwise a surface in host-imported memory is viewed through its cached storage image (made
|
||||
// here when there is none): its refresh after a CPU or GPU write is a GPU-direct detile from the
|
||||
// import, recorded behind the producer, so no bytes are read or compared on the CPU and nothing
|
||||
|
|
@ -496,7 +500,12 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
|
|||
const StorageKey key{context.device, SurfaceKey(context, resource)};
|
||||
auto& cache = StorageTextures();
|
||||
std::lock_guard lock(cache.mutex);
|
||||
if (auto it = findStorage(cache, key); it != cache.entries.end()) {
|
||||
auto it = findStorage(cache, key);
|
||||
if (it != cache.entries.end() && MetadataMoved(*it->texture, resource)) {
|
||||
evictStorage(cache, it);
|
||||
it = cache.entries.end();
|
||||
}
|
||||
if (it != cache.entries.end()) {
|
||||
it->texture->Refresh();
|
||||
cache.entries.splice(cache.entries.begin(), cache.entries, it);
|
||||
counters.storageHits.fetch_add(1, std::memory_order_relaxed);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
|
|
@ -106,6 +108,7 @@ public:
|
|||
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
|
||||
context.limits = properties.limits;
|
||||
context.bufferDeviceAddress = true;
|
||||
context.formatProperties = function<PFN_vkGetPhysicalDeviceFormatProperties>("vkGetPhysicalDeviceFormatProperties");
|
||||
context.Function<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(context.device, family, 0, &context.queue);
|
||||
VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
pool.queueFamilyIndex = family;
|
||||
|
|
@ -596,6 +599,103 @@ void storeRunTests(const Device& device, Recorder& recorder) {
|
|||
HostImportFor(context, address, bytes);
|
||||
}
|
||||
|
||||
void movedMetadataTests(const Device& device, Recorder& recorder) {
|
||||
const auto& base = device.GetContext();
|
||||
if (base.hostImportAlignment == 0) {
|
||||
std::cout << "host imports unavailable: moved DCC metadata not tested\n";
|
||||
return;
|
||||
}
|
||||
constexpr std::uint32_t side = 256;
|
||||
constexpr std::size_t surfaceBytes = side * side * 4;
|
||||
constexpr std::size_t keyCount = surfaceBytes / 256;
|
||||
constexpr std::size_t bytes = surfaceBytes + 65536;
|
||||
#ifdef _WIN32
|
||||
void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
#else
|
||||
void* block = std::aligned_alloc(65536, bytes);
|
||||
#endif
|
||||
Require(block != nullptr, "cannot allocate the moved metadata block");
|
||||
auto* texels = static_cast<std::uint8_t*>(block);
|
||||
auto* firstKeys = texels + surfaceBytes;
|
||||
auto* secondKeys = firstKeys + 4096;
|
||||
const auto address = reinterpret_cast<std::uint64_t>(block);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(block, bytes, true, true);
|
||||
}
|
||||
struct Unregister {
|
||||
const Context& context;
|
||||
void* block;
|
||||
std::uint64_t address;
|
||||
~Unregister() {
|
||||
ClearCachedTextures(context.device);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(block);
|
||||
}
|
||||
HostImportFor(context, address, bytes);
|
||||
}
|
||||
} unregister{base, block, address};
|
||||
if (HostImportFor(base, address, bytes) == nullptr) {
|
||||
std::cout << "host import of the moved metadata block refused: moved DCC metadata not tested\n";
|
||||
return;
|
||||
}
|
||||
TextureDetiler detiler(base);
|
||||
auto context = base;
|
||||
context.detiler = &detiler;
|
||||
GuestTextureResource resource{};
|
||||
resource.baseAddress = address;
|
||||
resource.width = side;
|
||||
resource.height = side;
|
||||
resource.mipCount = 1;
|
||||
resource.tileMode = TextureTileMode::kR64KBX;
|
||||
resource.dimension = TextureDimension::k2D;
|
||||
resource.format = 56;
|
||||
resource.dstSelX = 4;
|
||||
resource.dstSelY = 5;
|
||||
resource.dstSelZ = 6;
|
||||
resource.dstSelW = 7;
|
||||
Require(DescribeSurface(resource).guestBytes == surfaceBytes, "the moved metadata surface has an unexpected size");
|
||||
const auto first = address + surfaceBytes;
|
||||
const auto second = first + 4096;
|
||||
const auto withKeys = [&](std::uint64_t keys) {
|
||||
auto described = resource;
|
||||
described.dccAddress = keys;
|
||||
return described;
|
||||
};
|
||||
const auto holds = [&](const StorageTexture& image, std::array<std::uint8_t, 4> texel) {
|
||||
Buffer readback(context, surfaceBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
const auto commands = recorder.Commands();
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
copy.imageExtent = {side, side, 1};
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, image.Image(), VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, ©);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
recorder.Submit();
|
||||
device.WaitQueue();
|
||||
recorder.Sync();
|
||||
const auto pixels = readback.Bytes();
|
||||
for (std::size_t i = 0; i < pixels.size(); ++i) {
|
||||
if (std::to_integer<std::uint8_t>(pixels[i]) != texel[i % 4]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
std::memset(texels, 0x55, surfaceBytes);
|
||||
std::memset(firstKeys, 0xff, keyCount);
|
||||
std::memset(secondKeys, 0x00, keyCount);
|
||||
const auto original = CachedStorageSurface(context, withKeys(first));
|
||||
Require(original->Descriptor().dccAddress == first && holds(*original, {0x55, 0x55, 0x55, 0x55}), "the first image does not hold the stored texels");
|
||||
const auto moved = CachedStorageSurface(context, withKeys(second));
|
||||
Require(moved->Descriptor().dccAddress == second, "the storage image kept the keys the surface no longer names");
|
||||
Require(!StorageImageCached(context, original.get()), "the image of the old keys is still the surface's");
|
||||
Require(holds(*moved, {0, 0, 0, 0}), "a fast clear of the moved keys did not reach the image");
|
||||
Require(CachedStorageSurface(context, resource) == moved && moved->Descriptor().dccAddress == second, "a descriptor without metadata replaced the image");
|
||||
const auto back = CachedStorageSurface(context, withKeys(first));
|
||||
Require(back != moved && back->Descriptor().dccAddress == first, "the keys moving back did not remake the image");
|
||||
Require(holds(*back, {0x55, 0x55, 0x55, 0x55}), "the image remade under the first keys does not hold the stored texels");
|
||||
}
|
||||
|
||||
void resourceReadTests(const Device& device, Recorder& recorder) {
|
||||
const auto& context = device.GetContext();
|
||||
if (context.hostImportAlignment == 0) {
|
||||
|
|
@ -971,6 +1071,7 @@ int main() {
|
|||
keyProofTests(device, recorder);
|
||||
resourceReadTests(device, recorder);
|
||||
storeRunTests(device, recorder);
|
||||
movedMetadataTests(device, recorder);
|
||||
unitShadowTests(device, recorder);
|
||||
dataWordPositionsTests();
|
||||
dataRefreshTests(device, recorder);
|
||||
|
|
|
|||
Loading…
Reference in New Issue