graphics: preserve sparse spans in rebased mip-tail views

This commit is contained in:
nmzik
2026-10-14 16:07:49 +02:00
parent aeecc9343a
commit 3a925efddf
3 changed files with 63 additions and 19 deletions
@@ -21,7 +21,7 @@ bool IsDepthPlane(const ImageInfo& info) {
info.tile_mode == Prospero::TileMode::kLinear);
}
GuestRange ContiguousMipRange(const ImageInfo& info, uint32_t level) {
GuestRange TiledMipRange(const ImageInfo& info, uint32_t level) {
const auto& block = info.tiling.block;
if (info.tile_mode != Prospero::TileMode::kStandard4KB ||
block.family != TileBlockFamily::Standard4KB || info.IsVolume() || info.samples != 1 ||
@@ -42,11 +42,10 @@ GuestRange ContiguousMipRange(const ImageInfo& info, uint32_t level) {
!TileGetBlockOffset(block, mip.tail_x, mip.tail_y, 0, origin)) {
return {};
}
// Standard4KB interleaves monotonically ordered X/Y bits. A dense rectangle
// fills exactly this power-of-two interval, including when rebased into a tail.
// Standard4KB interleaves monotonically ordered X/Y bits, so the last
// corner bounds every active texel. Tiling preserves any holes in this span.
const uint64_t size = uint64_t {last} + block.bytes_per_element;
if (!std::has_single_bit(size) || size != uint64_t {width} * height * block.bytes_per_element ||
(origin & (size - 1u)) != 0 || size > block.block_size - origin ||
if ((origin & (std::bit_ceil(size) - 1u)) != 0 || size > block.block_size - origin ||
mip.offset > UINT64_MAX - origin) {
return {};
}
@@ -57,11 +56,11 @@ uint32_t CompactMipSize(const ImageInfo& info) {
if (info.resources.levels != 1 || info.resources.layers != 1) {
return 0;
}
const auto range = ContiguousMipRange(info, 0);
if (range.size == 0 || range.address != 0) {
const auto range = TiledMipRange(info, 0);
if (range.size == 0 || range.size % 4 != 0 || range.address != 0) {
return 0;
}
const auto alignment = std::max<uint64_t>(256, range.size);
const auto alignment = std::max<uint64_t>(256, std::bit_ceil(range.size));
const auto in_block = info.data.address & (info.tiling.block.block_size - 1u);
return (info.data.address & (alignment - 1u)) == 0 &&
range.size <= info.tiling.block.block_size - in_block
@@ -226,7 +225,7 @@ uint32_t ImageInfo::DataAlignment() const {
}
const auto compact_size = CompactMipSize(*this);
if (compact_size != 0 && data.size == compact_size && mip_layout[0].size == compact_size) {
return std::max(256u, compact_size);
return std::max(256u, std::bit_ceil(compact_size));
}
return tiling.block.block_size;
}
@@ -260,14 +259,14 @@ int32_t ImageInfo::SliceOf(const ImageInfo& container, int32_t mip) const noexce
}
// PS5 block slices contain the entire reversed mip chain. A rebased tail
// descriptor covers only its active bytes within the shared tail block.
// descriptor covers its addressed span within the shared tail block.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
auto mip_offset = layout.offset;
auto mip_size = layout.size / container.resources.layers;
const auto compact_size = CompactMipSize(*this);
if (compact_size != 0 && child_stride == compact_size && mip_layout[0].size == compact_size) {
const auto range = ContiguousMipRange(container, level);
const auto range = TiledMipRange(container, level);
if (range.size != compact_size) {
return -1;
}
+9 -6
View File
@@ -18673,15 +18673,18 @@ public:
check_round_trip("odd multi-mip", total.size, layout);
}
{
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
for (const bool sparse : {false, true}) {
const auto format = sparse ? Prospero::BufferFormat::k32_32_32_32UInt
: Prospero::BufferFormat::k16_16_16_16Float;
constexpr auto tile = Prospero::TileMode::kStandard4KB;
auto view = MakeTilingImage(format, 16, 16, 1, 1, tile, 0, false);
view.data.address = 0x5032b23800;
const uint32_t extent = sparse ? 2 : 16;
auto view = MakeTilingImage(format, extent, extent, 1, 1, tile, 0, false);
view.data.address = sparse ? 0x50322e3500 : 0x5032b23800;
view.UpdateSize();
Require(name, "rebased mip footprint",
view.data.size == 0x800 && view.DataAlignment() == 0x800 &&
view.mip_layout[0].pitch == 32 &&
view.data.size == (sparse ? 0x60 : 0x800) &&
view.DataAlignment() == (sparse ? 0x100 : 0x800) &&
view.mip_layout[0].pitch == (sparse ? 16 : 32) &&
view.mip_layout[0].height == 16 &&
view.tiled_slice_stride == 0x1000,
"a compact view changed its allocation geometry");
+44 -2
View File
@@ -1366,18 +1366,26 @@ void TestRebasedTextureMipViews() {
Check(view.MipOf(chain) == -1,
"full-block child ignored the parent mip's nonzero tail origin");
for (uint32_t variant = 0; variant < 7; ++variant) {
for (uint32_t variant = 0; variant < 9; ++variant) {
view = make_view(4, 0);
view.data = {chain.data.address, 0};
view.pitch = 0;
switch (variant) {
case 0: view.extent = {8, 16, 1}; break; // Holes in the Standard4KB span.
case 0: view.extent = {7, 16, 1}; break; // Unproven non-power-of-two view.
case 1: view.resources.levels = 2; break;
case 2: view.resources.layers = 2; break;
case 3: view.type = Prospero::ImageType::kColor3D; break;
case 4: view.tile_mode = Prospero::TileMode::kStandard64KB; break;
case 5: view.metadata.kind = ImageMetadataKind::Dcc; break;
case 6: view.data = {0x5032b23800, 0x1000}; break;
case 7:
case 8:
view.guest_format = variant == 7 ? Prospero::BufferFormat::k8UNorm
: Prospero::BufferFormat::k16UNorm;
view.pixel_format = variant == 7 ? vk::Format::eR8Unorm : vk::Format::eR16Unorm;
view.bytes_per_block = 0;
view.extent = {1, 1, 1};
break;
}
view.UpdateSize();
Check(view.data.size >= 0x1000 && view.DataAlignment() >= 0x1000,
@@ -1388,6 +1396,40 @@ void TestRebasedTextureMipViews() {
view.UpdateSize();
Check(view.data.size == 0x1000 && view.DataAlignment() == 0x1000,
"misaligned descriptor received compact tail alignment");
view = make_view(4, 0);
view.data = {chain.data.address, 0};
view.extent.width = 8;
view.UpdateSize();
Check(view.data.size == 0x600 && view.DataAlignment() == 0x800,
"sparse rectangle confused addressed span with active byte count");
auto sparse_chain = chain;
sparse_chain.data = {0x503225f000, 0};
sparse_chain.pixel_format = vk::Format::eR32G32B32A32Uint;
sparse_chain.guest_format = Prospero::BufferFormat::k32_32_32_32UInt;
sparse_chain.extent = {64, 64, 1};
sparse_chain.resources = {6, 7};
sparse_chain.pitch = sparse_chain.bytes_per_block = 0;
sparse_chain.UpdateSize();
auto sparse = sparse_chain;
sparse.data = {0x50322e3500, 0};
sparse.extent = {2, 2, 1};
sparse.resources = {1, 1};
sparse.pitch = 0;
sparse.UpdateSize();
Check(sparse_chain.tiled_slice_stride == 0x16000 &&
sparse.data.size == 0x60 && sparse.mip_layout[0].size == 0x60 &&
sparse.DataAlignment() == 0x100 && sparse.LinearSize() == 0x200 &&
sparse.MipOf(sparse_chain) == 5 && sparse.SliceOf(sparse_chain, 5) == 6,
"captured sparse 2x2 view lost its byte span, tail origin, or array layer");
sparse.UpdateSize();
Check(sparse.data.size == 0x60 && sparse.mip_layout[0].size == 0x60 &&
sparse.DataAlignment() == 0x100,
"repeated sparse view sizing changed its span or alignment");
sparse.data.address -= 0x100;
Check(sparse.MipOf(sparse_chain) == -1,
"sparse view accepted a different mip-tail origin");
}
void TestShaderFeatureCollection() {