From 30c0cf1dfbb54fe291fedda55f65954b4ac8afff Mon Sep 17 00:00:00 2001 From: STL1te Date: Tue, 29 Sep 2026 11:19:03 +0300 Subject: [PATCH] feat(agc-driver): sample depth and stencil surfaces from their GPU images A depth target lives only in a GPU image (its guest surface is never written), so a texture over its depth or stencil plane now samples that image through a view of the plane's aspect: R32_SFLOAT for D32, R16_UNORM for D16, R8_UINT for stencil, one level and slice at the surface's extent. Other shapes, and storage access to a depth surface, throw. Depth images get the sampled usage, and the barriers after a render pass and before a dispatch include depth attachment writes. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/VulkanDevice.cpp | 2 +- .../Graphics/include/DepthSurface.hpp | 11 ++++- .../Graphics/include/Texture.hpp | 3 ++ .../Graphics/src/DepthSurface.cpp | 47 ++++++++++++++++++- .../Graphics/src/GuestTextureResource.cpp | 7 --- .../libSceAgcDriver/Graphics/src/Recorder.cpp | 2 +- .../Graphics/src/ShaderResources.cpp | 8 ++++ .../libSceAgcDriver/Graphics/src/Texture.cpp | 11 +++++ 8 files changed, 80 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 9aa13dfb5..2ead2f5ea 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2871,7 +2871,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { Graphics::Recorder::CountMerged(CommandClass::DispatchLeading); } else { const auto timing = recorder.BeginGpuTiming(CommandClass::DispatchLeading); - Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); Graphics::Recorder::CountBarriers(CommandClass::DispatchLeading); recorder.EndGpuTiming(timing); ++d.preBarriersRecorded; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 424a80d85..551ceb320 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -1,15 +1,20 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEPTHSURFACE_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEPTHSURFACE_HPP +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include +#include +#include namespace AgcDriver::Graphics { +class Texture; + // The attachment view of the device's image for a depth/stencil target, made on first use. The // image lives on the GPU only, always in the general layout: its guest surface (tiled, HTILE // compressed) is never read or written, so a new image starts at the target's clear values, and -// texture reads of the surface are refused (DepthSurfaceAt). Images stay until the device goes +// shaders sample the image itself (DepthSurfaceTexture). Images stay until the device goes // (ClearDepthSurfaces), so their views are stable for cached framebuffers. // ponytail: never evicted, one image per distinct surface; evict (and keep recorded ones alive) if // titles churn through many. @@ -17,6 +22,10 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target); void ClearDepthSurfaces(VkDevice device); // Whether the depth or stencil plane of a surface drawn with starts at `address`. bool DepthSurfaceAt(std::uint64_t address); +// The texture sampling the depth or stencil plane that starts at the descriptor's base address, +// or null when no surface drawn with on this device has a plane there. The plane is read in place, +// so the descriptor must describe it (its format, extent, one level and slice); anything else throws. +std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index ff073f026..640f3e9ab 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -57,6 +57,9 @@ public: // compute pass writing it and the next pass sampling it share one image and copy nothing. // CanCopyFrom says whether the two descriptors address the same surface compatibly. Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components); + // A view of one plane (aspect) of a depth surface's image, which stays in the general layout; + // the surface (DepthSurface.hpp) owns the image and its views. + Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components); static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor); ~Texture(); Texture(const Texture&) = delete; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index ddb073359..cf507d855 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -1,9 +1,15 @@ #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include +#include +#include +#include #include #include +#include #include namespace AgcDriver::Graphics { @@ -16,6 +22,7 @@ public: VkFormatProperties properties{}; context.formatProperties(context.physical, target.format, &properties); Require((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0, "depth/stencil format " + std::to_string(target.format) + " cannot be an attachment on this device"); + Require((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0, "depth/stencil format " + std::to_string(target.format) + " cannot be sampled on this device"); Require(target.extent.width <= context.limits.maxFramebufferWidth && target.extent.height <= context.limits.maxFramebufferHeight, "depth target exceeds framebuffer limits"); const VkImageAspectFlags aspects = VK_IMAGE_ASPECT_DEPTH_BIT | (target.stencilAddress != 0 ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); try { @@ -27,7 +34,7 @@ public: info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth"); @@ -74,6 +81,31 @@ public: DepthSurface(const DepthSurface&) = delete; DepthSurface& operator=(const DepthSurface&) = delete; + // The texture sampling the plane at the descriptor's base address, one per descriptor. + std::shared_ptr Sampled(std::span words, const GuestTextureResource& resource, VkComponentMapping components) { + std::array key{}; + std::copy_n(words.begin(), std::min(words.size(), 8), key.begin()); + key[8] = components.r; + key[9] = components.g; + key[10] = components.b; + key[11] = components.a; + if (const auto found = textures.find(key); found != textures.end()) return found->second; + 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 auto format = ResolveTextureFormat(resource.format); + if (format != expected || resource.dimension != TextureDimension::k2D || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0) { + 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(target.address), target.extent.width, target.extent.height, static_cast(target.format), resource.width, resource.height, resource.format, static_cast(format), + static_cast(resource.tileMode), static_cast(resource.dimension), resource.baseLevel, resource.lastLevel, resource.baseArray, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]); + throw std::runtime_error(text); + } + auto texture = std::make_shared(context, image, target.format, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components); + textures.emplace(key, texture); + return texture; + } + const Context context; const DepthTarget target; VkImage image = VK_NULL_HANDLE; @@ -81,7 +113,10 @@ public: VkDeviceMemory memory = VK_NULL_HANDLE; private: + std::map, std::shared_ptr> textures; + void release() noexcept { + textures.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); @@ -122,6 +157,16 @@ void ClearDepthSurfaces(VkDevice device) { std::erase_if(surfaces(), [&](const auto& surface) { return surface->context.device == device; }); } +std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components) { + std::lock_guard lock(surfacesMutex()); + const auto& list = surfaces(); + // The newest surface made at the address. + 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 found == list.rend() ? nullptr : (*found)->Sampled(words, resource, components); +} + 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; }); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index cf4db9175..3cd364eae 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -1,7 +1,6 @@ #include #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" -#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include #include #include @@ -47,12 +46,6 @@ GuestTextureResource DecodeTextureResource(std::span words) const auto base40 = (static_cast(words[0]) | (static_cast(words[1]) << 32u)) & 0xffffffffffull; const auto baseAddress = base40 << 8u; Require(baseAddress != 0, "guest texture descriptor has a null base address"); - // A depth surface's contents live on the GPU only; its guest memory is stale. - if (DepthSurfaceAt(baseAddress)) { - char text[96]; - std::snprintf(text, sizeof(text), "AGC graphics: sampling depth/stencil surface 0x%llx is not implemented", static_cast(baseAddress)); - throw std::runtime_error(text); - } const auto minLod = (words[1] >> 8u) & 0xfffu; const auto format = (words[1] >> 20u) & 0x1ffu; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index f796220fd..f682ab5a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1304,7 +1304,7 @@ void Recorder::endOpenRenderPass() { context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(open->commands); // The pass's attachment and shader writes are visible to everything recorded after it (the // host sees them at the batch's fence). - recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); CountBarriers(CommandClass::Draw); EndGpuTiming(pass.timing); open->coveredAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index d84bf94de..ec4d398ea 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include @@ -262,6 +263,8 @@ std::shared_ptr sampledStorageSource(const Context& context, con // `guestBytes` is the surface size when the caller described the surface already (0: described here). std::shared_ptr cachedTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components, std::uint64_t guestBytes = 0) { CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast(resource.baseAddress), resource.width, resource.height, static_cast(resource.dccAddress)); + // A depth surface's contents live in its GPU image; its guest memory is stale. + if (auto depth = DepthSurfaceTexture(context, words, resource, components)) return depth; static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; const bool profile = LookupOutcomes::Profiled(); const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; @@ -474,6 +477,11 @@ std::array SurfaceKey(const Context& context, const GuestTextu // `guestBytes` is the surface size when the caller described the surface already (0: described here). std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes) { + if (DepthSurfaceAt(resource.baseAddress)) { + char text[112]; + std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast(resource.baseAddress)); + throw std::runtime_error(text); + } static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; if (disabled) return std::make_shared(context, *context.detiler, resource, mip); static_cast(words); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 7f5008dd6..f5cd4eccb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -457,6 +457,17 @@ Texture::Texture(const Context& context, const std::shared_ptr& } } +Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components) : context(context) { + layout = VK_IMAGE_LAYOUT_GENERAL; + VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.image = depthImage; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = depthFormat; + viewInfo.components = components; + viewInfo.subresourceRange = {aspect, 0, 1, 0, 1}; + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane"); +} + Texture::~Texture() { release(); }