feat(graphics): mipmapped 3D textures
3D textures with more than one mip level were refused. Each slab of blockDepth slices of a thick (SW_4KB_S / SW_64KB_S) volume now holds the whole mip chain, smallest level first and level 0 last, as 2D chains are laid out. A level's depth halves per level, so the texture loops skip the slices a level does not have. Thin 3D (D/R 64KB_X) chains use the 2D mip layout per depth slice. Thick mip tails still throw. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f9a8f5a53
commit
efde918a62
|
|
@ -2,6 +2,7 @@
|
|||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURETILING_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
|
@ -34,17 +35,18 @@ std::array<std::uint32_t, 3> ThickBlockExtent(TextureTileMode tileMode, std::uin
|
|||
// row (TileMipLayout), except the tail mips, which share one block. Throws for linear tiling.
|
||||
std::array<std::uint32_t, 3> ThinBlockLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement);
|
||||
|
||||
// Single-mip 3D surface. Each depth slice z 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, 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.
|
||||
struct ThickLayout {
|
||||
TileMipLayout mip;
|
||||
std::vector<TileMipLayout> mips;
|
||||
std::uint32_t depth;
|
||||
std::uint32_t blockDepth;
|
||||
std::uint64_t slabBytes;
|
||||
std::uint64_t sliceLinearBytes;
|
||||
std::uint64_t guestBytes;
|
||||
};
|
||||
ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth);
|
||||
ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount);
|
||||
|
||||
// Guest surface as the texture upload/write-back loops walk it: array layers of thin surfaces, or the
|
||||
// depth slices of a volume (which Vulkan holds as one layer with extent depth).
|
||||
|
|
@ -63,6 +65,7 @@ struct SurfaceGeometry {
|
|||
std::uint64_t LinearLayerOffset(std::uint32_t layer) const { return static_cast<std::uint64_t>(layer) * sliceLinearBytes; }
|
||||
std::uint32_t CopyLayer(std::uint32_t layer) const { return imageDepth > 1 ? 0u : layer; }
|
||||
std::int32_t CopyDepth(std::uint32_t layer) const { return imageDepth > 1 ? static_cast<std::int32_t>(layer) : 0; }
|
||||
bool HasLayer(std::uint32_t level, std::uint32_t layer) const { return imageDepth <= 1 || layer < std::max(imageDepth >> level, 1u); }
|
||||
};
|
||||
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor);
|
||||
|
||||
|
|
|
|||
|
|
@ -326,7 +326,9 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
|||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
const auto guestLayerOffset = geometry.GuestLayerOffset(layer);
|
||||
const auto linearLayerOffset = geometry.LinearLayerOffset(layer);
|
||||
for (const auto& mip : mips) {
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
detiler.Dispatch(commands, descriptor.tileMode, elementBytes, tiled->Handle(), guestLayerOffset + mip.tiledOffset, linear->Handle(), linearLayerOffset + mip.linearOffset, mip, false, layer, geometry.thick);
|
||||
}
|
||||
}
|
||||
|
|
@ -356,6 +358,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
|||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
const auto linearLayerOffset = static_cast<std::uint64_t>(layer) * sliceLinearBytes;
|
||||
for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = linearLayerOffset + mip.linearOffset;
|
||||
|
|
@ -1435,7 +1438,9 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
|||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (layers != nullptr && !(*layers)[layer]) continue;
|
||||
for (const auto& mip : mips) {
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
detiler.Dispatch(commands, descriptor.tileMode, elementBytes, import->buffer, importOffset + geometry.GuestLayerOffset(layer) + mip.tiledOffset, linear->Handle(), geometry.LinearLayerOffset(layer) + mip.linearOffset, mip, false, layer, geometry.thick);
|
||||
}
|
||||
}
|
||||
|
|
@ -1484,7 +1489,9 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
|||
CopyBuffer(context, commands, staging.Handle(), 0, tiled.Handle(), 0, original.size());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
for (const auto& mip : mips) {
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
detiler.Dispatch(commands, descriptor.tileMode, elementBytes, tiled.Handle(), geometry.GuestLayerOffset(layer) + mip.tiledOffset, linear.Handle(), geometry.LinearLayerOffset(layer) + mip.linearOffset, mip, false, layer, geometry.thick);
|
||||
}
|
||||
}
|
||||
|
|
@ -1534,6 +1541,7 @@ std::vector<StorageTexture::SliceWindow> StorageTexture::sliceWindows(std::span<
|
|||
for (const auto& [runBegin, runEnd] : runs) {
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
const auto sliceBegin = geometry.GuestLayerOffset(layer) + mip.tiledOffset;
|
||||
const auto sliceEnd = sliceBegin + mip.tiledSize;
|
||||
|
|
@ -1614,6 +1622,7 @@ std::uint64_t StorageTexture::uploadWindows(const HostImport& import, std::span<
|
|||
std::vector<std::pair<std::uint64_t, std::uint64_t>> tailBlocks;
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
if (!mip.tail) continue;
|
||||
const auto sliceBegin = geometry.GuestLayerOffset(layer) + mip.tiledOffset;
|
||||
|
|
@ -1912,6 +1921,7 @@ std::vector<VkBufferImageCopy> StorageTexture::CopyRegions(const std::vector<boo
|
|||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (layers != nullptr && !(*layers)[layer]) continue;
|
||||
for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = layer * sliceLinearBytes + mip.linearOffset;
|
||||
|
|
@ -2836,6 +2846,7 @@ bool StorageTexture::CopyFrom(StorageTexture& source, const char*& refusal) {
|
|||
std::vector<VkImageCopy> regions;
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
VkImageCopy region{};
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, geometry.CopyLayer(layer), 1};
|
||||
region.srcOffset = {0, 0, geometry.CopyDepth(layer)};
|
||||
|
|
@ -3258,7 +3269,9 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 1, &linearRead, 0, nullptr);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (storedLayers != nullptr && !(*storedLayers)[layer]) continue;
|
||||
for (const auto& mip : mips) {
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
detiler.Dispatch(commands, descriptor.tileMode, elementBytes, linear->Handle(), geometry.LinearLayerOffset(layer) + mip.linearOffset, tiledScratch->Handle(), geometry.GuestLayerOffset(layer) + mip.tiledOffset, mip, true, layer, geometry.thick);
|
||||
}
|
||||
}
|
||||
|
|
@ -3349,7 +3362,9 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
const VkBufferMemoryBarrier toShader[] = {WholeBufferBarrier(linear.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT), WholeBufferBarrier(tiled.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT)};
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 2, toShader, 0, nullptr);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
for (const auto& mip : mips) {
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
detiler.Dispatch(commands, descriptor.tileMode, elementBytes, linear.Handle(), geometry.LinearLayerOffset(layer) + mip.linearOffset, tiled.Handle(), geometry.GuestLayerOffset(layer) + mip.tiledOffset, mip, true, layer, geometry.thick);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,35 +255,54 @@ std::array<std::uint32_t, 3> ThinBlockLayout(TextureTileMode tileMode, std::uint
|
|||
return {block.blockSize, block.blockWidth, block.blockHeight};
|
||||
}
|
||||
|
||||
ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth) {
|
||||
ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) {
|
||||
Require(width != 0 && height != 0 && depth != 0, "cannot compute layout for a zero-sized 3D texture");
|
||||
Require(mipCount != 0 && mipCount <= 16u, "3D texture mip count is out of range");
|
||||
const auto bytesPerElement = BytesPerElement(format);
|
||||
Require(BlockWidth(format) == 1u && BlockHeight(format) == 1u, "block-compressed 3D textures are not implemented");
|
||||
ThickLayout result{};
|
||||
result.depth = depth;
|
||||
auto& mip = result.mip;
|
||||
mip.width = width;
|
||||
mip.height = height;
|
||||
mip.tail = false;
|
||||
if (tileMode == TextureTileMode::kLinear) {
|
||||
const auto paddedWidth = AlignUp(width, CalcLinearBlockWidth(bytesPerElement));
|
||||
mip.blocksPerRow = paddedWidth;
|
||||
mip.pitchBytes = paddedWidth * bytesPerElement;
|
||||
mip.tiledSize = static_cast<std::uint64_t>(mip.pitchBytes) * height;
|
||||
result.blockDepth = 1;
|
||||
result.slabBytes = mip.tiledSize;
|
||||
} else {
|
||||
const auto block = ThickBlockExtent(tileMode, bytesPerElement);
|
||||
const auto paddedWidth = AlignUp(width, block[0]);
|
||||
const auto paddedHeight = AlignUp(height, block[1]);
|
||||
mip.blocksPerRow = paddedWidth / block[0];
|
||||
mip.pitchBytes = paddedWidth * bytesPerElement;
|
||||
result.blockDepth = block[2];
|
||||
result.slabBytes = static_cast<std::uint64_t>(mip.blocksPerRow) * (paddedHeight / block[1]) * (tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u);
|
||||
mip.tiledSize = result.slabBytes;
|
||||
result.mips.resize(mipCount);
|
||||
std::array<std::uint32_t, 3> block{CalcLinearBlockWidth(bytesPerElement), 1u, 1u};
|
||||
std::uint32_t blockBytes = 0;
|
||||
if (tileMode != TextureTileMode::kLinear) {
|
||||
block = ThickBlockExtent(tileMode, bytesPerElement);
|
||||
blockBytes = tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u;
|
||||
const auto log2BlockBytes = static_cast<std::uint32_t>(std::countr_zero(blockBytes));
|
||||
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");
|
||||
}
|
||||
mip.linearSize = static_cast<std::uint64_t>(mip.pitchBytes) * height;
|
||||
result.sliceLinearBytes = mip.linearSize;
|
||||
result.blockDepth = block[2];
|
||||
std::uint64_t tiledOffset = 0;
|
||||
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.tailX = 0;
|
||||
mip.tailY = 0;
|
||||
const auto paddedWidth = AlignUp(mip.width, block[0]);
|
||||
mip.pitchBytes = paddedWidth * bytesPerElement;
|
||||
if (tileMode == TextureTileMode::kLinear) {
|
||||
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(mip.height, block[1]) / block[1]) * blockBytes;
|
||||
}
|
||||
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;
|
||||
result.sliceLinearBytes = linearOffset;
|
||||
result.guestBytes = result.slabBytes * ((depth + result.blockDepth - 1u) / result.blockDepth);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -291,9 +310,8 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
|
|||
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) {
|
||||
SurfaceGeometry geometry;
|
||||
if (descriptor.dimension == TextureDimension::k3D && (descriptor.tileMode == TextureTileMode::kD64KBX || descriptor.tileMode == TextureTileMode::kR64KBX)) {
|
||||
Require(descriptor.mipCount == 1, "mipmapped 3D textures are not implemented");
|
||||
const auto depth = descriptor.depthOrLastArray + 1u;
|
||||
geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, 1u);
|
||||
geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount);
|
||||
geometry.layers = depth;
|
||||
geometry.imageDepth = depth;
|
||||
geometry.guestBytes = ComputeSurfaceSize(geometry.mips, depth);
|
||||
|
|
@ -302,10 +320,9 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) {
|
|||
return geometry;
|
||||
}
|
||||
if (descriptor.dimension == TextureDimension::k3D) {
|
||||
Require(descriptor.mipCount == 1, "mipmapped 3D textures are not implemented");
|
||||
const auto depth = descriptor.depthOrLastArray + 1u;
|
||||
const auto thick = ComputeThickLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, depth);
|
||||
geometry.mips = {thick.mip};
|
||||
const auto thick = ComputeThickLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, depth, descriptor.mipCount);
|
||||
geometry.mips = thick.mips;
|
||||
geometry.layers = depth;
|
||||
geometry.imageDepth = depth;
|
||||
geometry.guestBytes = thick.guestBytes;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,29 @@ void RunTextureTilingTests() {
|
|||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 0); }, "mip count is out of range");
|
||||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 17); }, "mip count is out of range");
|
||||
|
||||
{
|
||||
GuestTextureResource volume{};
|
||||
volume.width = 64;
|
||||
volume.height = 64;
|
||||
volume.depthOrLastArray = 31;
|
||||
volume.mipCount = 3;
|
||||
volume.tileMode = TextureTileMode::kStandard4KB;
|
||||
volume.dimension = TextureDimension::k3D;
|
||||
volume.format = 56;
|
||||
const auto geometry = DescribeSurface(volume);
|
||||
Require(geometry.thick && geometry.blockDepth == 8 && geometry.mips.size() == 3, "mipmapped 3D texture geometry changed");
|
||||
Require(geometry.mips[2].tiledOffset == 0 && geometry.mips[2].tiledSize == 8192, "3D mip 2 must lead each slab");
|
||||
Require(geometry.mips[1].tiledOffset == 8192 && geometry.mips[1].tiledSize == 32768, "3D mip 1 offset or size changed");
|
||||
Require(geometry.mips[0].tiledOffset == 40960 && geometry.mips[0].tiledSize == 131072, "3D mip 0 must end each slab");
|
||||
Require(geometry.layerBytes == 172032 && geometry.guestBytes == 172032ull * 4, "3D slabs must hold the whole mip chain");
|
||||
Require(geometry.HasLayer(1, 15) && !geometry.HasLayer(1, 16) && !geometry.HasLayer(2, 8), "3D mips must halve their depth");
|
||||
volume.width = 8;
|
||||
volume.height = 8;
|
||||
volume.depthOrLastArray = 7;
|
||||
volume.mipCount = 2;
|
||||
reject([&] { DescribeSurface(volume); }, "3D texture mip tails are not implemented");
|
||||
}
|
||||
|
||||
reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain");
|
||||
reject([] { ComputeSurfaceSize(ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 1), 0); }, "zero array layers");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue