Files
Xenia-Canary/src/xenia/gpu/texture_util.cc
2024-08-10 18:15:16 +02:00

623 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"
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;
}
}
x_blocks >>= FormatInfo::GetWidthShift(format);
y_blocks >>= FormatInfo::GetHeightShift(format);
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;
}
XE_NOINLINE
XE_NOALIAS
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);
}
XE_NOINLINE
XE_NOALIAS
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;
}
XE_NOINLINE
XE_NOALIAS
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;
}
XE_NOINLINE
XE_NOALIAS
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