Previously, for mips, the dimensions of the texture weren't rounded to powers of two before calculating the mip tail extent, resulting in the mip tail for a 260 blocks tall texture, that contains mips ending at Y of up to 36, having the Y extent calculated as 32. With rounding to powers of two, it would have been 64. However, with the GetTiledAddressUpperBound functions, none of this is necessary at all (and neither is rounding the extents in TextureGuestLayout::Level to 32x32x4 blocks) - using the same code for calculating the XYZ extents of tiled textures as for linear textures now, which, for the mip tail, calculates the actual maximum coordinates of the mips stored in it - and rounding to tiles is done internally by GetTiledAddressUpperBound.
627 lines
25 KiB
C++
627 lines
25 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/texture_util.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/math.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace texture_util {
|
|
|
|
void GetSubresourcesFromFetchConstant(
|
|
const xenos::xe_gpu_texture_fetch_t& fetch, uint32_t* width_minus_1_out,
|
|
uint32_t* height_minus_1_out, uint32_t* depth_or_array_size_minus_1_out,
|
|
uint32_t* base_page_out, uint32_t* mip_page_out,
|
|
uint32_t* mip_min_level_out, uint32_t* mip_max_level_out) {
|
|
uint32_t width_minus_1 = 0;
|
|
uint32_t height_minus_1 = 0;
|
|
uint32_t depth_or_array_size_minus_1 = 0;
|
|
switch (fetch.dimension) {
|
|
case xenos::DataDimension::k1D:
|
|
assert_false(fetch.stacked);
|
|
assert_false(fetch.tiled);
|
|
assert_false(fetch.packed_mips);
|
|
width_minus_1 = fetch.size_1d.width;
|
|
break;
|
|
case xenos::DataDimension::k2DOrStacked:
|
|
width_minus_1 = fetch.size_2d.width;
|
|
height_minus_1 = fetch.size_2d.height;
|
|
depth_or_array_size_minus_1 =
|
|
fetch.stacked ? fetch.size_2d.stack_depth : 0;
|
|
break;
|
|
case xenos::DataDimension::k3D:
|
|
assert_false(fetch.stacked);
|
|
width_minus_1 = fetch.size_3d.width;
|
|
height_minus_1 = fetch.size_3d.height;
|
|
depth_or_array_size_minus_1 = fetch.size_3d.depth;
|
|
break;
|
|
case xenos::DataDimension::kCube:
|
|
assert_false(fetch.stacked);
|
|
assert_true(fetch.size_2d.stack_depth == 5);
|
|
width_minus_1 = fetch.size_2d.width;
|
|
height_minus_1 = fetch.size_2d.height;
|
|
depth_or_array_size_minus_1 = 5;
|
|
break;
|
|
}
|
|
if (width_minus_1_out) {
|
|
*width_minus_1_out = width_minus_1;
|
|
}
|
|
if (height_minus_1_out) {
|
|
*height_minus_1_out = height_minus_1;
|
|
}
|
|
if (depth_or_array_size_minus_1_out) {
|
|
*depth_or_array_size_minus_1_out = depth_or_array_size_minus_1;
|
|
}
|
|
|
|
uint32_t longest_axis_minus_1 = std::max(width_minus_1, height_minus_1);
|
|
if (fetch.dimension == xenos::DataDimension::k3D) {
|
|
longest_axis_minus_1 =
|
|
std::max(longest_axis_minus_1, depth_or_array_size_minus_1);
|
|
}
|
|
uint32_t size_mip_max_level =
|
|
xe::log2_floor(longest_axis_minus_1 + uint32_t(1));
|
|
|
|
uint32_t base_page = fetch.base_address & 0x1FFFF;
|
|
uint32_t mip_page = fetch.mip_address & 0x1FFFF;
|
|
|
|
uint32_t mip_min_level, mip_max_level;
|
|
// Not taking mip_filter == kBaseMap into account for mip_max_level because
|
|
// the mip filter may be overridden by shader fetch instructions.
|
|
if (mip_page == 0) {
|
|
mip_min_level = 0;
|
|
mip_max_level = 0;
|
|
} else {
|
|
mip_min_level = std::min(uint32_t(fetch.mip_min_level), size_mip_max_level);
|
|
mip_max_level =
|
|
std::max(std::min(uint32_t(fetch.mip_max_level), size_mip_max_level),
|
|
mip_min_level);
|
|
}
|
|
if (mip_max_level != 0) {
|
|
if (base_page == 0) {
|
|
mip_min_level = std::max(mip_min_level, uint32_t(1));
|
|
}
|
|
if (mip_min_level != 0) {
|
|
base_page = 0;
|
|
}
|
|
} else {
|
|
mip_page = 0;
|
|
}
|
|
|
|
if (base_page_out) {
|
|
*base_page_out = base_page;
|
|
}
|
|
if (mip_page_out) {
|
|
*mip_page_out = mip_page;
|
|
}
|
|
if (mip_min_level_out) {
|
|
*mip_min_level_out = mip_min_level;
|
|
}
|
|
if (mip_max_level_out) {
|
|
*mip_max_level_out = mip_max_level;
|
|
}
|
|
}
|
|
|
|
bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth,
|
|
xenos::TextureFormat format, uint32_t mip,
|
|
uint32_t& x_blocks, uint32_t& y_blocks,
|
|
uint32_t& z_blocks) {
|
|
// Tile size is 32x32, and once textures go <=16 they are packed into a
|
|
// single tile together. The math here is insane. Most sourced from
|
|
// graph paper, looking at dds dumps and executable reverse engineering.
|
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
// 0 +.4x4.+ +.....8x8.....+ +............16x16............+
|
|
// 1 +.4x4.+ +.....8x8.....+ +............16x16............+
|
|
// 2 +.4x4.+ +.....8x8.....+ +............16x16............+
|
|
// 3 +.4x4.+ +.....8x8.....+ +............16x16............+
|
|
// 4 x +.....8x8.....+ +............16x16............+
|
|
// 5 +.....8x8.....+ +............16x16............+
|
|
// 6 +.....8x8.....+ +............16x16............+
|
|
// 7 +.....8x8.....+ +............16x16............+
|
|
// 8 2x2 +............16x16............+
|
|
// 9 2x2 +............16x16............+
|
|
// 0 +............16x16............+
|
|
// ... .....
|
|
//
|
|
// The 2x2 and 1x1 squares are packed in their specific positions because
|
|
// each square is the size of at least one block (which is 4x4 pixels max)
|
|
//
|
|
// if (tile_aligned(w) > tile_aligned(h)) {
|
|
// // wider than tall, so packed horizontally
|
|
// } else if (tile_aligned(w) < tile_aligned(h)) {
|
|
// // taller than wide, so packed vertically
|
|
// } else {
|
|
// square
|
|
// }
|
|
// It's important to use logical sizes here, as the input sizes will be
|
|
// for the entire packed tile set, not the actual texture.
|
|
// The minimum dimension is what matters most: if either width or height
|
|
// is <= 16 this mode kicks in.
|
|
|
|
uint32_t log2_width = xe::log2_ceil(width);
|
|
uint32_t log2_height = xe::log2_ceil(height);
|
|
uint32_t log2_size = std::min(log2_width, log2_height);
|
|
if (log2_size > 4 + mip) {
|
|
// The shortest dimension is bigger than 16, not packed.
|
|
x_blocks = 0;
|
|
y_blocks = 0;
|
|
z_blocks = 0;
|
|
return false;
|
|
}
|
|
uint32_t packed_mip_base = (log2_size > 4) ? (log2_size - 4) : 0;
|
|
uint32_t packed_mip = mip - packed_mip_base;
|
|
|
|
// Find the block offset of the mip.
|
|
if (packed_mip < 3) {
|
|
if (log2_width > log2_height) {
|
|
// Wider than tall. Laid out vertically.
|
|
x_blocks = 0;
|
|
y_blocks = 16 >> packed_mip;
|
|
} else {
|
|
// Taller than wide. Laid out horizontally.
|
|
x_blocks = 16 >> packed_mip;
|
|
y_blocks = 0;
|
|
}
|
|
z_blocks = 0;
|
|
} else {
|
|
uint32_t offset;
|
|
if (log2_width > log2_height) {
|
|
// Wider than tall. Laid out horizontally.
|
|
offset = (1 << (log2_width - packed_mip_base)) >> (packed_mip - 2);
|
|
x_blocks = offset;
|
|
y_blocks = 0;
|
|
} else {
|
|
// Taller than wide. Laid out vertically.
|
|
x_blocks = 0;
|
|
offset = (1 << (log2_height - packed_mip_base)) >> (packed_mip - 2);
|
|
y_blocks = offset;
|
|
}
|
|
if (offset < 4) {
|
|
// Pack 1x1 Z mipmaps along Z - not reached for 2D.
|
|
uint32_t log2_depth = xe::log2_ceil(depth);
|
|
if (log2_depth > 1 + packed_mip) {
|
|
z_blocks = (log2_depth - packed_mip) * 4;
|
|
} else {
|
|
z_blocks = 4;
|
|
}
|
|
} else {
|
|
z_blocks = 0;
|
|
}
|
|
}
|
|
|
|
const FormatInfo* format_info = FormatInfo::Get(format);
|
|
x_blocks /= format_info->block_width;
|
|
y_blocks /= format_info->block_height;
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
break;
|
|
case xenos::DataDimension::kCube:
|
|
layout.array_size = 6;
|
|
break;
|
|
default:
|
|
layout.array_size = 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 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;
|
|
|
|
layout.packed_level = has_packed_levels
|
|
? GetPackedMipLevel(width_texels, height_texels)
|
|
: UINT32_MAX;
|
|
|
|
// Clear unused level layouts to zero strides/sizes.
|
|
if (!has_base) {
|
|
std::memset(&layout.base, 0, sizeof(layout.base));
|
|
}
|
|
if (layout.packed_level != 0) {
|
|
std::memset(&layout.mips[0], 0, sizeof(layout.mips[0]));
|
|
}
|
|
uint32_t max_stored_level =
|
|
std::min(max_level, uint32_t(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;
|
|
|
|
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::kTextureTileDepth);
|
|
}
|
|
level_layout.array_slice_stride_bytes =
|
|
xe::align(level_layout.array_slice_stride_bytes,
|
|
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 4E4D083E,
|
|
// 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.
|
|
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 (and, for mips, calculated from the base dimensions
|
|
// rounded to powers of two - 58410A7A has an 80x260 tiled texture with
|
|
// packed mips at level 3 containing a mip ending at Y = 36, while
|
|
// 260 >> 3 == 32, but 512 >> 3 == 64) tail. A 2x2 texture (for example,
|
|
// in 494707D4, 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 - the 32 rows
|
|
// in a linear texture (with 256-byte pitch alignment) would span two 4 KB
|
|
// pages rather than one.
|
|
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 =
|
|
xe::align(std::max(width_texels >> level, uint32_t(1)),
|
|
format_info->block_width) /
|
|
format_info->block_width;
|
|
level_layout.y_extent_blocks =
|
|
xe::align(std::max(height_texels >> level, uint32_t(1)),
|
|
format_info->block_height) /
|
|
format_info->block_height;
|
|
level_layout.z_extent = std::max(depth >> level, uint32_t(1));
|
|
}
|
|
if (is_tiled) {
|
|
uint32_t bytes_per_block_log2 = xe::log2_floor(bytes_per_block);
|
|
if (dimension == xenos::DataDimension::k3D) {
|
|
level_layout.array_slice_data_extent_bytes =
|
|
GetTiledAddressUpperBound3D(
|
|
level_layout.x_extent_blocks, level_layout.y_extent_blocks,
|
|
level_layout.z_extent, row_pitch_blocks_tile_aligned,
|
|
level_layout.y_extent_blocks, bytes_per_block_log2);
|
|
} else {
|
|
level_layout.array_slice_data_extent_bytes =
|
|
GetTiledAddressUpperBound2D(
|
|
level_layout.x_extent_blocks, level_layout.y_extent_blocks,
|
|
row_pitch_blocks_tile_aligned, bytes_per_block_log2);
|
|
}
|
|
} else {
|
|
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 + level_layout.level_data_extent_bytes);
|
|
mip_offset_bytes +=
|
|
level_layout.array_slice_stride_bytes * layout.array_size;
|
|
}
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
int32_t GetTiledOffset2D(int32_t x, int32_t y, uint32_t pitch,
|
|
uint32_t bytes_per_block_log2) {
|
|
// https://github.com/gildor2/UModel/blob/de8fbd3bc922427ea056b7340202dcdcc19ccff5/Unreal/UnTexture.cpp#L489
|
|
pitch = xe::align(pitch, xenos::kTextureTileWidthHeight);
|
|
// Top bits of coordinates.
|
|
int32_t macro = ((x >> 5) + (y >> 5) * int32_t(pitch >> 5))
|
|
<< (bytes_per_block_log2 + 7);
|
|
// Lower bits of coordinates (result is 6-bit value).
|
|
int32_t micro = ((x & 7) + ((y & 0xE) << 2)) << bytes_per_block_log2;
|
|
// Mix micro/macro + add few remaining x/y bits.
|
|
int32_t offset =
|
|
macro + ((micro & ~0xF) << 1) + (micro & 0xF) + ((y & 1) << 4);
|
|
// Mix bits again.
|
|
return ((offset & ~0x1FF) << 3) + ((y & 16) << 7) + ((offset & 0x1C0) << 2) +
|
|
(((((y & 8) >> 2) + (x >> 3)) & 3) << 6) + (offset & 0x3F);
|
|
}
|
|
|
|
int32_t GetTiledOffset3D(int32_t x, int32_t y, int32_t z, uint32_t pitch,
|
|
uint32_t height, uint32_t bytes_per_block_log2) {
|
|
// Reconstructed from disassembly of XGRAPHICS::TileVolume.
|
|
pitch = xe::align(pitch, xenos::kTextureTileWidthHeight);
|
|
height = xe::align(height, xenos::kTextureTileWidthHeight);
|
|
int32_t macro_outer =
|
|
((y >> 4) + (z >> 2) * int32_t(height >> 4)) * int32_t(pitch >> 5);
|
|
int32_t macro =
|
|
((((x >> 5) + macro_outer) << (bytes_per_block_log2 + 6)) & 0xFFFFFFF)
|
|
<< 1;
|
|
int32_t micro =
|
|
(((x & 7) + ((y & 6) << 2)) << (bytes_per_block_log2 + 6)) >> 6;
|
|
int32_t offset_outer = ((y >> 3) + (z >> 2)) & 1;
|
|
int32_t offset1 =
|
|
offset_outer + ((((x >> 3) + (offset_outer << 1)) & 3) << 1);
|
|
int32_t offset2 = ((macro + (micro & ~15)) << 1) + (micro & 15) +
|
|
((z & 3) << (bytes_per_block_log2 + 6)) + ((y & 1) << 4);
|
|
int32_t address = (offset1 & 1) << 3;
|
|
address += (offset2 >> 6) & 7;
|
|
address <<= 3;
|
|
address += offset1 & ~1;
|
|
address <<= 2;
|
|
address += offset2 & ~511;
|
|
address <<= 3;
|
|
address += offset2 & 63;
|
|
return address;
|
|
}
|
|
|
|
uint32_t GetTiledAddressUpperBound2D(uint32_t right, uint32_t bottom,
|
|
uint32_t pitch,
|
|
uint32_t bytes_per_block_log2) {
|
|
if (!right || !bottom) {
|
|
return 0;
|
|
}
|
|
// Get the origin of the 32x32 tile containing the last texel.
|
|
uint32_t upper_bound = uint32_t(GetTiledOffset2D(
|
|
int32_t((right - 1) & ~(xenos::kTextureTileWidthHeight - 1)),
|
|
int32_t((bottom - 1) & ~(xenos::kTextureTileWidthHeight - 1)), pitch,
|
|
bytes_per_block_log2));
|
|
switch (bytes_per_block_log2) {
|
|
case 0:
|
|
// Independent addressing within 128x128 portions, but the extent is 0xA00
|
|
// bytes from the 32x32 tile origin.
|
|
upper_bound += 0xA00;
|
|
break;
|
|
case 1:
|
|
// Independent addressing within 64x64 portions, but the extent is 0xC00
|
|
// bytes from the 32x32 tile origin.
|
|
upper_bound += 0xC00;
|
|
break;
|
|
default:
|
|
upper_bound += UINT32_C(0x400) << bytes_per_block_log2;
|
|
break;
|
|
}
|
|
return upper_bound;
|
|
}
|
|
|
|
uint32_t GetTiledAddressUpperBound3D(uint32_t right, uint32_t bottom,
|
|
uint32_t back, uint32_t pitch,
|
|
uint32_t height,
|
|
uint32_t bytes_per_block_log2) {
|
|
if (!right || !bottom || !back) {
|
|
return 0;
|
|
}
|
|
// Get the origin of the 32x32x4 tile containing the last texel.
|
|
uint32_t upper_bound = uint32_t(GetTiledOffset3D(
|
|
int32_t((right - 1) & ~(xenos::kTextureTileWidthHeight - 1)),
|
|
int32_t((bottom - 1) & ~(xenos::kTextureTileWidthHeight - 1)),
|
|
int32_t((back - 1) & ~(xenos::kTextureTileDepth - 1)), pitch, height,
|
|
bytes_per_block_log2));
|
|
uint32_t pitch_aligned = xe::align(pitch, xenos::kTextureTileWidthHeight);
|
|
switch (bytes_per_block_log2) {
|
|
case 0:
|
|
// 64x32x8 portions have independent addressing.
|
|
// Extent relative to the 32x32x4 tile origin:
|
|
// - Pitch = 32, 96, 160...: (Pitch / 64) * 0x1000 + 0x1000
|
|
// - Pitch = 64, 128, 192...: (Pitch / 64) * 0x1000 + 0xC00
|
|
upper_bound += ((pitch_aligned >> 6) << 12) + 0xC00 +
|
|
((pitch_aligned & (1 << 5)) << (10 - 5));
|
|
break;
|
|
default:
|
|
// 32x32x8 portions have independent addressing.
|
|
// Extent: ((Pitch / 32) * 0x1000 + 0x1000) * (BPB / 2)
|
|
// Or: ((Pitch / 32) * 0x1000 / 2 + 0x1000 / 2) * BPB
|
|
upper_bound += ((pitch_aligned << (12 - 5 - 1)) + (0x1000 >> 1))
|
|
<< bytes_per_block_log2;
|
|
break;
|
|
}
|
|
return upper_bound;
|
|
}
|
|
|
|
uint8_t SwizzleSigns(const xenos::xe_gpu_texture_fetch_t& fetch) {
|
|
uint8_t signs = 0;
|
|
bool any_not_signed = false, any_signed = false;
|
|
// 0b00 or 0b01 for each component, whether it's constant 0/1.
|
|
uint8_t constant_mask = 0b00000000;
|
|
for (uint32_t i = 0; i < 4; ++i) {
|
|
uint32_t swizzle = (fetch.swizzle >> (i * 3)) & 0b111;
|
|
if (swizzle & 0b100) {
|
|
constant_mask |= uint8_t(1) << (i * 2);
|
|
} else {
|
|
xenos::TextureSign sign =
|
|
xenos::TextureSign((fetch.dword_0 >> (2 + swizzle * 2)) & 0b11);
|
|
signs |= uint8_t(sign) << (i * 2);
|
|
if (sign == xenos::TextureSign::kSigned) {
|
|
any_signed = true;
|
|
} else {
|
|
any_not_signed = true;
|
|
}
|
|
}
|
|
}
|
|
xenos::TextureSign constants_sign = xenos::TextureSign::kUnsigned;
|
|
if (constant_mask == 0b01010101) {
|
|
// If only constant components, choose according to the original format
|
|
// (what would more likely be loaded if there were non-constant components).
|
|
// If all components would be signed, use signed.
|
|
// Textures with only constant components must still be bound to shaders for
|
|
// various queries (such as filtering weights) not involving the color data
|
|
// itself.
|
|
if (((fetch.dword_0 >> 2) & 0b11111111) ==
|
|
uint32_t(xenos::TextureSign::kSigned) * 0b01010101) {
|
|
constants_sign = xenos::TextureSign::kSigned;
|
|
}
|
|
} else {
|
|
// If only signed and constant components, reading just from the signed host
|
|
// view is enough.
|
|
if (any_signed && !any_not_signed) {
|
|
constants_sign = xenos::TextureSign::kSigned;
|
|
}
|
|
}
|
|
signs |= uint8_t(constants_sign) * constant_mask;
|
|
return signs;
|
|
}
|
|
|
|
void GetClampModesForDimension(const xenos::xe_gpu_texture_fetch_t& fetch,
|
|
xenos::ClampMode& clamp_x_out,
|
|
xenos::ClampMode& clamp_y_out,
|
|
xenos::ClampMode& clamp_z_out) {
|
|
clamp_x_out = xenos::ClampMode::kClampToEdge;
|
|
clamp_y_out = xenos::ClampMode::kClampToEdge;
|
|
clamp_z_out = xenos::ClampMode::kClampToEdge;
|
|
switch (fetch.dimension) {
|
|
case xenos::DataDimension::k3D:
|
|
clamp_z_out = fetch.clamp_z;
|
|
[[fallthrough]];
|
|
case xenos::DataDimension::k2DOrStacked:
|
|
clamp_y_out = fetch.clamp_y;
|
|
[[fallthrough]];
|
|
case xenos::DataDimension::k1D:
|
|
clamp_x_out = fetch.clamp_x;
|
|
break;
|
|
default:
|
|
// Not applicable to cube textures.
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace texture_util
|
|
} // namespace gpu
|
|
} // namespace xe
|