diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 24a468603..ddabb2b69 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1168,6 +1168,12 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() set_tests_properties(agc_driver_provoking_vertex PROPERTIES TIMEOUT 30) + add_test(NAME agc_driver_uint8x4_color_target_no_resident COMMAND agc_driver_uint8x4_color_target_tests) + set_tests_properties(agc_driver_uint8x4_color_target_no_resident PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_RESIDENT_TARGETS=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_uint8x4_color_target_no_resident PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() set_tests_properties(agc_driver_flat_store PROPERTIES RUN_SERIAL TRUE) set_target_properties(agc_driver_flat_apertures_tests PROPERTIES EXCLUDE_FROM_ALL OFF) foreach(flatAperturesPart RANGE 13) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp index d5a82a6ed..c58694803 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp @@ -18,11 +18,12 @@ enum class ColorTileMode : std::uint32_t { Standard64KB = 9, S4KBX = 0x15, D4KBX = 0x16, + D64KBX = 0x1a, RenderTarget = 0x1b }; constexpr bool ColorTileModeIsXor(ColorTileMode mode) { - return mode == ColorTileMode::S4KBX || mode == ColorTileMode::D4KBX || mode == ColorTileMode::RenderTarget; + return mode == ColorTileMode::S4KBX || mode == ColorTileMode::D4KBX || mode == ColorTileMode::D64KBX || mode == ColorTileMode::RenderTarget; } constexpr std::uint32_t ColorTileModeBlockBytes(ColorTileMode mode) { @@ -30,7 +31,7 @@ constexpr std::uint32_t ColorTileModeBlockBytes(ColorTileMode mode) { } constexpr TextureTileMode ColorTextureTileMode(ColorTileMode mode) { - return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : mode == ColorTileMode::Standard64KB ? TextureTileMode::kStandard64KB : mode == ColorTileMode::S4KBX ? TextureTileMode::kS4KBX : mode == ColorTileMode::D4KBX ? TextureTileMode::kD4KBX : TextureTileMode::kR64KBX; + return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : mode == ColorTileMode::Standard64KB ? TextureTileMode::kStandard64KB : mode == ColorTileMode::S4KBX ? TextureTileMode::kS4KBX : mode == ColorTileMode::D4KBX ? TextureTileMode::kD4KBX : mode == ColorTileMode::D64KBX ? TextureTileMode::kD64KBX : TextureTileMode::kR64KBX; } ColorTileMode DecodeColorTileMode(std::uint32_t attrib3); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp index 832305195..699325b2e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp @@ -47,7 +47,7 @@ ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) { require((attrib3 & 0x80002000u) == 0 && ((resourceType == 0 && (attrib3 & 0x1fffu) == 0) || resourceType == 1 || resourceType == 2) && ((attrib3 >> 27u) & 7u) == 1, "AGC graphics: unsupported color depth, dimension, resource level or metadata mode"); const auto mode = (attrib3 >> 14u) & 0x1fu; const auto fmaskMode = (attrib3 >> 19u) & 0x1fu; - require(mode == 0 || mode == 5 || mode == 9 || mode == 0x15 || mode == 0x16 || mode == 0x1b, "AGC graphics: unsupported color tile mode"); + require(mode == 0 || mode == 5 || mode == 9 || mode == 0x15 || mode == 0x16 || mode == 0x1a || mode == 0x1b, "AGC graphics: unsupported color tile mode"); require(fmaskMode == 0 || fmaskMode == 0x18 || fmaskMode == (mode & ~3u), "AGC graphics: unsupported color FMASK swizzle mode"); require(resourceType != 0 || mode == 0 || mode == 0x1b, "AGC graphics: 1D color targets with a standard swizzle mode are invalid"); return static_cast(mode); @@ -74,9 +74,9 @@ struct SwizzleTables { }; const SwizzleTables& equationTables(ColorTileMode mode, std::uint32_t bytesPerElement, std::uint32_t blockWidth, std::uint32_t blockHeight) { - static std::once_flag once[3][5]; - static SwizzleTables tables[3][5]; - const auto modeIndex = mode == ColorTileMode::S4KBX ? 0u : mode == ColorTileMode::D4KBX ? 1u : 2u; + static std::once_flag once[4][5]; + static SwizzleTables tables[4][5]; + const auto modeIndex = mode == ColorTileMode::S4KBX ? 0u : mode == ColorTileMode::D4KBX ? 1u : mode == ColorTileMode::D64KBX ? 2u : 3u; const auto index = static_cast(std::countr_zero(bytesPerElement)); std::call_once(once[modeIndex][index], [&] { const auto* equation = FindTextureSwizzleEquation(static_cast(mode), bytesPerElement); @@ -118,6 +118,7 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, require(width != 0 && height != 0 && width <= 16384 && height <= 16384, "AGC graphics: invalid color surface extent"); require(std::has_single_bit(bytesPerElement) && bytesPerElement <= 16u, "AGC graphics: unsupported color element size"); require(pipeBankXor == 0 || (ColorTileModeIsXor(mode) && pipeBankXor < ColorTileModeBlockBytes(mode) && pipeBankXor % 256u == 0), "AGC graphics: a color pipe/bank XOR applies only to whole 256-byte units of XOR swizzle blocks"); + require(mode != ColorTileMode::D64KBX || (pipeBankXor & ~0xcf00u) == 0, "AGC graphics: unsupported display color pipe/bank XOR column bits"); std::uint32_t paddedHeight = height; switch (mode) { case ColorTileMode::Linear: { @@ -127,9 +128,9 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, } case ColorTileMode::S4KBX: case ColorTileMode::D4KBX: + case ColorTileMode::D64KBX: case ColorTileMode::RenderTarget: { - // SW_64KB_R_X: 64 KiB blocks of 2^(16 - log2(bpe)) elements, wider than tall for odd powers. - const auto log2Elements = (mode == ColorTileMode::RenderTarget ? 16u : 12u) - static_cast(std::countr_zero(bytesPerElement)); + const auto log2Elements = static_cast(std::countr_zero(ColorTileModeBlockBytes(mode))) - static_cast(std::countr_zero(bytesPerElement)); blockWidth = 1u << ((log2Elements + 1u) / 2u); blockHeight = 1u << (log2Elements / 2u); pitch = (width + blockWidth - 1u) / blockWidth * blockWidth; diff --git a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp index 0aebfe320..629659f9e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp @@ -207,4 +207,55 @@ void RunColorTargetLayoutTests() { Require(back == source, "4 KiB XOR color tiling round trip lost pixels"); reject([mode] { static_cast(ColorTargetLayout(40, 20, mode, 4, 0x1000)); }); } + + Require(DecodeColorTileMode(0x0dc68000u) == ColorTileMode::D64KBX, "the observed SW_64KB_D_X color descriptor was rejected"); + Require(ColorTextureTileMode(ColorTileMode::D64KBX) == TextureTileMode::kD64KBX, "a display XOR color target did not map to its texture mode"); + Require(ColorTileModeIsXor(ColorTileMode::D64KBX) && ColorTileModeBlockBytes(ColorTileMode::D64KBX) == 65536u, "a display XOR color target lost its block alignment or XOR flag"); + reject([] { DecodeColorTileMode(0x0cc68000u); }); + reject([] { DecodeColorTileMode((0x0dc68000u & ~(0x1fu << 19u)) | (1u << 19u)); }); + reject([] { ColorTargetLayout(64, 64, ColorTileMode::D64KBX, 4, 0x80); }); + reject([] { ColorTargetLayout(64, 64, ColorTileMode::D64KBX, 4, 0x10000); }); + for (const std::uint32_t columnBits : {0x1000u, 0x2000u, 0x3000u}) reject([columnBits] { ColorTargetLayout(64, 64, ColorTileMode::D64KBX, 4, columnBits); }); + const std::array, 5> sourceOffsets{{{27647u, 66048u}, {29694u, 212992u}, {62204u, 344064u}, {45304u, 540672u}, {110832u, 933888u}}}; + for (const std::uint32_t bpe : {1u, 2u, 4u, 8u, 16u}) { + const ColorTargetLayout sourceLayout(257, 129, ColorTileMode::D64KBX, bpe, 0x4000); + const auto& offsets = sourceOffsets[std::countr_zero(bpe)]; + for (std::uint32_t xorTag = 0; xorTag < 256u; ++xorTag) { + if ((xorTag & ~0xcfu) != 0) continue; + const ColorTargetLayout xorLayout(1, 1, ColorTileMode::D64KBX, bpe, xorTag << 8u); + Require(xorLayout.Offset(0, 0) == (xorTag << 8u), "a display XOR color origin differs from its pipe/bank bits"); + } + Require(sourceLayout.Offset(127, 63) == offsets[0] && sourceLayout.Offset(256, 128) == offsets[1], "display XOR color offsets differ from the AMD GFX10 non-RB+ source reference"); + const auto block = ThinBlockLayout(TextureTileMode::kD64KBX, bpe); + const auto width = 2u * block[1] + 1u; + const auto height = 2u * block[2] + 1u; + for (const auto mode : {ColorTileMode::D64KBX, ColorTileMode::RenderTarget}) { + const auto* equation = FindTextureSwizzleEquation(static_cast(mode), bpe); + Require(equation != nullptr, "a 64 KiB color swizzle equation is missing"); + const ColorTargetLayout layout(width, height, mode, bpe, 0x4600); + Require(layout.Alignment() == block[0] && layout.BlocksPerRow() == 3u && layout.Bytes() == ComputeSurfaceSize(ComputeElementMipLayout(ColorTextureTileMode(mode), bpe, width, height, 1), 1), "a 64 KiB color layout differs from its texture geometry"); + for (const auto x : {0u, 1u, block[1] - 1u, block[1], block[1] + 1u, width - 1u}) { + for (const auto y : {0u, 1u, block[2] - 1u, block[2], block[2] + 1u, height - 1u}) { + std::size_t expected = 0; + for (std::uint32_t bit = 0; bit < 16u; ++bit) expected |= static_cast(std::popcount((x & equation->bits[bit] & 0xfffu) ^ ((y << 12u) & equation->bits[bit] & 0xfff000u)) & 1) << bit; + const auto blockIndex = (y / block[2]) * 3u + x / block[1]; + Require(layout.Offset(x, y) == blockIndex * 65536u + (expected ^ 0x4600u), "a 64 KiB color offset differs from its mode's equation across block boundaries"); + } + } + std::vector source(layout.LinearBytes()); + for (std::size_t index = 0; index < source.size(); ++index) source[index] = static_cast((index * 31u + index / 251u) & 255u); + std::vector tiled(layout.Bytes()); + std::vector restored(source.size()); + layout.Tile(source, tiled); + layout.Detile(tiled, restored); + Require(restored == source, "64 KiB display/render-target XOR color tiling lost pixels"); + } + } + std::fill(guest.begin(), guest.end(), std::byte{0x6b}); + target = {reinterpret_cast(guest.data()), {2, 2}, VK_FORMAT_R8G8B8A8_UNORM, guest.size(), 0xe4, ColorTileMode::D64KBX}; + target.pipeBankXor = 0x0200; + WriteColorTarget(target, pixels); + readback.fill(std::byte{0}); + ReadColorTarget(target, readback); + Require(readback == pixels && guest[0] == std::byte{0x6b}, "display XOR guest color transfer lost pixels or wrote the untagged block origin"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 935796066..10f88a9bb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -814,6 +814,25 @@ void ColorPipeBankXorTests() { const auto color = AgcDriver::Graphics::DecodeState(queue).color; Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::D4KBX && color.address == block + 0x3000u && color.pipeBankXor == pipeBankXor, "a SW_4KB_D_X color base did not split into its 4 KiB block base and pipe/bank XOR " + std::to_string(pipeBankXor)); } + queue.context[0x3b8] = 0x0dc68000u; + for (const std::uint32_t pipeBankXor : {0u, 0x4600u, 0xcf00u}) { + const auto address = block + pipeBankXor; + queue.context[0x318] = static_cast(address >> 8u); + queue.context[0x390] = static_cast(address >> 40u); + const auto color = AgcDriver::Graphics::DecodeState(queue).color; + Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::D64KBX && color.address == block && color.surfaceAddress == block && color.pipeBankXor == pipeBankXor, "a SW_64KB_D_X color base did not split its block address and XOR"); + } + static std::vector mipStorage(16u * blockBytes); + const auto mipBase = (reinterpret_cast(mipStorage.data()) + blockBytes - 1u) / blockBytes * blockBytes; + const auto mips = AgcDriver::Graphics::ComputeElementMipLayout(AgcDriver::Graphics::TextureTileMode::kD64KBX, 4, 256, 128, 5); + const auto sliceBytes = AgcDriver::Graphics::ComputeSurfaceSize(mips, 1); + queue.context[0x318] = static_cast((mipBase + 0x0200u) >> 8u); + queue.context[0x390] = static_cast((mipBase + 0x0200u) >> 40u); + queue.context[0x3b0] = (4u << 28u) | (255u << 14u) | 127u; + queue.context[0x3b8] |= 1u; + queue.context[0x31b] = (2u << 26u) | 1u | (1u << 13u); + const auto mipColor = AgcDriver::Graphics::DecodeColorBuffer(queue.context, 0); + Require(mipColor.mip == 2 && mipColor.mipCount == 5 && mipColor.extent.width == 64 && mipColor.extent.height == 32 && mipColor.surfaceAddress == mipBase + sliceBytes && mipColor.address == mipBase + sliceBytes + mips[2].tiledOffset && mipColor.mipTail == mips[2].tail && mipColor.pipeBankXor == 0x0200u, "a display XOR color view lost its array slice, mip placement or XOR"); } void HiddenUserWordVertexTablesTests() { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp index f03fe5dbb..8576fd86e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp @@ -133,17 +133,18 @@ struct Block { std::uint8_t* data = nullptr; }; -AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) { +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info, AgcDriver::Graphics::ColorTileMode mode, std::uint32_t pipeBankXor) { AgcDriver::Registers cx; - cx[0x318] = static_cast(block.Address() >> 8u); + const auto address = block.Address() + pipeBankXor; + cx[0x318] = static_cast(address >> 8u); cx[0x31b] = 0; cx[0x31c] = info; cx[0x31d] = 0; cx[0x323] = 0; cx[0x324] = 0; cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); - cx[0x3b8] = LinearAttrib3; - cx[0x390] = static_cast(block.Address() >> 40u); + cx[0x3b8] = LinearAttrib3 | (static_cast(mode) << 14u); + cx[0x390] = static_cast(address >> 40u); auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); color.exportIndex = 0; color.uintExport = true; @@ -218,15 +219,17 @@ std::vector ReadBack(AgcDriver::VulkanDevice& device, std::uint64_ return stored; } -void Expect(AgcDriver::VulkanDevice& device, std::uint32_t info, VkFormat format, std::uint32_t elementBytes, std::array exported, const std::string& what) { +void Expect(AgcDriver::VulkanDevice& device, std::uint32_t info, VkFormat format, std::uint32_t elementBytes, std::array exported, const std::string& what, AgcDriver::Graphics::ColorTileMode mode = AgcDriver::Graphics::ColorTileMode::Linear, std::uint32_t pipeBankXor = 0) { const auto bytes = static_cast(Width) * Height * elementBytes; - Require(AgcDriver::Graphics::ColorTargetLayout(Width, Height, AgcDriver::Graphics::ColorTileMode::Linear, elementBytes).Bytes() == bytes, what + ": the linear target is padded"); - Block block(bytes); - std::memset(block.data, Kept, bytes); - const auto color = DecodeTarget(block, info); - Require(color.format == format && color.elementBytes == elementBytes && color.bytes == bytes, what + ": the target decoded to the wrong format"); + const AgcDriver::Graphics::ColorTargetLayout layout(Width, Height, mode, elementBytes, pipeBankXor); + Block block(layout.Bytes()); + std::memset(block.data, Kept, block.bytes); + const auto color = DecodeTarget(block, info, mode, pipeBankXor); + Require(color.format == format && color.elementBytes == elementBytes && color.bytes == layout.Bytes() && color.tileMode == mode && color.pipeBankXor == pipeBankXor, what + ": the target decoded to the wrong format, mode or XOR"); Draw(device, color, Program(exported)); - const auto stored = ReadBack(device, block.Address(), bytes); + const auto tiled = ReadBack(device, block.Address(), block.bytes); + std::vector stored(bytes); + layout.Detile(std::as_bytes(std::span(tiled)), std::as_writable_bytes(std::span(stored))); for (std::size_t offset = 0; offset < bytes; ++offset) { const bool inside = (offset / elementBytes) % Width < ScissorWidth; const auto expected = inside ? static_cast(exported[offset % elementBytes]) : Kept; @@ -241,6 +244,8 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; Expect(*device, Rgba8UintInfo, VK_FORMAT_R8G8B8A8_UINT, 4, {0x12u, 0xbeu, 0x42u, 0x7fu}, "a UINT16_ABGR export into an 8_8_8_8_UINT target"); + Expect(*device, Rgba8UintInfo, VK_FORMAT_R8G8B8A8_UINT, 4, {0x23u, 0x9au, 0x65u, 0x81u}, "a UINT16_ABGR export into a SW_64KB_D_X target", AgcDriver::Graphics::ColorTileMode::D64KBX); + Expect(*device, Rgba8UintInfo, VK_FORMAT_R8G8B8A8_UINT, 4, {0x31u, 0x7cu, 0x42u, 0x96u}, "a UINT16_ABGR export into a SW_64KB_D_X target with XOR", AgcDriver::Graphics::ColorTileMode::D64KBX, 0x4600); std::puts("8_8_8_8_UINT color target tests passed"); return 0; } catch (const std::exception& error) { diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 0a8e13c84..4207abc6b 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -25,6 +25,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. Matching depth/stencil storage access still throws. +- [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. - [Partial rect-list primitives](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): a rect-list draw whose vertex or index count is not a multiple of 3 draws its complete rectangles and leaves out the 1 or 2 vertices after them, because rect lists are drawn as Vulkan patch lists of 3 control points, which generate [⌊vertexCount/3⌋ patches](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#drawing-patch-lists). What the console draws for the incomplete rectangle is not measured; Palworld (PPSA23126) draws 4 vertices × 9 instances as a rect list when its character creation opens. - [Separate alpha blend into 8_8 SWAP_ALT targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver): the second channel of a two-channel target with `COMP_SWAP` 1 holds the alpha export, but Vulkan blends it with the color equation of the R8G8 attachment, so a draw whose alpha equation differs from its color equation is recorded twice, the first channel with the color equation and the second with the alpha factors and operation moved into the color equation. The vertex stages run twice, so a vertex shader that writes memory writes twice, and occlusion counts include both passes. Draws that write depth or stencil, pixel shaders that write memory and factors of the destination alpha throw. That the hardware blends the second channel with the alpha equation is taken from Mesa's `si_translate_colorswap` and radeonsi's use of it for luminance-alpha formats, not measured on hardware.