Merge pull request #454 from SP4C3B4R-8/fix/aliased-depth-surface-texture

fix(libSceAgcDriver): retire a depth surface once another writer covers its memory
This commit is contained in:
Adrià authored and GitHub committed 2026-10-15 11:25:04 +02:00
commit 042eb7c801
7 files changed
+331 -19

No files matched your search

@@ -22,6 +22,7 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes);
void RequireNoNativeDepthFeedback_nid_no_patch(const Context& context, const DepthTarget& target, std::span<const std::shared_ptr<Texture>> sampled);
void ClearDepthSurfaces(VkDevice device);
void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t bytes);
bool DepthSurfaceAt(std::uint64_t address);
bool DepthSurfaceAt(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource);
std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const std::array<std::uint32_t, 3>& numThreads);
@@ -235,6 +235,15 @@ public:
else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
}
bool SampledAccepts(std::span<const std::uint32_t> words, const GuestTextureResource& resource) const {
const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress;
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT;
const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u);
const bool oneSlice = target.samples != 1 ? resource.dimension == TextureDimension::k2DMsaa : resource.dimension == TextureDimension::k2D || (resource.dimension == TextureDimension::k2DArray && resource.depthOrLastArray == 0);
return (ResolveTextureFormat(resource.format) == expected || depthBits) && oneSlice && resource.width == target.extent.width && resource.height == target.extent.height && resource.baseLevel == 0 && resource.lastLevel == 0 && resource.baseArray == 0;
}
std::shared_ptr<Texture> Sampled(std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
CheckCurrent();
CheckResource(resource);
@@ -248,17 +257,16 @@ public:
const bool multisampled = target.samples != 1;
Require(!multisampled || sampleable, "the device cannot sample the native depth target's format and sample count");
const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress;
const auto format = ResolveTextureFormat(resource.format);
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT;
const auto format = ResolveTextureFormat(resource.format);
const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u);
const bool oneSlice = multisampled ? resource.dimension == TextureDimension::k2DMsaa : resource.dimension == TextureDimension::k2D || (resource.dimension == TextureDimension::k2DArray && resource.depthOrLastArray == 0);
if (multisampled) {
Require(!stencil && !depthBits && format == expected, "native multisampled depth sampling needs a numeric depth-plane format");
Require(resource.samples == target.samples, "the sampled depth descriptor's sample count differs from its native target");
Require(hostFormat == target.format, "sampling a native multisampled depth plane that the device stores in another depth format is not implemented");
}
if ((format != expected && !depthBits) || !oneSlice || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0) {
if (!SampledAccepts(words, resource)) {
char text[448];
std::snprintf(text, sizeof(text), "AGC graphics: sampling the %s plane of depth surface 0x%llx (%ux%u, vk format %d) as a %ux%u texture of guest format %u (vk %d), tile mode %u, dimension %d, levels %u-%u, slice %u is not implemented (T# %08x %08x %08x %08x %08x %08x %08x %08x)",
stencil ? "stencil" : "depth", static_cast<unsigned long long>(target.address), target.extent.width, target.extent.height, static_cast<int>(target.format), resource.width, resource.height, resource.format, static_cast<int>(format),
@@ -298,6 +306,26 @@ public:
}
std::uint64_t written = ++depthWrites;
bool retired = false;
bool Overlaps(std::uint64_t address, std::uint64_t bytes) const {
const auto end = address + bytes;
if (address < target.address + depthBytes() && target.address < end) return true;
return target.stencilAddress != 0 && address < target.stencilAddress + stencilBytes() && target.stencilAddress < end;
}
void NoteMemory() {
depthMemory = GuestMemory::CollectWrites(target.address, static_cast<std::size_t>(depthBytes()));
stencilMemory = target.stencilAddress != 0 ? GuestMemory::CollectWrites(target.stencilAddress, static_cast<std::size_t>(stencilBytes())) : 0;
}
bool OverwrittenInMemory() const {
GuestMemory::CollectWrites(target.address, static_cast<std::size_t>(depthBytes()));
if (GuestMemory::WrittenSince(target.address, static_cast<std::size_t>(depthBytes()), depthMemory)) return true;
if (target.stencilAddress == 0) return false;
GuestMemory::CollectWrites(target.stencilAddress, static_cast<std::size_t>(stencilBytes()));
return GuestMemory::WrittenSince(target.stencilAddress, static_cast<std::size_t>(stencilBytes()), stencilMemory);
}
VkImageAspectFlags pendingClear = 0;
float clearDepth = 0.0f;
@@ -422,6 +450,17 @@ private:
std::map<std::array<std::uint32_t, 12>, std::shared_ptr<Texture>> textures;
PlaneStorage depthPlane;
PlaneStorage stencilPlane;
std::uint64_t depthMemory = 0;
std::uint64_t stencilMemory = 0;
std::uint64_t depthBytes() const {
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
return DepthSliceBytes(target.extent, d16 ? 2u : 4u);
}
std::uint64_t stencilBytes() const {
return DepthSliceBytes(target.extent, 1u);
}
bool CoversPlane(const StorageTexture& storage, bool stencil) const {
const auto& resource = storage.Descriptor();
@@ -801,7 +840,7 @@ DepthSurface* depthSlice(VkDevice device, std::uint64_t address, const DepthTarg
if (target.extent.width == base.extent.width && target.extent.height == base.extent.height && target.format == base.format) return found->second.surface;
}
const auto& list = surfaces();
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == device && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format; });
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == device && !surface->retired && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format; });
return slice == list.rend() ? nullptr : slice->get();
}
@@ -814,11 +853,18 @@ DepthSurface* planeSurface(VkDevice device, std::uint64_t address) {
if (hasStencil) return stencil->second.surface;
const auto& list = surfaces();
const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) {
return surface->context.device == device && (surface->target.address == address || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == address));
return surface->context.device == device && !surface->retired && (surface->target.address == address || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == address));
});
return found == list.rend() ? nullptr : found->get();
}
void retire(DepthSurface& surface) {
surface.retired = true;
for (auto* owners : {&depthOwners(), &stencilOwners()}) {
std::erase_if(*owners, [&](const auto& entry) { return entry.second.surface == &surface; });
}
}
std::map<std::uint64_t, std::size_t>& clearedHtiles() {
static auto* cleared = new std::map<std::uint64_t, std::size_t>();
return *cleared;
@@ -948,6 +994,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
}
surface = surfaces().back().get();
}
surface->retired = false;
const bool inherits = depthOwners().contains({context.device, target.address});
surface->clearDepth = target.clearDepth;
surface->clearStencil = target.clearStencil;
@@ -959,6 +1006,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
}
surface->ApplyFastClear();
if (!created || inherits) surface->written = ++depthWrites;
surface->NoteMemory();
drawnDepth = surface;
return surface->view;
}
@@ -1025,7 +1073,7 @@ void ClearDepthSurfaces(VkDevice device) {
std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
const auto held = [&](const auto& surface) {
return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
return surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
};
{
std::lock_guard lock(surfacesMutex());
@@ -1055,14 +1103,20 @@ std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<c
const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u);
const bool integer = d16 && format == VK_FORMAT_R16_UINT && !depthBits;
if (!stencil && format != (d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT) && !depthBits && !integer) return nullptr;
found->ApplyFastClear();
const bool cube = resource.dimension == TextureDimension::kCube;
const bool layered = cube || (resource.dimension == TextureDimension::k2DArray && resource.depthOrLastArray != 0);
if (stencil || (!layered && !integer)) return found->Sampled(words, resource, components);
Require(found->hostFormat == base.format, "sampling several slices or an integer view of a depth plane that the device stores in another depth format is not implemented");
const bool sampled = stencil || (!layered && !integer);
const auto copyFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT;
const bool planes = (format == copyFormat || depthBits) && resource.width == base.extent.width && resource.height == base.extent.height && resource.baseLevel == 0 && resource.lastLevel == 0 && !(resource.baseArray != 0 && (cube || !layered || resource.baseArray > resource.depthOrLastArray)) && !(cube && resource.depthOrLastArray != 0 && resource.depthOrLastArray != 5) && (resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray || cube);
if (!(sampled ? found->SampledAccepts(words, resource) : planes) && found->OverwrittenInMemory()) {
retire(*found);
return nullptr;
}
found->ApplyFastClear();
if (sampled) return found->Sampled(words, resource, components);
Require(found->hostFormat == base.format, "sampling several slices or an integer view of a depth plane that the device stores in another depth format is not implemented");
const auto layers = cube ? 6u : layered ? resource.depthOrLastArray + 1u : 1u;
if ((format != copyFormat && !depthBits) || resource.width != base.extent.width || resource.height != base.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || (resource.baseArray != 0 && (cube || !layered || resource.baseArray > resource.depthOrLastArray)) || (cube && resource.depthOrLastArray != 0 && resource.depthOrLastArray != 5) || (resource.dimension != TextureDimension::k2D && resource.dimension != TextureDimension::k2DArray && !cube)) {
if (!planes) {
char text[320];
std::snprintf(text, sizeof(text), "AGC graphics: sampling the depth planes of depth surface 0x%llx (%ux%u, vk format %d) as a %ux%u texture of guest format %u (vk %d), dimension %d, %u slices, levels %u-%u, base slice %u is not implemented", static_cast<unsigned long long>(base.address), base.extent.width, base.extent.height, static_cast<int>(base.format), resource.width, resource.height, resource.format, static_cast<int>(format), static_cast<int>(resource.dimension), layers, resource.baseLevel, resource.lastLevel, resource.baseArray);
throw std::runtime_error(text);
@@ -1154,7 +1208,7 @@ std::vector<LayeredSlice> layeredSlices(const Context& context, const LayeredDep
const auto& list = surfaces();
for (std::uint32_t layer = storage.firstLayer; layer < storage.layers; ++layer) {
const auto address = base.address + static_cast<std::uint64_t>(layer) * DepthSliceBytes(base.extent, storage.bytesPerTexel);
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == context.device && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format && surface->target.samples == 1; });
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == context.device && !surface->retired && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format && surface->target.samples == 1; });
if (slice != list.rend()) slices.push_back({slice->get(), layer});
}
return slices;
@@ -1187,7 +1241,7 @@ std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std:
std::lock_guard lock(surfacesMutex());
const auto& list = surfaces();
const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) {
return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
return surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
});
if (found == list.rend()) return nullptr;
auto& surface = **found;
@@ -1217,7 +1271,14 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage
bool DepthSurfaceAt(std::uint64_t address) {
std::lock_guard lock(surfacesMutex());
return std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& surface) { return surface->target.address == address || surface->target.stencilAddress == address; });
return std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& surface) { return !surface->retired && (surface->target.address == address || surface->target.stencilAddress == address); });
}
void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t bytes) {
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (surface->context.device == device && !surface->retired && surface->Overlaps(address, bytes)) retire(*surface);
}
}
}
@@ -1983,6 +1983,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
}
APS5_LOG_OUT_DEBUG("Creating color target %zu address=0x%llx bytes=%llu extent=%ux%u", index, static_cast<unsigned long long>(color.address), static_cast<unsigned long long>(color.bytes), color.extent.width, color.extent.height);
const ColorTargetLayout colorLayout(color.extent.width, color.extent.height, color.tileMode, color.elementBytes);
RetireDepthSurfaces(context.device, color.address, colorLayout.Bytes());
constexpr VkBufferUsageFlags copies = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
timer.phase(PhaseSetup);
// Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again.
@@ -396,7 +396,7 @@ void logLookup(const LookupRecord& record) {
log.push_back(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);
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 written = false);
std::uint64_t heldBytes(const StorageTexture& image) {
return std::max<std::uint64_t>(image.AllocationBytes(), image.GuestBytes());
@@ -925,10 +925,11 @@ GuestTextureResource StorageSurface(const Context& context, const GuestTextureRe
}
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes) {
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes, bool written) {
Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented");
RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a storage image access");
if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth;
if (written) RetireDepthSurfaces(context.device, viewed.baseAddress, guestBytes != 0 ? guestBytes : DescribeSurface(viewed).guestBytes);
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
if (disabled) return StorageTexture::Create(context, *context.detiler, viewed, mip);
static_cast<void>(words);
@@ -2849,7 +2850,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
if (storageIndex >= storageTextures.size()) return false;
std::shared_ptr<StorageTexture> expected;
if (SameAsPreviousStorageElement(binding, element) && StorageDedupeEnabled()) expected = storageTextures[storageIndex - 1];
else expected = cachedStorageTexture(context, words, DecodeTextureResource(words), storageMips[storageIndex]);
else expected = cachedStorageTexture(context, words, DecodeTextureResource(words), storageMips[storageIndex], 0, storageWritten[storageIndex]);
if (expected != storageTextures[storageIndex]) return false;
++storageIndex;
}
@@ -4013,14 +4014,15 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
const auto guestBytes = record != nullptr && record->decoded ? record->guestBytes : DescribeSurface(resource).guestBytes;
CompletePendingCpuWrites(context, resource.baseAddress, static_cast<std::size_t>(guestBytes));
if (resource.dccAddress != 0) CompletePendingCpuWrites(context, resource.dccAddress, DccKeyBytes(guestBytes));
// Images the shader only reads have nothing to store back.
const bool written = element >= binding.imageWritten.size() || binding.imageWritten[element];
// The same surface as the previous element: its image was just looked up and refreshed.
if (sameAsPrevious && StorageDedupeEnabled()) storageTextures.push_back(storageTextures.back());
else storageTextures.push_back(cachedStorageTexture(context, words, resource, mip, guestBytes));
else storageTextures.push_back(cachedStorageTexture(context, words, resource, mip, guestBytes, written));
storageMips.push_back(mip);
storageKeys.push_back(resource.dccAddress);
storageFirstLayer.push_back(firstLayer);
// Images the shader only reads have nothing to store back.
storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]);
storageWritten.push_back(written);
storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]);
storageAtomic64.push_back(element < binding.imageAtomic64.size() && binding.imageAtomic64[element]);
describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
@@ -68,6 +68,7 @@ int main() {
Require(DepthFastClearHarness::Draw(*device, shaders, surface, {.depthWrite = true}) == DepthFastClearHarness::Covered, "depth producer did not cover the surface");
Read(*device, format == VK_FORMAT_D16_UNORM ? 7u : 22u, Width, true, 0.5f);
Read(*device, 22u, format == VK_FORMAT_D16_UNORM ? Width : Width / 2u, false, 0.25f);
if (format == VK_FORMAT_D32_SFLOAT) Read(*device, 22u, Width, true, 0.25f);
}
std::puts("depth storage reuse GPU tests passed");
return 0;
@@ -0,0 +1,245 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "VulkanTestDevice.hpp"
#include <SDL_loadso.h>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <span>
#include <string>
#include <vector>
namespace {
using namespace AgcDriver::Graphics;
constexpr std::uint64_t DepthAddress = 0x7f0000000000ull;
constexpr VkExtent2D Extent{64, 64};
constexpr std::uint32_t Format32Float = 22;
class Device {
public:
Device() {
#ifdef _WIN32
library = SDL_LoadObject("vulkan-1.dll");
#else
library = SDL_LoadObject("libvulkan.so.1");
#endif
Require(library != nullptr, "cannot load Vulkan");
try {
instanceProc = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_LoadFunction(library, "vkGetInstanceProcAddr"));
Require(instanceProc != nullptr, "missing Vulkan instance resolver");
VkApplicationInfo application{VK_STRUCTURE_TYPE_APPLICATION_INFO};
application.apiVersion = VK_API_VERSION_1_1;
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
info.pApplicationInfo = &application;
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
std::uint32_t count = 0;
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
Check(enumerate(instance, &count, nullptr), "vkEnumeratePhysicalDevices");
Require(count != 0, "no Vulkan device");
std::vector<VkPhysicalDevice> devices(count);
Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices");
context.physical = devices.front();
const auto queues = function<PFN_vkGetPhysicalDeviceQueueFamilyProperties>("vkGetPhysicalDeviceQueueFamilyProperties");
queues(context.physical, &count, nullptr);
std::vector<VkQueueFamilyProperties> families(count);
queues(context.physical, &count, families.data());
std::uint32_t family = 0;
while (family < count && (families[family].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) ++family;
Require(family < count, "no Vulkan graphics queue");
const float priority = 1;
VkDeviceQueueCreateInfo queue{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
queue.queueFamilyIndex = family;
queue.queueCount = 1;
queue.pQueuePriorities = &priority;
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
device.queueCreateInfoCount = 1;
device.pQueueCreateInfos = &queue;
Check(function<PFN_vkCreateDevice>("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice");
context.deviceProc = function<PFN_vkGetDeviceProcAddr>("vkGetDeviceProcAddr");
context.formatProperties = function<PFN_vkGetPhysicalDeviceFormatProperties>("vkGetPhysicalDeviceFormatProperties");
function<PFN_vkGetPhysicalDeviceMemoryProperties>("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory);
VkPhysicalDeviceProperties properties{};
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
context.limits = properties.limits;
context.Function<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(context.device, family, 0, &context.queue);
VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
pool.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
pool.queueFamilyIndex = family;
Check(context.Function<PFN_vkCreateCommandPool>("vkCreateCommandPool")(context.device, &pool, nullptr, &context.pool), "vkCreateCommandPool");
} catch (...) {
release();
throw;
}
}
~Device() { release(); }
const Context& GetContext() const { return context; }
private:
template<typename TFunction>
TFunction function(const char* name) const {
const auto result = reinterpret_cast<TFunction>(instanceProc(instance, name));
Require(result != nullptr, name);
return result;
}
void release() noexcept {
if (context.device != VK_NULL_HANDLE) ClearDepthSurfaces(context.device);
if (context.pool != VK_NULL_HANDLE) context.Function<PFN_vkDestroyCommandPool>("vkDestroyCommandPool")(context.device, context.pool, nullptr);
context.bufferPool.reset();
if (context.device != VK_NULL_HANDLE) function<PFN_vkDestroyDevice>("vkDestroyDevice")(context.device, nullptr);
if (instance != VK_NULL_HANDLE) function<PFN_vkDestroyInstance>("vkDestroyInstance")(instance, nullptr);
if (library != nullptr) SDL_UnloadObject(library);
}
void* library = nullptr;
PFN_vkGetInstanceProcAddr instanceProc = nullptr;
VkInstance instance = VK_NULL_HANDLE;
Context context{};
};
constexpr std::size_t Block = 65536;
void* AllocateWatched(std::size_t bytes) {
#ifdef _WIN32
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block);
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
#else
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(raw != MAP_FAILED, "cannot map the watched block");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
void* block = reinterpret_cast<void*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
#endif
Require(AgcDriver::GuestMemory::Watched(reinterpret_cast<std::uint64_t>(block), bytes), "the test block is not watched");
return block;
}
DepthTarget Depth(std::uint64_t address = DepthAddress, VkExtent2D extent = Extent) {
return {address, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
}
GuestTextureResource View(std::uint32_t width, std::uint32_t height, std::uint64_t address = DepthAddress, std::uint32_t levels = 1) {
GuestTextureResource resource{};
resource.baseAddress = address;
resource.width = width;
resource.height = height;
resource.mipCount = levels;
resource.lastLevel = levels - 1;
resource.dimension = TextureDimension::k2D;
resource.format = Format32Float;
return resource;
}
constexpr VkComponentMapping Identity{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
std::shared_ptr<Texture> Sample(const Context& context, const GuestTextureResource& resource) {
const std::array<std::uint32_t, 8> words{0, 0, 0, 0, resource.width, resource.height, resource.lastLevel, 0};
return DepthSurfaceTexture(context, words, resource, Identity);
}
bool Refused(const Context& context, const GuestTextureResource& resource) {
try {
Sample(context, resource);
} catch (const std::runtime_error& error) {
return std::string(error.what()).find("is not implemented") != std::string::npos;
}
return false;
}
void Run(const Context& context) {
const auto mips = View(Extent.width, Extent.height, DepthAddress, 2);
const auto exact = View(Extent.width, Extent.height);
const auto depthBytes = DepthSliceBytes(Extent, 4);
DepthSurfaceView(context, Depth());
Require(DepthSurfaceAt(DepthAddress), "a bound depth surface is not found at its address");
Require(Refused(context, mips), "depth, then a view with two levels: the live depth surface did not refuse it");
Require(Sample(context, exact) != nullptr, "depth, then its own view: the depth surface did not serve it");
RetireDepthSurfaces(context.device, DepthAddress + depthBytes, 0x10000);
Require(Refused(context, mips), "a write past the depth plane retired the depth surface");
RetireDepthSurfaces(context.device, DepthAddress + 0x100, 0x1000);
Require(!DepthSurfaceAt(DepthAddress), "a color target over the depth plane left the depth surface live");
Require(Sample(context, mips) == nullptr, "depth, then a color target, then a view with two levels: the view was not left to guest memory");
Require(Sample(context, exact) == nullptr, "depth, then a color target, then its own view: the retired depth surface served it");
DepthSurfaceView(context, Depth());
Require(DepthSurfaceAt(DepthAddress), "binding the depth target again did not bring the depth surface back");
Require(Refused(context, mips), "depth, color target, depth again, then a view with two levels: the live depth surface did not refuse it");
if (!AgcDriver::GuestMemory::WriteWatched()) {
Require(Refused(context, mips), "without write watching, a view with two levels was not refused");
std::puts("guest memory is not write-watched here: the CPU write order is not exercised");
return;
}
auto* memory = static_cast<volatile std::uint8_t*>(AllocateWatched(2 * Block));
const auto watched = reinterpret_cast<std::uint64_t>(memory);
Require(depthBytes <= 2 * Block, "the depth plane does not fit the watched block");
const auto watchedMips = View(Extent.width, Extent.height, watched, 2);
const auto watchedExact = View(Extent.width, Extent.height, watched);
AgcDriver::GuestMemory::CollectWrites(watched, 2 * Block);
DepthSurfaceView(context, Depth(watched));
AgcDriver::GuestMemory::BumpCollectEpoch();
Require(Refused(context, watchedMips), "depth, then a view with two levels: the live depth surface over watched memory did not refuse it");
memory[8] = 0x5a;
AgcDriver::GuestMemory::BumpCollectEpoch();
Require(Sample(context, watchedExact) != nullptr, "depth, then a CPU write, then its own view: the depth surface did not serve it");
Require(Sample(context, watchedMips) == nullptr, "depth, then a CPU write, then a view with two levels: the view was not left to guest memory");
Require(!DepthSurfaceAt(watched), "depth, then a CPU write, then a view with two levels: the depth surface stayed live");
memory[16] = 0xa5;
AgcDriver::GuestMemory::BumpCollectEpoch();
DepthSurfaceView(context, Depth(watched));
AgcDriver::GuestMemory::BumpCollectEpoch();
Require(Refused(context, watchedMips), "a CPU write, then depth, then a view with two levels: the live depth surface did not refuse it");
AgcDriver::GuestMemory::MarkWritten(watched + Block, 4);
Require(Refused(context, watchedMips), "a driver store past the depth plane retired the depth surface");
AgcDriver::GuestMemory::MarkWritten(watched + 0x100, 4);
Require(Sample(context, watchedMips) == nullptr, "depth, then a driver store, then a view with two levels: the view was not left to guest memory");
}
}
int main() {
try {
std::unique_ptr<Device> device;
try {
device = std::make_unique<Device>();
} catch (const std::exception& error) {
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw;
std::printf("skipped, no usable Vulkan device: %s\n", error.what());
return VulkanTestSkipped;
}
Run(device->GetContext());
std::puts("depth surface last writer tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
+1
View File
@@ -39,6 +39,7 @@
- [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. A 2D storage view, or a one-layer 2D array view of layer 0, of level 0 with the plane's extent and format (32-bit float or the 32-bit depth-bits layout for the depth plane, 16-bit unorm for D16, 8-bit uint for the stencil plane) is a storage image that is [filled](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) from the depth image before every use and, when the shader writes it, copied back after the work: a whole-plane copy through a staging buffer each way. A written view is also left pending like any other storage image, so the plane's content, drawn depth included, reaches guest memory on the next flush of that range. A 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (`LayeredDepthStorage`); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (`DepthSliceBytes`, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube, a 2D array of more than one layer of the stencil plane or from a later slice, or another level, when the surface is multisampled, when it names the depth plane of a 16-bit depth with stencil surface the device stores in a wider format, and when it was written after another view of the same plane (another format, tile mode, address or dimension) replaced it, as two such views bound in one dispatch are. Stored depth values outside 0..1 are not clamped, and Vulkan leaves that copy undefined without `VK_EXT_depth_range_unrestricted`. `agc_driver_depth_storage_dispatch` pins these paths; none is measured on the console.
- [Collapsed color array views](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) preserve the selected slice in the XOR address equation when the view is represented as a shifted 2D surface. Native Vulkan coverage checks R_X draws, array reads, publication and re-upload for slices 1-3; Lost Soul Aside PPSA26358 validates the RGBA16F cube mip chain on Windows/NVIDIA. Other devices and console comparisons remain unverified. Full-array views retain their layer addressing.
- A [depth surface](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) is retired when a color target or a written storage image it does not serve overlaps it, or when it is sampled through a view it cannot serve and guest memory over it was written since it was last bound; lookups then skip it, so sampling reads the memory like any texture. Bound again, it keeps its own depth instead of the bytes written over it. Without write watching, a view it cannot serve still throws. A retired surface also gives up its depth and stencil planes, so another surface sharing them no longer copies from it.
- [Depth plane copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as `R16_UINT`, is copied into an image of its own (`DepthPlaneCopy`), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target, when a pending fast clear is applied and when a written storage view of the plane is copied back), a guest-memory slice when its bytes were written since they were last read (`GuestMemory::UnchangedSince`), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (`DepthSliceBytes`), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. A 2D-array T# whose `BASE_ARRAY` is past slice 0 reads slices `BASE_ARRAY` to `LAST_ARRAY` of the copy, which holds slices 0 to `LAST_ARRAY`, and a 2D binding of it reads slice `BASE_ARRAY`; that follows the colour 2D-array semantics and the titles that hit it (PPSA01556, PPSA03912), and was not measured on hardware for depth surfaces. Planes with mips, a cube or a 2D T# with a base slice other than 0, a `BASE_ARRAY` past `LAST_ARRAY`, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below).
- [SW_64KB_Z_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the texture equations and 64 KiB block geometry of `SW_64KB_Z_X`, whose addresses are checked against [AMD addrlib GFX10](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) in the texture tiling tests. Any 256-byte pipe/bank XOR below the block size is accepted, as for `SW_64KB_R_X` targets and Z_X textures; which of those bits the console honours, and the render results, are not measured.
- [SW_64KB_D_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the existing texture block/mip geometry and equations from [AMD addrlib GFX10 non-RB+](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) for 16 pipes and 256-byte interleave. The address-reference tests use legal pipe/bank XOR bits for that configuration. Raw column bits outside the source configuration's pipe/bank mask 0xcf00 throw; their console meaning is unresolved. Console address equations and render results have not been measured.