From 9d65003ced1e74491060eac82a032c0ba31345f4 Mon Sep 17 00:00:00 2001 From: TideOPL Date: Thu, 8 Oct 2026 10:06:11 +0100 Subject: [PATCH 1/2] fix(agc): treat other-shaped views of a depth surface's memory as reused memory Engines alias transient render targets, so a texture can sit at the address of a depth surface after the memory has been handed to a color image: a 384x384 R11G11B10 texture over a 384x384 D32S8 surface, say. Sampling it threw "sampling the depth plane ... is not implemented" and the draw was skipped. A view with another extent, or a depth-plane view in a format that cannot hold the plane (neither the plane's own R32F / R16 unorm nor a T# that reads the raw depth bits), is now treated as memory reuse: the lookup falls through to the ordinary texture path instead of the depth surface. Stencil-plane views keep their current handling. --- .../prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 33194fdb2fb8..d1cc7c90abdc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -169,7 +169,16 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::spancontext.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); + if (found == list.rend()) return nullptr; + const auto& target = (*found)->target; + if (resource.width != target.extent.width || resource.height != target.extent.height) return nullptr; + const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress; + if (!stencil) { + const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; + const bool depthBits = words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u); + if (ResolveTextureFormat(resource.format) != (d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT) && !depthBits) return nullptr; + } + return (*found)->Sampled(words, resource, components); } bool DepthSurfaceAt(std::uint64_t address) { From 46606e9c73887521728062f3e9b834a5030087a1 Mon Sep 17 00:00:00 2001 From: TideOPL Date: Sat, 10 Oct 2026 00:44:40 +0100 Subject: [PATCH 2/2] test(agc): cover depth surface lookups of reused memory agc_driver_graphics registers a D32 and a D16 surface on a mock device and looks up views at their addresses. Views of the surface's own extent in R32F or R16, and T#s that read the raw depth bits, sample the depth plane; a view of another extent, a 2D array view of another extent, an R11G11B10 view, an R32 uint view without a depth layout and an R32F view of a D16 surface return null and take the ordinary texture path. On main the first of these throws in DepthSurface::Sampled. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 1 + .../tests/DepthSurfaceReuse.cpp | 181 ++++++++++++++++++ .../prx/libSceAgcDriver/tests/Graphics.cpp | 1 + .../libSceAgcDriver/tests/GraphicsTests.hpp | 1 + 4 files changed, 184 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 44f27630d227..5a41f5a6f65e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -557,6 +557,7 @@ add_test_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL tests/GuestTextureResource.cpp tests/GuestSamplerResource.cpp tests/TextureDetiler.cpp + tests/DepthSurfaceReuse.cpp Graphics/src/Draw.cpp Graphics/src/DepthSurface.cpp Graphics/src/Pipeline.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp new file mode 100644 index 000000000000..b91361ceae98 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp @@ -0,0 +1,181 @@ +#include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include +#include +#include +#include +#include + +namespace { + +using namespace AgcDriver::Graphics; + +constexpr std::uint32_t FormatR16Unorm = 7; +constexpr std::uint32_t FormatR16Uint = 11; +constexpr std::uint32_t FormatR32Uint = 20; +constexpr std::uint32_t FormatR32Float = 22; +constexpr std::uint32_t FormatR11G11B10Float = 36; +constexpr std::uint32_t TileDepth64KB = 0x18; +constexpr std::uint32_t TileRenderTarget64KB = 0x1b; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Type2DArray = 13; + +std::uintptr_t nextHandle = 0x1000; + +template +THandle newHandle() { + return reinterpret_cast(nextHandle += 0x10); +} + +VKAPI_ATTR VkResult VKAPI_CALL createImage(VkDevice, const VkImageCreateInfo*, const VkAllocationCallbacks*, VkImage* image) { + *image = newHandle(); + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL imageMemoryRequirements(VkDevice, VkImage, VkMemoryRequirements* requirements) { + *requirements = {65536, 65536, 1}; +} + +VKAPI_ATTR VkResult VKAPI_CALL allocateMemory(VkDevice, const VkMemoryAllocateInfo*, const VkAllocationCallbacks*, VkDeviceMemory* memory) { + *memory = newHandle(); + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL bindImageMemory(VkDevice, VkImage, VkDeviceMemory, VkDeviceSize) { + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL createImageView(VkDevice, const VkImageViewCreateInfo*, const VkAllocationCallbacks*, VkImageView* view) { + *view = newHandle(); + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL allocateCommandBuffers(VkDevice, const VkCommandBufferAllocateInfo*, VkCommandBuffer* commands) { + *commands = newHandle(); + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL createFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, VkFence* fence) { + *fence = newHandle(); + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL beginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL endCommandBuffer(VkCommandBuffer) { + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL queueSubmit(VkQueue, std::uint32_t, const VkSubmitInfo*, VkFence) { + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL waitForFences(VkDevice, std::uint32_t, const VkFence*, VkBool32, std::uint64_t) { + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL getFenceStatus(VkDevice, VkFence) { + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL freeCommandBuffers(VkDevice, VkCommandPool, std::uint32_t, const VkCommandBuffer*) {} +VKAPI_ATTR void VKAPI_CALL destroyFence(VkDevice, VkFence, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL destroyImageView(VkDevice, VkImageView, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL destroyImage(VkDevice, VkImage, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL freeMemory(VkDevice, VkDeviceMemory, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL cmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags, VkPipelineStageFlags, VkDependencyFlags, std::uint32_t, const VkMemoryBarrier*, std::uint32_t, const VkBufferMemoryBarrier*, std::uint32_t, const VkImageMemoryBarrier*) {} +VKAPI_ATTR void VKAPI_CALL cmdClearDepthStencilImage(VkCommandBuffer, VkImage, VkImageLayout, const VkClearDepthStencilValue*, std::uint32_t, const VkImageSubresourceRange*) {} + +VKAPI_ATTR void VKAPI_CALL formatProperties(VkPhysicalDevice, VkFormat, VkFormatProperties* properties) { + *properties = {}; + properties->optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; +} + +PFN_vkVoidFunction VKAPI_CALL deviceProc(VkDevice, const char* name) { + static const std::map table{ + {"vkCreateImage", reinterpret_cast(createImage)}, + {"vkGetImageMemoryRequirements", reinterpret_cast(imageMemoryRequirements)}, + {"vkAllocateMemory", reinterpret_cast(allocateMemory)}, + {"vkBindImageMemory", reinterpret_cast(bindImageMemory)}, + {"vkCreateImageView", reinterpret_cast(createImageView)}, + {"vkAllocateCommandBuffers", reinterpret_cast(allocateCommandBuffers)}, + {"vkCreateFence", reinterpret_cast(createFence)}, + {"vkBeginCommandBuffer", reinterpret_cast(beginCommandBuffer)}, + {"vkEndCommandBuffer", reinterpret_cast(endCommandBuffer)}, + {"vkQueueSubmit", reinterpret_cast(queueSubmit)}, + {"vkWaitForFences", reinterpret_cast(waitForFences)}, + {"vkGetFenceStatus", reinterpret_cast(getFenceStatus)}, + {"vkFreeCommandBuffers", reinterpret_cast(freeCommandBuffers)}, + {"vkDestroyFence", reinterpret_cast(destroyFence)}, + {"vkDestroyImageView", reinterpret_cast(destroyImageView)}, + {"vkDestroyImage", reinterpret_cast(destroyImage)}, + {"vkFreeMemory", reinterpret_cast(freeMemory)}, + {"vkCmdPipelineBarrier", reinterpret_cast(cmdPipelineBarrier)}, + {"vkCmdClearDepthStencilImage", reinterpret_cast(cmdClearDepthStencilImage)} + }; + const auto it = table.find(name); + return it == table.end() ? nullptr : it->second; +} + +struct View { + std::uint64_t address; + std::uint32_t format; + std::uint32_t width; + std::uint32_t height; + std::uint32_t tileMode = TileDepth64KB; + std::uint32_t type = Type2D; + std::uint32_t lastArray = 0; +}; + +std::array tsharp(const View& view) { + std::array words{}; + const auto base = view.address >> 8u; + const auto widthMinus1 = view.width - 1u; + const auto heightMinus1 = view.height - 1u; + words[0] = static_cast(base); + words[1] = static_cast((base >> 32u) & 0xffu) | ((view.format & 0x1ffu) << 20u) | ((widthMinus1 & 0x3u) << 30u); + words[2] = ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u); + words[3] = 4u | (5u << 3u) | (6u << 6u) | (7u << 9u) | ((view.tileMode & 0x1fu) << 20u) | ((view.type & 0xfu) << 28u); + words[4] = view.lastArray & 0x1fffu; + return words; +} + +std::shared_ptr lookup(const Context& context, const View& view) { + const auto words = tsharp(view); + return DepthSurfaceTexture(context, words, DecodeTextureResource(words), VkComponentMapping{}); +} + +} + +void RunDepthSurfaceReuseTests() { + static int deviceTag = 0; + Context context{}; + context.device = reinterpret_cast(&deviceTag); + context.deviceProc = deviceProc; + context.formatProperties = formatProperties; + context.memory.memoryTypeCount = 1; + context.memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + context.limits.maxFramebufferWidth = 16384; + context.limits.maxFramebufferHeight = 16384; + constexpr std::uint64_t depth32 = 0x40000000; + constexpr std::uint64_t depth16 = 0x50000000; + DepthSurfaceView(context, {depth32, 0, {384, 384}, VK_FORMAT_D32_SFLOAT, 1.0f, 0}); + DepthSurfaceView(context, {depth16, 0, {256, 256}, VK_FORMAT_D16_UNORM, 1.0f, 0}); + + Require(lookup(context, {depth32, FormatR32Float, 384, 384}) != nullptr, "an R32F view of a D32 surface's own extent must sample its depth plane"); + Require(lookup(context, {depth16, FormatR16Unorm, 256, 256}) != nullptr, "an R16 view of a D16 surface's own extent must sample its depth plane"); + Require(lookup(context, {depth32, FormatR32Uint, 384, 384}) != nullptr, "a 32-bit raw depth bits view of a D32 surface must sample its depth plane"); + Require(lookup(context, {depth16, FormatR16Uint, 256, 256}) != nullptr, "a 16-bit raw depth bits view of a D16 surface must sample its depth plane"); + + Require(lookup(context, {depth32, FormatR32Float, 192, 192}) == nullptr, "a view of another extent over a depth surface must be read as reused memory"); + Require(lookup(context, {depth32, FormatR11G11B10Float, 384, 384}) == nullptr, "an R11G11B10 view of a D32 surface's own extent must be read as reused memory"); + Require(lookup(context, {depth32, FormatR32Float, 512, 512, TileDepth64KB, Type2DArray, 5}) == nullptr, "a 2D array view of another extent over a depth surface must be read as reused memory"); + Require(lookup(context, {depth32, FormatR32Uint, 384, 384, TileRenderTarget64KB}) == nullptr, "an R32 uint view without a depth layout must be read as reused memory"); + Require(lookup(context, {depth16, FormatR32Float, 256, 256}) == nullptr, "an R32F view of a D16 surface's own extent must be read as reused memory"); + + ClearDepthSurfaces(context.device); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 487e18f4d579..41e86c700dd0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2940,6 +2940,7 @@ int main() { RunTextureFormatTests(); RunTextureTilingTests(); RunGuestTextureResourceTests(); + RunDepthSurfaceReuseTests(); RunGuestSamplerResourceTests(); mock = MockVulkan{}; auto textureDetilerContext = mockContext(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp b/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp index f3a1e91df4dd..3a48ac3e94b3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/GraphicsTests.hpp @@ -32,6 +32,7 @@ struct TextureDetilerTestAccess { void RunTextureFormatTests(); void RunTextureTilingTests(); void RunGuestTextureResourceTests(); +void RunDepthSurfaceReuseTests(); void RunGuestSamplerResourceTests(); void RunTextureDetilerTests(const AgcDriver::Graphics::Context& context, const TextureDetilerTestAccess& access);