diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index e543119eb..9337c6ef1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -160,7 +160,8 @@ public: 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); if (ResolveTextureFormat(resource.format) != expected && !depthBits) return nullptr; - if (resource.dimension != TextureDimension::k2D) { + const bool oneLayer = resource.dimension == TextureDimension::k2DArray && resource.baseArray == 0 && resource.depthOrLastArray == 0; + if (resource.dimension != TextureDimension::k2D && !oneLayer) { char text[256]; std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented (storage=%ux%u guest format=%u mip=%u dimension=%d tile=%d)", static_cast(resource.baseAddress), resource.width, resource.height, resource.format, mip, static_cast(resource.dimension), static_cast(resource.tileMode)); throw std::runtime_error(text); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp index 85014deca..2c0031e06 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp @@ -227,7 +227,8 @@ void Run(Context context, std::uint8_t* block) { auto layeredDepth = PlaneResource(address, Format32Float); layeredDepth.dimension = TextureDimension::k2DArray; const auto arrayDepth = DepthSurfaceStorage(context, {}, layeredDepth, 0); - Require(arrayDepth != nullptr, "a one-layer 2D array view of the depth plane was not taken for the depth surface"); + Require(arrayDepth != nullptr && arrayDepth != depth, "a one-layer 2D array view of the depth plane was not taken for the depth surface as a storage image of its own"); + ExpectFailure([&] { static_cast(DepthSurfaceStoreBack(context, *depth)); }, "through a view that another view of the plane", "a store back through the 2D view that the one-layer 2D array view replaced was accepted"); submit(recorder); readBack(*arrayDepth); Require(CountFloats(block, 0.5f) == Side * Side, "the one-layer 2D array view did not receive the depth plane"); @@ -240,7 +241,12 @@ void Run(Context context, std::uint8_t* block) { readBack(*arrayStencil); Require(std::count(block + BlockBytes / 2, block + BlockBytes / 2 + stencilBytes, 7) == static_cast(Side * Side), "the one-layer 2D array view did not receive the stencil plane"); layeredStencil.depthOrLastArray = 1; - Require(DepthSurfaceStorage(context, {}, layeredStencil, 0) == nullptr, "a two-layer 2D array view of the stencil plane was taken for the depth surface"); + ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredStencil, 0)); }, "storage image access to depth/stencil surface", "a two-layer 2D array view of the stencil plane was accepted"); + layeredDepth.depthOrLastArray = 1; + ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredDepth, 0)); }, "storage image access to depth/stencil surface", "a two-layer 2D array view of the depth plane was accepted"); + layeredDepth.depthOrLastArray = 0; + layeredDepth.baseArray = 1; + ExpectFailure([&] { static_cast(DepthSurfaceStorage(context, {}, layeredDepth, 0)); }, "storage image access to depth/stencil surface", "a 2D array view of layer 1 of the depth plane was accepted"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageDispatch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageDispatch.cpp index 8888be01f..9b8dba980 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageDispatch.cpp @@ -46,6 +46,8 @@ constexpr std::size_t MultisampledOffset = 4u << 16u; constexpr std::size_t MultisampledColorOffset = 5u << 16u; constexpr std::size_t SampledOffset = 8u << 16u; constexpr std::size_t LaterDepthOffset = 10u << 16u; +constexpr std::size_t ArrayDepthOffset = 12u << 16u; +constexpr std::size_t ArrayStencilOffset = 13u << 16u; alignas(256) constexpr std::array ScaleDepthCode{ 0x7e3c0300u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u, @@ -57,6 +59,16 @@ alignas(256) constexpr std::array AddStencilCode{ 0xe0701000u, 0x80000a03u, 0x4a141484u, 0xf0201108u, 0x00010a1eu, 0xbf810000u, 0xbf810000u, }; +alignas(256) constexpr std::array ScaleDepthLayerCode{ + 0x7e3c0300u, 0x7e3e0280u, 0x7e400280u, 0x7e140280u, 0xf0001128u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u, + 0xe0701000u, 0x80000a03u, 0x1014140cu, 0xf0201128u, 0x00010a1eu, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; + +alignas(256) constexpr std::array AddStencilLayerCode{ + 0x7e3c0300u, 0x7e3e0280u, 0x7e400280u, 0x7e140280u, 0xf0001128u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u, + 0xe0701000u, 0x80000a03u, 0x4a141484u, 0xf0201128u, 0x00010a1eu, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; + alignas(256) constexpr std::array StoreCode{ 0x7e280d00u, 0x102828ffu, 0x3c800000u, 0x7e3c0300u, 0x7e3e0280u, 0xf0201108u, 0x0001141eu, 0xbf810000u, 0xbf810000u, }; @@ -215,13 +227,33 @@ void StoreBackCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, std: } } -void ArrayViewCase(AgcDriver::VulkanDevice& device, std::uint8_t* block) { +void ArrayViewCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, std::uint8_t* block) { + auto* depth = block + ArrayDepthOffset; + auto* stencil = block + ArrayStencilOffset; + const std::span output(reinterpret_cast(block + OutputOffset), Threads); + const Surface surface{reinterpret_cast(depth), reinterpret_cast(stencil), 0, false, VK_FORMAT_D32_SFLOAT_S8_UINT}; + const VkStencilOpState writeFive{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, 5u}; + Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = writeFive}) == Covered, "the depth and stencil producer of the array views did not cover the surface"); + const auto layer = [&](std::span code, const std::uint8_t* plane, std::uint32_t format, float factor) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(output.data(), static_cast(output.size_bytes())); + const auto texture = TextureDescriptor(plane, format, Type2DArray); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + userData[12] = std::bit_cast(factor); + return Compile(device, code, userData); + }; + RunPlane(device, layer(ScaleDepthLayerCode, depth, Format32Float, 1.5f), ScaleDepthLayerCode, output, std::bit_cast(0.5f), "depth dispatch through a one-layer 2D array view"); + RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test against the one-layer 2D array view's depth"); + RunPlane(device, layer(AddStencilLayerCode, stencil, Format8Uint, 0.0f), AddStencilLayerCode, output, 5u, "stencil dispatch through a one-layer 2D array view"); + RequireRow(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(9u)}), "stencil test against the one-layer 2D array view's stencil"); + std::vector userData(16, 0u); - const auto texture = TextureDescriptor(block, Format32Float, Type2DArray); + const auto texture = TextureDescriptor(depth, Format32Float, Type2DArray, TileLinear, 1u); std::copy(texture.begin(), texture.end(), userData.begin() + 4); userData[14] = std::bit_cast(1.0f / 64.0f); const auto program = Compile(device, StoreLayerCode, userData); - ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast(StoreLayerCode.data())); }, "storage image access to depth/stencil surface", "a one-layer 2D array storage view of the depth plane"); + ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast(StoreLayerCode.data())); }, "storage image access to depth/stencil surface", "a two-layer 2D array storage view of the depth plane"); } void MultisampledCase(AgcDriver::VulkanDevice& device, const Shaders& compiled, std::uint8_t* block) { @@ -301,7 +333,7 @@ int main() { if (!device) return VulkanTestSkipped; const auto shaders = DepthFastClearHarness::Compile(*device); StoreBackCase(*device, shaders, block.Data()); - ArrayViewCase(*device, block.Data()); + ArrayViewCase(*device, shaders, block.Data()); MultisampledCase(*device, shaders, block.Data()); TwoViewsCase(*device, block.Data()); SampledCopyCase(*device, shaders, block.Data()); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 02f95793f..f8ef84ea6 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -26,7 +26,7 @@ ### Functional -- [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 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 matching view still throws when it has another dimension (2D array, cube) or 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 or address) 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. +- [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 matching view still throws when it has another dimension (2D array of more than one layer or from another layer, cube) or 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. - [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. Planes with mips, a base slice other than 0, 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_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. - [Per-face depth bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order.