diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index b4a5c6427..b83d4f4f8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -41,9 +41,9 @@ std::array ThinBlockLayout(TextureTileMode tileMode, std::uint // levels and their padding). A retile of the mip writes every byte of these and no other. std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip); -// 3D surface. Each slab holds the whole mip chain, smallest level first. Depth slice z of a level is detiled -// from slab z / blockDepth (slabBytes apart) with the swizzle's slice input set to z, into linear -// slices sliceLinearBytes apart. +// 3D surface. Each slab holds the whole mip chain: the mip tail block, if any, then the other levels from the +// smallest to level 0. Depth slice z of a level is detiled from slab z / blockDepth (slabBytes apart) with the +// swizzle's slice input set to z, into linear slices sliceLinearBytes apart. struct ThickLayout { std::vector mips; std::uint32_t depth; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index cc3b08146..a25632749 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -289,6 +289,8 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s result.mips.resize(mipCount); std::array block{CalcLinearBlockWidth(bytesPerElement), 1u, 1u}; std::uint32_t blockBytes = 0; + std::uint32_t maxTailLevels = 0; + auto firstTailLevel = mipCount; if (tileMode != TextureTileMode::kLinear) { block = ThickBlockExtent(tileMode, bytesPerElement); blockBytes = tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; @@ -296,18 +298,22 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto tailWidth = log2BlockBytes % 3u == 1u ? block[0] >> 1u : block[0]; const auto tailHeight = log2BlockBytes % 3u == 0u ? block[1] >> 1u : block[1]; const auto effectiveLog2 = log2BlockBytes - (log2BlockBytes - 8u) / 3u; - const auto maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; - for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) - Require(std::max(width >> level, 1u) > tailWidth || std::max(height >> level, 1u) > tailHeight || mipCount - level > maxTailLevels, "3D texture mip tails are not implemented"); + maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; + for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) { + if (ShiftCeil(width, level) <= tailWidth && ShiftCeil(height, level) <= tailHeight && mipCount - level <= maxTailLevels) { + firstTailLevel = level; + break; + } + } } result.blockDepth = block[2]; - std::uint64_t tiledOffset = 0; + std::uint64_t tiledOffset = firstTailLevel < mipCount ? blockBytes : 0u; std::uint64_t linearOffset = 0; for (auto level = mipCount; level-- > 0;) { auto& mip = result.mips[level]; mip.width = std::max(width >> level, 1u); mip.height = std::max(height >> level, 1u); - mip.tail = false; + mip.tail = level >= firstTailLevel; mip.tailX = 0; mip.tailY = 0; const bool tiled = tileMode != TextureTileMode::kLinear; @@ -315,17 +321,34 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto levelHeight = tiled ? std::max((height + (1u << level) - 1u) >> level, 1u) : mip.height; const auto paddedWidth = AlignUp(levelWidth, block[0]); mip.pitchBytes = paddedWidth * bytesPerElement; - if (!tiled) { + if (mip.tail) { + constexpr std::uint32_t microBlock[5][2] = {{8, 4}, {4, 4}, {4, 4}, {4, 2}, {2, 2}}; + const auto element = static_cast(std::countr_zero(bytesPerElement)); + const auto slot = maxTailLevels - 1u - (level - firstTailLevel); + const auto slotOffset = slot > 6u ? 16u << slot : slot << 8u; + for (std::uint32_t bit = 0; bit < 6u; ++bit) { + mip.tailX |= ((slotOffset >> (9u + 2u * bit)) & 1u) << bit; + mip.tailY |= ((slotOffset >> (8u + 2u * bit)) & 1u) << bit; + } + mip.tailX *= microBlock[element][0]; + mip.tailY *= microBlock[element][1]; + mip.blocksPerRow = 1u; + mip.pitchBytes = block[0] * bytesPerElement; + mip.tiledOffset = 0; + mip.tiledSize = blockBytes; + } else if (!tiled) { mip.blocksPerRow = paddedWidth; mip.tiledSize = static_cast(mip.pitchBytes) * mip.height; } else { mip.blocksPerRow = paddedWidth / block[0]; mip.tiledSize = static_cast(mip.blocksPerRow) * (AlignUp(levelHeight, block[1]) / block[1]) * blockBytes; } + if (!mip.tail) { + mip.tiledOffset = tiledOffset; + tiledOffset += mip.tiledSize; + } mip.linearSize = static_cast(mip.pitchBytes) * mip.height; - mip.tiledOffset = tiledOffset; mip.linearOffset = linearOffset; - tiledOffset += mip.tiledSize; linearOffset += mip.linearSize; } result.slabBytes = tiledOffset; diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index 38324bf69..3058cc098 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,14 @@ void reject(TAction action, std::string_view reason) { throw std::runtime_error(std::string("expected texture tiling rejection: ") + std::string(reason)); } +struct ElementAddress { + std::uint32_t mip; + std::uint32_t x; + std::uint32_t y; + std::uint32_t z; + std::uint64_t address; +}; + std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { std::uint64_t offset = 0; for (std::uint32_t bit = 0; bit < 16; ++bit) { @@ -34,6 +43,39 @@ std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32 return offset; } +GuestTextureResource thickVolume(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) { + GuestTextureResource resource{}; + resource.width = width; + resource.height = height; + resource.depthOrLastArray = depth - 1u; + resource.mipCount = mipCount; + resource.tileMode = tileMode; + resource.dimension = TextureDimension::k3D; + resource.format = format; + return resource; +} + +void requireThickAddresses(const GuestTextureResource& resource, std::uint32_t bytesPerElement, std::uint32_t firstTailLevel, std::uint64_t guestBytes, std::span expected, const std::string& what) { + const auto geometry = DescribeSurface(resource); + Require(geometry.thick && geometry.mips.size() == resource.mipCount && geometry.guestBytes == guestBytes, what + ": guest size differs from addrlib"); + for (std::uint32_t level = 0; level < resource.mipCount; ++level) Require(geometry.mips[level].tail == (level >= firstTailLevel), what + ": mip " + std::to_string(level) + " is on the wrong side of the mip tail"); + const auto blockBytes = resource.tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; + const auto* equation = FindTextureSwizzleEquation(resource.tileMode == TextureTileMode::kStandard4KB ? 0x105u : 0x109u, bytesPerElement); + Require(equation != nullptr, what + ": missing thick swizzle equation"); + const auto block = ThickBlockExtent(resource.tileMode, bytesPerElement); + for (const auto& element : expected) { + Require(geometry.HasLayer(element.mip, element.z), what + ": sample slice lies outside its level"); + const auto& mip = geometry.mips.at(element.mip); + auto address = geometry.GuestLayerOffset(element.z) + mip.tiledOffset; + if (mip.tail) { + address += equationOffset(*equation, element.x + mip.tailX, element.y + mip.tailY, element.z); + } else { + address += (static_cast(element.y / block[1]) * mip.blocksPerRow + element.x / block[0]) * blockBytes + equationOffset(*equation, element.x, element.y, element.z); + } + Require(address == element.address, what + ": mip " + std::to_string(element.mip) + " element (" + std::to_string(element.x) + ", " + std::to_string(element.y) + ", " + std::to_string(element.z) + ") detiles from " + std::to_string(address) + " instead of addrlib's " + std::to_string(element.address)); + } +} + } void RunTextureTilingTests() { @@ -154,11 +196,19 @@ void RunTextureTilingTests() { const auto odd = DescribeSurface(volume); Require(odd.mips[1].width == 16 && odd.mips[1].blocksPerRow == 3 && odd.mips[1].tiledSize == 12288, "a 3D level must be padded from its size rounded up, as addrlib does"); Require(odd.mips[0].blocksPerRow == 5 && odd.mips[0].tiledOffset == 12288 && odd.mips[0].tiledSize == 40960, "3D mip 0 of a non-power-of-two volume changed"); - volume.width = 8; - volume.height = 8; - volume.depthOrLastArray = 7; - volume.mipCount = 2; - reject([&] { DescribeSurface(volume); }, "3D texture mip tails are not implemented"); + } + + { + constexpr ElementAddress chainInTail[] = {{0, 0, 0, 0, 0x800}, {0, 7, 7, 7, 0xffc}, {0, 4, 2, 4, 0xe20}, {0, 2, 7, 0, 0x968}, {0, 7, 0, 7, 0xed4}, {1, 0, 0, 0, 0x300}, {1, 3, 3, 3, 0x3fc}, {1, 2, 1, 2, 0x3c8}, {1, 1, 3, 0, 0x32c}, {1, 3, 0, 3, 0x3d4}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 8, 8, 8, 2), 4, 0, 4096, chainInTail, "SW_4KB_S 32 bpp 8x8x8, 2 levels"); + constexpr ElementAddress unevenTail[] = {{0, 0, 0, 0, 0x6000}, {0, 32, 19, 11, 0x1f0b8}, {0, 16, 6, 6, 0x85a0}, {0, 11, 19, 0, 0xc06c}, {0, 32, 0, 11, 0x1a090}, {1, 0, 0, 0, 0x3000}, {1, 15, 9, 5, 0x4e5c}, {1, 8, 3, 3, 0x40b8}, {1, 5, 9, 0, 0x3a0c}, {1, 15, 0, 5, 0x4654}, {2, 0, 0, 0, 0x1000}, {2, 7, 4, 2, 0x13c4}, {2, 4, 1, 1, 0x1218}, {2, 2, 4, 0, 0x1140}, {2, 7, 0, 2, 0x12c4}, {3, 0, 0, 0, 0x800}, {3, 3, 1, 0, 0x84c}, {3, 2, 0, 0, 0x840}, {3, 1, 1, 0, 0x80c}, {3, 3, 0, 0, 0x844}, {4, 0, 0, 0, 0x300}, {4, 1, 0, 0, 0x304}, {4, 0, 0, 0, 0x300}, {5, 0, 0, 0, 0x200}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 33, 20, 12, 6), 4, 3, 131072, unevenTail, "SW_4KB_S 32 bpp 33x20x12, 6 levels"); + constexpr ElementAddress wideTail[] = {{0, 0, 0, 0, 0x20000}, {0, 39, 23, 19, 0xb0bf8}, {0, 20, 8, 10, 0x2e280}, {0, 13, 23, 0, 0x41b28}, {0, 39, 0, 19, 0x902d8}, {1, 0, 0, 0, 0x10000}, {1, 19, 11, 9, 0x1e178}, {1, 10, 4, 5, 0x11c50}, {1, 6, 11, 0, 0x14360}, {1, 19, 0, 9, 0x1a058}, {2, 0, 0, 0, 0x8000}, {2, 9, 5, 4, 0x9c28}, {2, 5, 2, 2, 0x8388}, {2, 3, 5, 0, 0x8868}, {2, 9, 0, 4, 0x9408}, {3, 0, 0, 0, 0x4000}, {3, 4, 2, 1, 0x4310}, {3, 2, 1, 1, 0x4070}, {3, 1, 2, 0, 0x4108}, {3, 4, 0, 1, 0x4210}, {4, 0, 0, 0, 0x1000}, {4, 1, 0, 0, 0x1008}, {5, 0, 0, 0, 0xa00}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 71, 40, 24, 20, 6), 8, 2, 786432, wideTail, "SW_64KB_S 64 bpp 40x24x20, 6 levels"); + constexpr ElementAddress byteTail[] = {{0, 0, 0, 0, 0x20000}, {0, 99, 59, 69, 0x11c8af}, {0, 50, 20, 35, 0x8d116}, {0, 33, 59, 0, 0x4c829}, {0, 99, 0, 69, 0xf8087}, {1, 0, 0, 0, 0x10000}, {1, 49, 29, 34, 0x7d919}, {1, 25, 10, 17, 0x13a25}, {1, 16, 29, 0, 0x15908}, {1, 49, 0, 34, 0x79011}, {2, 0, 0, 0, 0x8000}, {2, 24, 14, 16, 0xbb20}, {2, 12, 5, 8, 0x8748}, {2, 8, 14, 0, 0x8b20}, {2, 24, 0, 16, 0xb200}, {3, 0, 0, 0, 0x4000}, {3, 11, 6, 7, 0x43b7}, {3, 6, 2, 4, 0x40e2}, {3, 4, 6, 0, 0x4160}, {3, 11, 0, 7, 0x4297}, {4, 0, 0, 0, 0x1000}, {4, 5, 2, 3, 0x1075}, {4, 3, 1, 2, 0x101b}, {4, 2, 2, 0, 0x1022}, {4, 5, 0, 3, 0x1055}, {5, 0, 0, 0, 0xa00}, {5, 2, 0, 1, 0xa06}, {5, 1, 0, 0, 0xa01}, {6, 0, 0, 0, 0x900}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 1, 100, 60, 70, 7), 1, 2, 1179648, byteTail, "SW_64KB_S 8 bpp 100x60x70, 7 levels"); + constexpr ElementAddress wideElementTail[] = {{0, 0, 0, 0, 0x3000}, {0, 8, 4, 2, 0x5880}, {0, 4, 1, 1, 0x4030}, {0, 3, 4, 0, 0x3a40}, {0, 8, 0, 2, 0x5080}, {1, 0, 0, 0, 0x1000}, {1, 3, 1, 0, 0x1260}, {1, 2, 0, 0, 0x1200}, {1, 1, 1, 0, 0x1060}, {1, 3, 0, 0, 0x1240}, {2, 0, 0, 0, 0x800}, {2, 1, 0, 0, 0x840}, {3, 0, 0, 0, 0x300}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 77, 9, 5, 3, 4), 16, 2, 24576, wideElementTail, "SW_4KB_S 128 bpp 9x5x3, 4 levels"); } {