Merge pull request #503 from oneandonlydean/feat/agc-3d-thick-mip-tails

feat(agc): thick mip tails of mipmapped 3D textures
This commit is contained in:
Dean 2026-10-05 21:46:40 +13:00 committed by GitHub
commit 6df3301700
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 16 deletions

View File

@ -41,9 +41,9 @@ std::array<std::uint32_t, 3> ThinBlockLayout(TextureTileMode tileMode, std::uint
// levels and their padding). A retile of the mip writes every byte of these and no other.
std::vector<std::pair<std::uint64_t, std::uint64_t>> 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<TileMipLayout> mips;
std::uint32_t depth;

View File

@ -289,6 +289,8 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
result.mips.resize(mipCount);
std::array<std::uint32_t, 3> 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::size_t>(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<std::uint64_t>(mip.pitchBytes) * mip.height;
} else {
mip.blocksPerRow = paddedWidth / block[0];
mip.tiledSize = static_cast<std::uint64_t>(mip.blocksPerRow) * (AlignUp(levelHeight, block[1]) / block[1]) * blockBytes;
}
if (!mip.tail) {
mip.tiledOffset = tiledOffset;
tiledOffset += mip.tiledSize;
}
mip.linearSize = static_cast<std::uint64_t>(mip.pitchBytes) * mip.height;
mip.tiledOffset = tiledOffset;
mip.linearOffset = linearOffset;
tiledOffset += mip.tiledSize;
linearOffset += mip.linearSize;
}
result.slabBytes = tiledOffset;

View File

@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
#include <bit>
#include <span>
#include <string>
#include <string_view>
#include <utility>
@ -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<const ElementAddress> 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<std::uint64_t>(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");
}
{