[GPU] Linear mip tail exact extent estimation

This commit is contained in:
Triang3l
2021-05-15 18:19:06 +03:00
parent a69a058991
commit dd5ea87213
4 changed files with 309 additions and 280 deletions

View File

@@ -204,35 +204,22 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth,
return true;
}
TextureGuestLevelLayout GetGuestLevelLayout(
TextureGuestLayout GetGuestTextureLayout(
xenos::DataDimension dimension, uint32_t base_pitch_texels_div_32,
uint32_t width_texels, uint32_t height_texels, uint32_t depth_or_array_size,
bool is_tiled, xenos::TextureFormat format, bool is_mip, uint32_t level,
bool is_packed_level) {
// If with packed mips the mips 1... happen to be packed in what's stored as
// mip 0, this mip tail appears to be stored like mips (with power of two size
// rounding) rather than like the base level (with the pitch from the fetch
// constant), so we distinguish between them for mip == 0.
// Base is by definition the level 0.
assert_false(!is_mip && level);
// Level 0 for mips is the special case for a packed mip tail of very small
// textures, where the tail is stored like it's at the level 0.
assert_false(is_mip && !level && !is_packed_level);
bool is_tiled, xenos::TextureFormat format, bool has_packed_levels,
bool has_base, uint32_t max_level) {
TextureGuestLayout layout;
TextureGuestLevelLayout layout;
// For safety, for instance, with empty resolve regions (extents calculation
// may overflow otherwise due to the assumption of at least one row, for
// example, but an empty texture is empty anyway).
if (!width_texels ||
(dimension != xenos::DataDimension::k1D && !height_texels) ||
((dimension == xenos::DataDimension::k2DOrStacked ||
dimension == xenos::DataDimension::k3D) &&
!depth_or_array_size)) {
std::memset(&layout, 0, sizeof(layout));
return layout;
if (dimension == xenos::DataDimension::k1D) {
assert_false(is_tiled);
// GetPackedMipOffset may result in packing along Y for `width > height`
// textures.
assert_false(has_packed_levels);
height_texels = 1;
}
uint32_t depth =
dimension == xenos::DataDimension::k3D ? depth_or_array_size : 1;
switch (dimension) {
case xenos::DataDimension::k2DOrStacked:
layout.array_size = depth_or_array_size;
@@ -244,158 +231,17 @@ TextureGuestLevelLayout GetGuestLevelLayout(
layout.array_size = 1;
}
const FormatInfo* format_info = FormatInfo::Get(format);
uint32_t bytes_per_block = format_info->bytes_per_block();
// Calculate the strides.
// Mips have row / depth slice strides calculated from a mip of a texture
// whose base size is a power of two.
// The base mip has tightly packed depth slices, and takes the row pitch from
// the fetch constant.
// For stride calculation purposes, mip dimensions are always aligned to
// 32x32x4 blocks (or x1 for the missing dimensions), including for linear
// textures.
// Linear texture rows are 256-byte-aligned.
uint32_t row_pitch_texels_unaligned;
uint32_t z_slice_stride_texel_rows_unaligned;
if (is_mip) {
row_pitch_texels_unaligned =
std::max(xe::next_pow2(width_texels) >> level, uint32_t(1));
z_slice_stride_texel_rows_unaligned =
std::max(xe::next_pow2(height_texels) >> level, uint32_t(1));
} else {
row_pitch_texels_unaligned = base_pitch_texels_div_32 << 5;
z_slice_stride_texel_rows_unaligned = height_texels;
}
uint32_t row_pitch_blocks_tile_aligned = xe::align(
xe::align(row_pitch_texels_unaligned, format_info->block_width) /
format_info->block_width,
xenos::kTextureTileWidthHeight);
layout.row_pitch_bytes = row_pitch_blocks_tile_aligned * bytes_per_block;
// Assuming the provided pitch is already 256-byte-aligned for linear, but
// considering the guest-provided pitch more important (no information about
// how the GPU actually handles unaligned rows).
if (!is_tiled && is_mip) {
layout.row_pitch_bytes = xe::align(layout.row_pitch_bytes,
xenos::kTextureLinearRowAlignmentBytes);
}
layout.z_slice_stride_block_rows =
dimension != xenos::DataDimension::k1D
? xe::align(xe::align(z_slice_stride_texel_rows_unaligned,
format_info->block_height) /
format_info->block_height,
xenos::kTextureTileWidthHeight)
: 1;
layout.array_slice_stride_bytes =
layout.row_pitch_bytes * layout.z_slice_stride_block_rows;
uint32_t z_stride_bytes = layout.array_slice_stride_bytes;
if (dimension == xenos::DataDimension::k3D) {
layout.array_slice_stride_bytes *=
xe::align(depth_or_array_size, xenos::kTextureTiledDepthGranularity);
}
uint32_t array_slice_stride_bytes_non_4kb_aligned =
layout.array_slice_stride_bytes;
layout.array_slice_stride_bytes =
xe::align(array_slice_stride_bytes_non_4kb_aligned,
xenos::kTextureSubresourceAlignmentBytes);
// Estimate the memory amount actually referenced by the texture, which may be
// smaller (especially in the 2x2 linear k_8_8_8_8 case in Test Drive
// Unlimited, for which 4 KB are allocated, while the stride is 8 KB) or
// bigger than the stride. For tiled textures, this is the dimensions aligned
// to 32x32x4 blocks (or x1 for the missing dimensions).
// For linear, doing almost the same for the mip tail (which can be used for
// both the mips and, if the texture is very small, the base) because it
// stores multiple mips outside the first mip in it in the tile padding
// (though there's no need to align the size to the next power of two for this
// purpose for mips - packed mips are only used when min(width, height) <= 16,
// and packing is first done along the shorter axis - even if the longer axis
// is larger than 32, nothing will be packed beyond the extent of the longer
// axis). "Almost" because for linear textures, we're rounding the size to
// 32x32x4 texels, not blocks - first packed mips start from 16-texel, not
// 16-block, shortest dimension, and are placed in 32x- or x32-texel tiles,
// while 32 blocks for compressed textures are bigger in memory than 32
// texels.
layout.x_extent_blocks = xe::align(width_texels, format_info->block_width) /
format_info->block_width;
layout.y_extent_blocks =
dimension != xenos::DataDimension::k1D
? xe::align(height_texels, format_info->block_height) /
format_info->block_height
: 1;
layout.z_extent =
dimension == xenos::DataDimension::k3D ? depth_or_array_size : 1;
if (is_tiled) {
layout.x_extent_blocks =
xe::align(layout.x_extent_blocks, xenos::kTextureTileWidthHeight);
assert_true(dimension != xenos::DataDimension::k1D);
layout.y_extent_blocks =
xe::align(layout.y_extent_blocks, xenos::kTextureTileWidthHeight);
if (dimension == xenos::DataDimension::k3D) {
layout.z_extent =
xe::align(layout.z_extent, xenos::kTextureTiledDepthGranularity);
// 3D texture addressing is pretty complex, so it's hard to determine the
// memory extent of a subregion - just use pitch_tiles * height_tiles *
// depth_tiles * bytes_per_tile at least for now, until we find a case
// where it causes issues. width > pitch is a very weird edge case anyway,
// and is extremely unlikely.
assert_true(layout.x_extent_blocks <= row_pitch_blocks_tile_aligned);
layout.array_slice_data_extent_bytes =
array_slice_stride_bytes_non_4kb_aligned;
} else {
// 2D 32x32-block tiles are laid out linearly in the texture.
// Calculate the extent as ((all rows except for the last * pitch in
// tiles + last row length in tiles) * bytes per tile).
layout.array_slice_data_extent_bytes =
(layout.y_extent_blocks - xenos::kTextureTileWidthHeight) *
layout.row_pitch_bytes +
bytes_per_block * layout.x_extent_blocks *
xenos::kTextureTileWidthHeight;
}
} else {
if (is_packed_level) {
layout.x_extent_blocks =
xe::align(layout.x_extent_blocks,
xenos::kTextureTileWidthHeight / format_info->block_width);
if (dimension != xenos::DataDimension::k1D) {
layout.y_extent_blocks =
xe::align(layout.y_extent_blocks, xenos::kTextureTileWidthHeight /
format_info->block_height);
if (dimension == xenos::DataDimension::k3D) {
layout.z_extent =
xe::align(layout.z_extent, xenos::kTextureTiledDepthGranularity);
}
}
}
layout.array_slice_data_extent_bytes =
z_stride_bytes * (layout.z_extent - 1) +
layout.row_pitch_bytes * (layout.y_extent_blocks - 1) +
bytes_per_block * layout.x_extent_blocks;
}
layout.level_data_extent_bytes =
layout.array_slice_stride_bytes * (layout.array_size - 1) +
layout.array_slice_data_extent_bytes;
return layout;
}
TextureGuestLayout GetGuestTextureLayout(
xenos::DataDimension dimension, uint32_t base_pitch_texels_div_32,
uint32_t width_texels, uint32_t height_texels, uint32_t depth_or_array_size,
bool is_tiled, xenos::TextureFormat format, bool has_packed_levels,
bool has_base, uint32_t max_level) {
TextureGuestLayout layout;
if (dimension == xenos::DataDimension::k1D) {
height_texels = 1;
// For safety, for instance, with empty resolve regions (extents calculation
// may overflow otherwise due to the assumption of at least one row, for
// example, but an empty texture is empty anyway).
if (!width_texels || !height_texels || !depth || !layout.array_size) {
std::memset(&layout, 0, sizeof(layout));
return layout;
}
// For safety, clamp the maximum level.
uint32_t longest_axis = std::max(width_texels, height_texels);
if (dimension == xenos::DataDimension::k3D) {
longest_axis = std::max(longest_axis, depth_or_array_size);
}
uint32_t max_level_for_dimensions = xe::log2_floor(longest_axis);
uint32_t max_level_for_dimensions =
xe::log2_floor(std::max(std::max(width_texels, height_texels), depth));
assert_true(max_level <= max_level_for_dimensions);
max_level = std::min(max_level, max_level_for_dimensions);
layout.max_level = max_level;
@@ -404,33 +250,210 @@ TextureGuestLayout GetGuestTextureLayout(
? GetPackedMipLevel(width_texels, height_texels)
: UINT32_MAX;
if (has_base) {
layout.base =
GetGuestLevelLayout(dimension, base_pitch_texels_div_32, width_texels,
height_texels, depth_or_array_size, is_tiled,
format, false, 0, layout.packed_level == 0);
} else {
// Clear unused level layouts to zero strides/sizes.
if (!has_base) {
std::memset(&layout.base, 0, sizeof(layout.base));
}
std::memset(layout.mips, 0, sizeof(layout.mips));
std::memset(layout.mip_offsets_bytes, 0, sizeof(layout.mip_offsets_bytes));
if (layout.packed_level != 0) {
std::memset(&layout.mips[0], 0, sizeof(layout.mips[0]));
}
uint32_t max_stored_level = std::min(max_level, layout.packed_level);
{
uint32_t mips_end = max_stored_level + 1;
assert_true(mips_end <= xe::countof(layout.mips));
uint32_t mips_unused_count = uint32_t(xe::countof(layout.mips)) - mips_end;
if (mips_unused_count) {
std::memset(&layout.mips[mips_end], 0,
sizeof(layout.mips[0]) * mips_unused_count);
std::memset(&layout.mip_offsets_bytes[mips_end], 0,
sizeof(layout.mip_offsets_bytes[0]) * mips_unused_count);
}
}
layout.mips_total_extent_bytes = 0;
if (max_level) {
uint32_t mip_offset_bytes = 0;
uint32_t max_stored_mip = std::min(max_level, layout.packed_level);
for (uint32_t mip = std::min(uint32_t(1), layout.packed_level);
mip <= max_stored_mip; ++mip) {
layout.mip_offsets_bytes[mip] = mip_offset_bytes;
TextureGuestLevelLayout& mip_layout = layout.mips[mip];
mip_layout =
GetGuestLevelLayout(dimension, base_pitch_texels_div_32, width_texels,
height_texels, depth_or_array_size, is_tiled,
format, true, mip, mip == layout.packed_level);
const FormatInfo* format_info = FormatInfo::Get(format);
uint32_t bytes_per_block = format_info->bytes_per_block();
// The loop counter can mean two things depending on whether the packed mip
// tail is stored as mip 0, because in this case, it would be ambiguous since
// both the base and the mips would be on "level 0", but stored separately and
// possibly with a different layout.
uint32_t loop_level_last;
if (layout.packed_level == 0) {
// Packed mip tail is the level 0 - may need to load mip tails for the base,
// the mips, or both.
// Loop iteration 0 - base packed mip tail.
// Loop iteration 1 - mips packed mip tail.
loop_level_last = uint32_t(max_level != 0);
} else {
// Packed mip tail is not the level 0.
// Loop iteration is the actual level being loaded.
loop_level_last = max_stored_level;
}
uint32_t mip_offset_bytes = 0;
for (uint32_t loop_level = has_base ? 0 : 1; loop_level <= loop_level_last;
++loop_level) {
bool is_base = loop_level == 0;
uint32_t level = (layout.packed_level == 0) ? 0 : loop_level;
TextureGuestLayout::Level& level_layout =
is_base ? layout.base : layout.mips[level];
// Calculate the strides.
// Mips have row / depth slice strides calculated from a mip of a texture
// whose base size is a power of two.
// The base mip has tightly packed depth slices, and takes the row pitch
// from the fetch constant.
// For stride calculation purposes, mip dimensions are always aligned to
// 32x32x4 blocks (or x1 for the missing dimensions), including for linear
// textures.
// Linear texture rows are 256-byte-aligned.
uint32_t row_pitch_texels_unaligned;
uint32_t z_slice_stride_texel_rows_unaligned;
if (is_base) {
row_pitch_texels_unaligned = base_pitch_texels_div_32 << 5;
z_slice_stride_texel_rows_unaligned = height_texels;
} else {
row_pitch_texels_unaligned =
std::max(xe::next_pow2(width_texels) >> level, uint32_t(1));
z_slice_stride_texel_rows_unaligned =
std::max(xe::next_pow2(height_texels) >> level, uint32_t(1));
}
uint32_t row_pitch_blocks_tile_aligned = xe::align(
xe::align(row_pitch_texels_unaligned, format_info->block_width) /
format_info->block_width,
xenos::kTextureTileWidthHeight);
level_layout.row_pitch_bytes =
row_pitch_blocks_tile_aligned * bytes_per_block;
// Assuming the provided pitch is already 256-byte-aligned for linear, but
// considering the guest-provided pitch more important (no information about
// how the GPU actually handles unaligned rows).
if (!is_tiled && !is_base) {
level_layout.row_pitch_bytes = xe::align(
level_layout.row_pitch_bytes, xenos::kTextureLinearRowAlignmentBytes);
}
level_layout.z_slice_stride_block_rows =
dimension != xenos::DataDimension::k1D
? xe::align(xe::align(z_slice_stride_texel_rows_unaligned,
format_info->block_height) /
format_info->block_height,
xenos::kTextureTileWidthHeight)
: 1;
level_layout.array_slice_stride_bytes =
level_layout.row_pitch_bytes * level_layout.z_slice_stride_block_rows;
uint32_t z_stride_bytes = level_layout.array_slice_stride_bytes;
if (dimension == xenos::DataDimension::k3D) {
level_layout.array_slice_stride_bytes *=
xe::align(depth_or_array_size, xenos::kTextureTiledDepthGranularity);
}
uint32_t array_slice_stride_bytes_non_4kb_aligned =
level_layout.array_slice_stride_bytes;
level_layout.array_slice_stride_bytes =
xe::align(array_slice_stride_bytes_non_4kb_aligned,
xenos::kTextureSubresourceAlignmentBytes);
// Estimate the memory amount actually referenced by the texture, which may
// be smaller (especially in the 1280x720 linear k_8_8_8_8 case in Ridge
// Racer Unbounded, for which memory exactly for 1280x720 is allocated, and
// aligning the height to 32 would cause access of an unallocated page) or
// bigger than the stride. For tiled textures, this is the dimensions
// aligned to 32x32x4 blocks (or x1 for the missing dimensions).
uint32_t level_width_blocks =
xe::align(std::max(width_texels >> level, uint32_t(1)),
format_info->block_width) /
format_info->block_width;
uint32_t level_height_blocks =
xe::align(std::max(height_texels >> level, uint32_t(1)),
format_info->block_height) /
format_info->block_height;
uint32_t level_depth = std::max(depth >> level, uint32_t(1));
if (is_tiled) {
level_layout.x_extent_blocks =
xe::align(level_width_blocks, xenos::kTextureTileWidthHeight);
level_layout.y_extent_blocks =
xe::align(level_height_blocks, xenos::kTextureTileWidthHeight);
if (dimension == xenos::DataDimension::k3D) {
level_layout.z_extent =
xe::align(level_depth, xenos::kTextureTiledDepthGranularity);
// 3D texture addressing is pretty complex, so it's hard to determine
// the memory extent of a subregion - just use `pitch_tiles *
// height_tiles * depth_tiles * bytes_per_tile` at least for now, until
// we find a case where it causes issues. `width > pitch` is a very
// weird edge case anyway, and is extremely unlikely.
assert_true(level_layout.x_extent_blocks <=
row_pitch_blocks_tile_aligned);
level_layout.array_slice_data_extent_bytes =
array_slice_stride_bytes_non_4kb_aligned;
} else {
level_layout.z_extent = 1;
// 2D 32x32-block tiles are laid out linearly in the texture.
// Calculate the extent as ((all rows except for the last * pitch in
// tiles + last row length in tiles) * bytes per tile).
level_layout.array_slice_data_extent_bytes =
(level_layout.y_extent_blocks - xenos::kTextureTileWidthHeight) *
level_layout.row_pitch_bytes +
bytes_per_block * level_layout.x_extent_blocks *
xenos::kTextureTileWidthHeight;
}
} else {
if (level == layout.packed_level) {
// Calculate the portion of the mip tail actually used by the needed
// mips. The actually used region may be significantly smaller than the
// full 32x32-texel-aligned tail. A 2x2 texture (for example, in Test
// Drive Unlimited, there's a 2x2 k_8_8_8_8 linear texture with packed
// mips), for instance, would have its 2x2 base at (16, 0) and its 1x1
// mip at (8, 0) - and we need 2 or 1 rows in these cases, not 32.
level_layout.x_extent_blocks = 0;
level_layout.y_extent_blocks = 0;
level_layout.z_extent = 0;
uint32_t packed_sublevel_last = is_base ? 0 : max_level;
for (uint32_t packed_sublevel = layout.packed_level;
packed_sublevel <= packed_sublevel_last; ++packed_sublevel) {
uint32_t packed_sublevel_x_blocks;
uint32_t packed_sublevel_y_blocks;
uint32_t packed_sublevel_z;
GetPackedMipOffset(width_texels, height_texels, depth, format,
packed_sublevel, packed_sublevel_x_blocks,
packed_sublevel_y_blocks, packed_sublevel_z);
level_layout.x_extent_blocks = std::max(
level_layout.x_extent_blocks,
packed_sublevel_x_blocks +
xe::align(
std::max(width_texels >> packed_sublevel, uint32_t(1)),
format_info->block_width) /
format_info->block_width);
level_layout.y_extent_blocks = std::max(
level_layout.y_extent_blocks,
packed_sublevel_y_blocks +
xe::align(
std::max(height_texels >> packed_sublevel, uint32_t(1)),
format_info->block_height) /
format_info->block_height);
level_layout.z_extent =
std::max(level_layout.z_extent,
packed_sublevel_z +
std::max(depth >> packed_sublevel, uint32_t(1)));
}
} else {
level_layout.x_extent_blocks = level_width_blocks;
level_layout.y_extent_blocks = level_height_blocks;
level_layout.z_extent = level_depth;
}
level_layout.array_slice_data_extent_bytes =
z_stride_bytes * (level_layout.z_extent - 1) +
level_layout.row_pitch_bytes * (level_layout.y_extent_blocks - 1) +
bytes_per_block * level_layout.x_extent_blocks;
}
level_layout.level_data_extent_bytes =
level_layout.array_slice_stride_bytes * (layout.array_size - 1) +
level_layout.array_slice_data_extent_bytes;
if (!is_base) {
layout.mip_offsets_bytes[level] = mip_offset_bytes;
layout.mips_total_extent_bytes =
std::max(layout.mips_total_extent_bytes,
mip_offset_bytes + mip_layout.level_data_extent_bytes);
mip_offset_bytes += mip_layout.next_level_distance_bytes();
mip_offset_bytes + level_layout.level_data_extent_bytes);
mip_offset_bytes +=
level_layout.array_slice_stride_bytes * layout.array_size;
}
}