From d7d7c142f4140e839b7b00b676b5cba63080d904 Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Fri, 2 Oct 2026 11:33:11 +0200 Subject: [PATCH 1/2] feat(agc): 4 KiB standard color targets and color views of one array slice Color targets with the SW_4KB_S swizzle (CB_COLOR_ATTRIB3 tile mode 5) are laid out in 4 KiB blocks with the standard element order the texture detiler uses, and are resident through the same storage images as 64 KiB targets. A CB_COLOR_VIEW selecting one slice (SLICE_START equal to SLICE_MAX) renders into that slice: the target starts one slice size per slice after the surface, the slice size being the surface's mip chain as a texture of the same tile mode lays it out. Views of several slices are still rejected. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Graphics/include/ColorTargetLayout.hpp | 8 +++- .../Graphics/src/ColorTargetLayout.cpp | 39 ++++++++++++++++++- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 8 ++-- .../libSceAgcDriver/Graphics/src/State.cpp | 7 +++- .../tests/ColorTargetLayout.cpp | 15 ++++++- .../prx/libSceAgcDriver/tests/Graphics.cpp | 11 +++++- 6 files changed, 76 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp index 90e71db9587..6431cd075b3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_COLORTARGETLAYOUT_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_COLORTARGETLAYOUT_HPP +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include #include #include @@ -10,9 +11,14 @@ namespace AgcDriver::Graphics { enum class ColorTileMode : std::uint32_t { Linear = 0, + Standard4KB = 5, RenderTarget = 0x1b }; +constexpr TextureTileMode ColorTextureTileMode(ColorTileMode mode) { + return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : TextureTileMode::kR64KBX; +} + ColorTileMode DecodeColorTileMode(std::uint32_t attrib3); class ColorTargetLayout { @@ -20,7 +26,7 @@ class ColorTargetLayout { ColorTargetLayout(std::uint32_t width, std::uint32_t height, ColorTileMode mode, std::uint32_t bytesPerElement = 4); std::size_t Bytes() const { return bytes; } std::size_t LinearBytes() const { return static_cast(width) * height * elementBytes; } - std::size_t Alignment() const { return mode == ColorTileMode::Linear ? 256u : 65536u; } + std::size_t Alignment() const { return mode == ColorTileMode::Linear ? 256u : mode == ColorTileMode::Standard4KB ? 4096u : 65536u; } std::uint32_t BlocksPerRow() const { return pitch / blockWidth; } std::size_t Offset(std::uint32_t x, std::uint32_t y) const; void Detile(std::span source, std::span destination) const; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp index b25def3e61e..ec2bd739587 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp @@ -17,6 +17,16 @@ std::uint32_t parity(std::uint32_t value) { return static_cast(std::popcount(value)) & 1u; } +std::uint32_t standardOffset(std::uint32_t x, std::uint32_t y, std::uint32_t elementBytes) { + switch (elementBytes) { + case 1u: return ((y << 4) & 0x1f0u) ^ ((y << 5) & 0x400u) ^ (x & 0x00fu) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u); + case 2u: return ((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^ ((y << 6) & 0x400u) ^ ((x << 1) & 0x00eu) ^ ((x << 4) & 0x080u) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u); + case 4u: return ((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^ ((y << 6) & 0x400u) ^ ((x << 2) & 0x00cu) ^ ((x << 5) & 0x080u) ^ ((x << 6) & 0x200u) ^ ((x << 7) & 0x800u); + case 8u: return ((y << 4) & 0x030u) ^ ((y << 6) & 0x100u) ^ ((y << 7) & 0x400u) ^ ((x << 3) & 0x008u) ^ ((x << 5) & 0x0c0u) ^ ((x << 6) & 0x200u) ^ ((x << 7) & 0x800u); + default: return ((y << 4) & 0x030u) ^ ((y << 6) & 0x100u) ^ ((y << 7) & 0x400u) ^ ((x << 6) & 0x0c0u) ^ ((x << 7) & 0x200u) ^ ((x << 8) & 0x800u); + } +} + } ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) { @@ -24,7 +34,7 @@ ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) { const auto mode = (attrib3 >> 14u) & 0x1fu; const auto fmaskMode = (attrib3 >> 19u) & 0x1fu; require(fmaskMode == 0 || fmaskMode == 0x18, "AGC graphics: unsupported color FMASK swizzle mode"); - require(mode == 0 || mode == 0x1b, "AGC graphics: unsupported color tile mode"); + require(mode == 0 || mode == 5 || mode == 0x1b, "AGC graphics: unsupported color tile mode"); return static_cast(mode); } @@ -59,6 +69,20 @@ const SwizzleTables& renderTargetTables(std::uint32_t bytesPerElement, std::uint return tables[index]; } +const SwizzleTables& standardTables(std::uint32_t bytesPerElement, std::uint32_t blockWidth, std::uint32_t blockHeight) { + static std::once_flag once[5]; + static SwizzleTables tables[5]; + const auto index = static_cast(std::countr_zero(bytesPerElement)); + std::call_once(once[index], [&] { + auto& table = tables[index]; + table.x.resize(blockWidth); + table.y.resize(blockHeight); + for (std::uint32_t x = 0; x < blockWidth; ++x) table.x[x] = standardOffset(x, 0, bytesPerElement); + for (std::uint32_t y = 0; y < blockHeight; ++y) table.y[y] = standardOffset(0, y, bytesPerElement); + }); + return tables[index]; +} + } ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, ColorTileMode mode, std::uint32_t bytesPerElement) : width(width), height(height), pitch(width), mode(mode), bytes(0), elementBytes(bytesPerElement) { @@ -83,6 +107,17 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, yOffsets = tables.y.data(); break; } + case ColorTileMode::Standard4KB: { + const auto log2Bytes = static_cast(std::countr_zero(bytesPerElement)); + blockWidth = 1u << (6u - (log2Bytes + 1u) / 2u); + blockHeight = 1u << (6u - log2Bytes / 2u); + pitch = (width + blockWidth - 1u) / blockWidth * blockWidth; + paddedHeight = (height + blockHeight - 1u) / blockHeight * blockHeight; + const auto& tables = standardTables(bytesPerElement, blockWidth, blockHeight); + xOffsets = tables.x.data(); + yOffsets = tables.y.data(); + break; + } default: throw std::runtime_error("AGC graphics: unsupported color tile mode"); } const auto size = static_cast(pitch) * paddedHeight * bytesPerElement; @@ -93,7 +128,7 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, std::size_t ColorTargetLayout::offset(std::uint32_t x, std::uint32_t y) const { if (mode == ColorTileMode::Linear) return (static_cast(y) * pitch + x) * elementBytes; const auto block = static_cast(y / blockHeight) * (pitch / blockWidth) + x / blockWidth; - return block * 65536u + (xOffsets[x % blockWidth] ^ yOffsets[y % blockHeight]); + return block * Alignment() + (xOffsets[x % blockWidth] ^ yOffsets[y % blockHeight]); } std::size_t ColorTargetLayout::Offset(std::uint32_t x, std::uint32_t y) const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index aff6f513e13..382c71e99ab 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -42,7 +42,7 @@ std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) { // The color buffer as a single-mip 2D surface descriptor (tile mode SW_64KB_R_X). GuestTextureResource SurfaceForTarget(const ColorTarget& color) { - Require(color.tileMode == ColorTileMode::RenderTarget, "only 64 KiB tiled color targets are resident"); + Require(color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB, "only 4 KiB standard and 64 KiB tiled color targets are resident"); const bool chain = color.mipCount > 1; GuestTextureResource surface{}; surface.baseAddress = chain ? color.surfaceAddress : color.address; @@ -53,7 +53,7 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) { surface.mipCount = color.mipCount; surface.baseLevel = 0; surface.lastLevel = color.mipCount - 1; - surface.tileMode = TextureTileMode::kR64KBX; + surface.tileMode = ColorTextureTileMode(color.tileMode); surface.dimension = TextureDimension::k2D; surface.format = GuestFormatFor(color.format, color.elementBytes); surface.dstSelX = 4; @@ -1508,7 +1508,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& timer.phase(PhaseSetup); // Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again. static const bool residentTargets = std::getenv("APS5_NO_RESIDENT_TARGETS") == nullptr; - if (binding.gpuTiling && residentTargets) { + if ((binding.gpuTiling || (color.tileMode == ColorTileMode::Standard4KB && context.detiler != nullptr)) && residentTargets) { // The lookup refreshes the image on every draw (StorageTexture::Refresh: FlushPending, // CollectWrites over the target's pages, the DCC key scan of TextureClearKeys, then // UnchangedSince). The page walk is skipped while the worker's collect epoch lasts @@ -2074,7 +2074,7 @@ void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass) Require(IsDccClear(keys), std::string("CB metadata pass over DCC keys that are ") + DccKeysName(keys) + " (per-block metadata is not modeled)"); const auto texel = clearTexel(color, keys); std::shared_ptr resident; - if (color.tileMode == ColorTileMode::RenderTarget && context.detiler != nullptr) { + if ((color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB) && context.detiler != nullptr) { try { resident = CachedStorageSurface(context, SurfaceForTarget(color)); } catch (const std::exception&) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 22373ebce06..83e20bb73c9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -619,7 +619,9 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")"); Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported"); const auto view = read(cx, 0x31b + stride); - Require((view & ~0x3c000000u) == 0, "color array views are unsupported"); + Require((view & ~0x3fffffffu) == 0, "reserved CB_COLOR_VIEW bits are set"); + const auto slice = view & 0x1fffu; + Require(slice == ((view >> 13u) & 0x1fffu), "color views of several array slices are unsupported"); const auto viewMip = (view >> 26u) & 0xfu; zero(cx, 0x31d + stride, ~0u, "color samples, fragments or destination alpha override"); const auto attrib2 = read(cx, 0x3b0 + slot); @@ -635,7 +637,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { color.mip = viewMip; if (maxMip != 0) { // A mipmapped surface is addressed like a texture; the view renders into one mip of it. - const auto mips = ComputeElementMipLayout(color.tileMode == ColorTileMode::Linear ? TextureTileMode::kLinear : TextureTileMode::kR64KBX, color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u); + const auto mips = ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u); const auto& mip = mips.at(viewMip); mipOffset = mip.tiledOffset; color.mipTail = mip.tail; @@ -645,6 +647,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { const auto high = read(cx, 0x390 + slot); Require((high & ~0xffu) == 0, "invalid color address extension"); color.surfaceAddress = (static_cast(high) << 40u) | (static_cast(read(cx, 0x318 + stride)) << 8u); + if (slice != 0) color.surfaceAddress += slice * ComputeSurfaceSize(ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.surfaceExtent.width, color.surfaceExtent.height, color.mipCount), 1); color.address = color.surfaceAddress + mipOffset; color.bytes = colorLayout.Bytes(); GuestMemory::CheckRange(reinterpret_cast(color.address), color.bytes, colorLayout.Alignment(), true); diff --git a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp index f95863fdc9b..97227046635 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp @@ -1,5 +1,6 @@ #include "BdaTests.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetTransfer.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include #include @@ -26,7 +27,7 @@ void RunColorTargetLayoutTests() { reject([] { DecodeColorTileMode(0x4dc6c001); }); reject([] { DecodeColorTileMode(0x4dc6e000); }); reject([] { DecodeColorTileMode(0xcdc6c000); }); - reject([] { DecodeColorTileMode(0x09014000); }); + reject([] { DecodeColorTileMode(0x09004000); }); reject([] { ColorTargetLayout(0, 1, ColorTileMode::RenderTarget); }); const ColorTargetLayout padded(63, 2, ColorTileMode::Linear); Require(padded.Bytes() == 512 && padded.LinearBytes() == 504 && padded.Offset(0, 1) == 256, "linear rows are not padded to 256 bytes"); @@ -37,6 +38,18 @@ void RunColorTargetLayoutTests() { Require(layout.Offset(128, 0) == 65536 && layout.Offset(0, 128) == 3 * 65536, "block raster order is incorrect"); Require(layout.Offset(16, 0) == 0x2200 && layout.Offset(0, 8) == 0x1100, "render-target XOR addressing is incorrect"); Require(layout.Offset(256, 0) == 2 * 65536, "third block address is incorrect"); + Require(DecodeColorTileMode(0x4dc14000) == ColorTileMode::Standard4KB, "4 KiB standard color descriptor was rejected"); + const ColorTargetLayout standard(256, 256, ColorTileMode::Standard4KB); + Require(standard.Bytes() == ComputeSurfaceSize(ComputeElementMipLayout(TextureTileMode::kStandard4KB, 4, 256, 256, 1), 1) && standard.Alignment() == 4096, "4 KiB standard color layout differs from the texture layout"); + Require(standard.Offset(32, 0) == 4096 && standard.Offset(0, 32) == 8 * 4096 && standard.Offset(1, 0) == 4 && standard.Offset(0, 1) == 16, "4 KiB standard block or element addressing is incorrect"); + std::vector standardSeen(1024); + for (std::uint32_t y = 0; y < 32; ++y) { + for (std::uint32_t x = 0; x < 32; ++x) { + const auto address = standard.Offset(x, y); + Require(address < 4096 && address % 4 == 0 && !standardSeen[address / 4], "4 KiB standard block aliases or leaves its texels"); + standardSeen[address / 4] = true; + } + } reject([&] { layout.Offset(257, 0); }); std::vector tiled(layout.Bytes(), std::byte{0x5a}); std::vector linear(layout.LinearBytes()); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index b74c63b80df..a837bbca775 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -28,6 +28,7 @@ namespace { using AgcDriver::Graphics::Require; alignas(256) std::array colorMemory{}; +alignas(256) std::array sliceMemory{}; AgcDriver::QueueState makeState() { AgcDriver::QueueState queue; @@ -126,7 +127,7 @@ void stateTests() { queue.context.erase(0x3b8); expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); queue = makeState(); - queue.context[0x3b8] |= 5u << 14u; + queue.context[0x3b8] |= 1u << 14u; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "unsupported color tile mode"); queue = makeState(); queue.context[0x3b0] = (62u << 14u) | 3u; @@ -482,7 +483,13 @@ void DepthStencilTests() { queue.context[0x31b] = 1u << 26u; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "mip exceeds"); queue.context[0x31b] = 1u << 13u; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "array views"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "several array slices"); + const auto sliced = reinterpret_cast(sliceMemory.data()); + queue.context[0x318] = static_cast(sliced >> 8u); + queue.context[0x390] = static_cast(sliced >> 40u); + queue.context[0x31b] = 1u | (1u << 13u); + const auto slice = AgcDriver::Graphics::DecodeState(queue); + Require(slice.color.address == sliced + 1024u && slice.color.bytes == 1024u, "a color view of one slice did not move the target by one slice"); } void DepthBoundsBiasTests() { From 34c57ca65d363895e5330fd9af8b9e0cbaf51f55 Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Sat, 3 Oct 2026 11:28:15 +0200 Subject: [PATCH 2/2] feat(agc): render into one depth slice of a 3D color target Co-Authored-By: Claude Opus 5.5 (1M context) --- .../prx/libSceAgcDriver/Graphics/include/State.hpp | 2 ++ .../libSceAgcDriver/Graphics/include/Texture.hpp | 5 +++-- .../Graphics/src/ColorTargetLayout.cpp | 3 ++- .../libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp | 9 +++++---- .../prx/libSceAgcDriver/Graphics/src/State.cpp | 9 ++++++++- .../prx/libSceAgcDriver/Graphics/src/Texture.cpp | 14 ++++++++------ core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 9 +++++++++ 7 files changed, 37 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 9c49f888d58..a32309b7c2c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -48,6 +48,8 @@ struct ColorTarget { bool mipTail = false; std::array clearWords{}; std::uint32_t slot = 0; + std::uint32_t depth = 1; + std::uint32_t depthSlice = 0; }; struct DepthTarget { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index ed80ade6c56..d2149878836 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -116,7 +117,7 @@ class StorageTexture : public std::enable_shared_from_this { // Render targets live in the same images: draws attach mip 0 through a view of the color // buffer's format and mark the image dirty like a storage write. bool Attachable() const { return attachable; } - VkImageView AttachmentView(VkFormat format, std::uint32_t mip = 0); + VkImageView AttachmentView(VkFormat format, std::uint32_t mip = 0, std::uint32_t depthSlice = 0); void WriteBack(); // Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead). void MarkDirty(); @@ -421,7 +422,7 @@ class StorageTexture : public std::enable_shared_from_this { std::map extraViews; std::map firstLayerViews; bool attachable = false; - std::map, VkImageView> attachmentViews; + std::map, VkImageView> attachmentViews; VkFormat storageFormat = VK_FORMAT_UNDEFINED; // Results are on the GPU only (guarded by the pending-write registry lock). bool dirty = false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp index ec2bd739587..243402731f4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp @@ -30,7 +30,8 @@ std::uint32_t standardOffset(std::uint32_t x, std::uint32_t y, std::uint32_t ele } ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) { - require((attrib3 & 0x80002000u) == 0 && (attrib3 & 0x1fffu) == 0 && ((attrib3 >> 24u) & 3u) == 1 && ((attrib3 >> 27u) & 7u) == 1, "AGC graphics: unsupported color depth, dimension, resource level or metadata mode"); + const auto resourceType = (attrib3 >> 24u) & 3u; + require((attrib3 & 0x80002000u) == 0 && ((resourceType == 1 && (attrib3 & 0x1fffu) == 0) || 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(fmaskMode == 0 || fmaskMode == 0x18, "AGC graphics: unsupported color FMASK swizzle mode"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 382c71e99ab..3ca3ffd1f29 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -48,13 +48,13 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) { surface.baseAddress = chain ? color.surfaceAddress : color.address; surface.width = chain ? color.surfaceExtent.width : color.extent.width; surface.height = chain ? color.surfaceExtent.height : color.extent.height; - surface.depthOrLastArray = 0; + surface.depthOrLastArray = color.depth - 1u; surface.baseArray = 0; surface.mipCount = color.mipCount; surface.baseLevel = 0; surface.lastLevel = color.mipCount - 1; surface.tileMode = ColorTextureTileMode(color.tileMode); - surface.dimension = TextureDimension::k2D; + surface.dimension = color.depth > 1 ? TextureDimension::k3D : TextureDimension::k2D; surface.format = GuestFormatFor(color.format, color.elementBytes); surface.dstSelX = 4; surface.dstSelY = 5; @@ -1518,16 +1518,17 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& binding.resident = refreshResidentTarget(context, state, color, outcome, profile, [&] { auto resident = CachedStorageSurface(context, SurfaceForTarget(color)); Require(resident->Attachable(), "storage format cannot be a color attachment"); - Require(color.mipCount > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout"); + Require(color.mipCount > 1 || color.depth > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout"); return resident; }); } if (binding.resident != nullptr) { timer.phase(PhaseReadTarget); - targetViews.push_back(binding.resident->AttachmentView(color.format, color.mip)); + targetViews.push_back(binding.resident->AttachmentView(color.format, color.mip, color.depthSlice)); continue; } Require(!color.mipTail, "rendering into a packed mip tail needs the resident image of its surface"); + Require(color.depth == 1, "rendering into a 3D color target needs the resident image of its surface"); if (binding.gpuTiling) { binding.mip = ColorTargetMip(color, colorLayout); binding.tiled = std::make_unique(context, colorLayout.Bytes(), copies); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 83e20bb73c9..81e05a896fa 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -629,6 +629,13 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { Require(viewMip <= maxMip, "color view mip exceeds the surface"); const auto attrib3 = read(cx, 0x3b8 + slot); color.tileMode = DecodeColorTileMode(attrib3); + const bool volume = ((attrib3 >> 24u) & 3u) == 2u; + if (volume) { + color.depth = (attrib3 & 0x1fffu) + 1u; + Require(maxMip == 0 && (info & 0x10000000u) == 0, "mipmapped or DCC 3D color targets are unsupported"); + Require(slice < color.depth, "the color view slice is beyond the 3D surface"); + color.depthSlice = slice; + } color.extent = {((attrib2 >> 14u) & 0x3fffu) + 1u, (attrib2 & 0x3fffu) + 1u}; color.elementBytes = decoded.elementBytes; std::uint64_t mipOffset = 0; @@ -647,7 +654,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { const auto high = read(cx, 0x390 + slot); Require((high & ~0xffu) == 0, "invalid color address extension"); color.surfaceAddress = (static_cast(high) << 40u) | (static_cast(read(cx, 0x318 + stride)) << 8u); - if (slice != 0) color.surfaceAddress += slice * ComputeSurfaceSize(ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.surfaceExtent.width, color.surfaceExtent.height, color.mipCount), 1); + if (slice != 0 && !volume) color.surfaceAddress += slice * ComputeSurfaceSize(ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.surfaceExtent.width, color.surfaceExtent.height, color.mipCount), 1); color.address = color.surfaceAddress + mipOffset; color.bytes = colorLayout.Bytes(); GuestMemory::CheckRange(reinterpret_cast(color.address), color.bytes, colorLayout.Alignment(), true); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 962cbccef00..ea69132a00f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -663,7 +663,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; // Sampled views of other same-size formats (sRGB, reinterpretations) read the image directly. - imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT; + imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | (descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT : 0u) | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT; imageInfo.imageType = ImageTypeFor(descriptor.dimension); imageInfo.format = vkFormat; imageInfo.extent = {descriptor.width, descriptor.height, geometry.imageDepth}; @@ -675,7 +675,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, { VkFormatProperties properties{}; context.formatProperties(context.physical, vkFormat, &properties); - attachable = descriptor.dimension == TextureDimension::k2D && (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; + attachable = (descriptor.dimension == TextureDimension::k2D || descriptor.dimension == TextureDimension::k3D) && (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; if (attachable) imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -958,10 +958,12 @@ VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const return created; } -VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip) { +VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip, std::uint32_t depthSlice) { Require(attachable, "storage image cannot be a color attachment"); Require(mip < descriptor.mipCount, "attachment mip exceeds the storage image"); - const auto found = attachmentViews.find({format, mip}); + const bool volume = descriptor.dimension == TextureDimension::k3D; + Require(depthSlice == 0 || (volume && mip == 0 && depthSlice <= descriptor.depthOrLastArray), "attachment slice is outside the storage image"); + const auto found = attachmentViews.find({format, mip, depthSlice}); if (found != attachmentViews.end()) return found->second; VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; usage.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -971,10 +973,10 @@ VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip) { viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = format; viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; - viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, 1u}; + viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, volume ? depthSlice : descriptor.baseArray, 1u}; VkImageView created = VK_NULL_HANDLE; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &created), "vkCreateImageView attachment"); - attachmentViews.emplace(std::pair{format, mip}, created); + attachmentViews.emplace(std::tuple{format, mip, depthSlice}, created); return created; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index a837bbca775..5f1d4bfe89e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -490,6 +490,15 @@ void DepthStencilTests() { queue.context[0x31b] = 1u | (1u << 13u); const auto slice = AgcDriver::Graphics::DecodeState(queue); Require(slice.color.address == sliced + 1024u && slice.color.bytes == 1024u, "a color view of one slice did not move the target by one slice"); + queue.context[0x3b8] = 0x0a000003; + queue.context[0x31b] = 2u | (2u << 13u); + const auto volume = AgcDriver::Graphics::DecodeState(queue); + Require(volume.color.address == sliced && volume.color.depth == 4u && volume.color.depthSlice == 2u, "a color view of one 3D depth slice did not keep the surface address and select the slice"); + queue.context[0x31b] = 4u | (4u << 13u); + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "beyond the 3D surface"); + queue.context[0x31b] = 0; + queue.context[0x31c] |= 0x10000000; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DCC 3D color targets"); } void DepthBoundsBiasTests() {