diff --git a/src/xenia/gpu/d3d12/texture_cache.h b/src/xenia/gpu/d3d12/texture_cache.h index 2013d0837..006d69b2f 100644 --- a/src/xenia/gpu/d3d12/texture_cache.h +++ b/src/xenia/gpu/d3d12/texture_cache.h @@ -32,13 +32,16 @@ class D3D12CommandProcessor; // - If the texture has a base address, but no mip address, it's not mipmapped - // the host texture has only the largest level too. // - If the texture has different non-zero base address and mip address, a host -// texture full mipmap pyramid is created, disregarding min/max LOD and -// treating it purely as sampler state because there are tfetch instructions +// texture with mip_max_level+1 mipmaps is created - mip_min_level is ignored +// and treated purely as sampler state because there are tfetch instructions // working directly with LOD values - including fetching with an explicit LOD. +// However, the max level is not ignored because any mip count can be +// specified when creating a texture, and another texture may be placed after +// the last one. // - If the texture has a mip address, but the base address is 0 or the same as -// the mip address, a fully mipmapped texture is created, but min/max LOD is -// clamped to 1 - the game is expected to do that anyway until the largest LOD -// is loaded. +// the mip address, a mipmapped texture is created, but min/max LOD is clamped +// to the lower bound of 1 - the game is expected to do that anyway until the +// largest LOD is loaded. // TODO(Triang3l): Check if there are any games with BaseAddress==MipAddress // but min or max LOD being 0, especially check Modern Warfare 2/3. // TODO(Triang3l): Attach the largest LOD to existing textures with a valid diff --git a/src/xenia/gpu/texture_info_formats.cc b/src/xenia/gpu/texture_info_formats.cc index 700a342e5..0440cf603 100644 --- a/src/xenia/gpu/texture_info_formats.cc +++ b/src/xenia/gpu/texture_info_formats.cc @@ -33,7 +33,7 @@ const FormatInfo* FormatInfo::Get(uint32_t gpu_format) { FORMAT_INFO(k_8_8 , kUncompressed, 1, 1, 16), FORMAT_INFO(k_Cr_Y1_Cb_Y0 , kCompressed , 2, 1, 16), FORMAT_INFO(k_Y1_Cr_Y0_Cb , kCompressed , 2, 1, 16), - FORMAT_INFO(kUnknown , kUncompressed, 0, 0, 0), // k_Shadow + FORMAT_INFO(kUnknown , kUncompressed, 1, 1, 32), // k_Shadow FORMAT_INFO(k_8_8_8_8_A , kUncompressed, 1, 1, 32), FORMAT_INFO(k_4_4_4_4 , kUncompressed, 1, 1, 16), FORMAT_INFO(k_10_11_11 , kUncompressed, 1, 1, 32), @@ -41,7 +41,7 @@ const FormatInfo* FormatInfo::Get(uint32_t gpu_format) { FORMAT_INFO(k_DXT1 , kCompressed , 4, 4, 4), FORMAT_INFO(k_DXT2_3 , kCompressed , 4, 4, 8), FORMAT_INFO(k_DXT4_5 , kCompressed , 4, 4, 8), - FORMAT_INFO(kUnknown , kUncompressed, 0, 0, 0), // k_DXV + FORMAT_INFO(kUnknown , kUncompressed, 1, 1, 64), // k_DXV FORMAT_INFO(k_24_8 , kUncompressed, 1, 1, 32), FORMAT_INFO(k_24_8_FLOAT , kUncompressed, 1, 1, 32), FORMAT_INFO(k_16 , kUncompressed, 1, 1, 16), @@ -82,8 +82,8 @@ const FormatInfo* FormatInfo::Get(uint32_t gpu_format) { FORMAT_INFO(k_DXT5A , kCompressed , 4, 4, 4), FORMAT_INFO(k_CTX1 , kCompressed , 4, 4, 4), FORMAT_INFO(k_DXT3A_AS_1_1_1_1 , kCompressed , 4, 4, 4), - FORMAT_INFO(kUnknown , kUncompressed, 0, 0, 0), // k_2_10_10_10_FLOAT - FORMAT_INFO(kUnknown , kUncompressed, 0, 0, 0), // invalid + FORMAT_INFO(kUnknown , kUncompressed, 1, 1, 32), // k_2_10_10_10_FLOAT + FORMAT_INFO(kUnknown , kUncompressed, 1, 1, 32), // invalid }; return &format_infos[gpu_format]; } diff --git a/src/xenia/gpu/texture_util.cc b/src/xenia/gpu/texture_util.cc new file mode 100644 index 000000000..1fa9ce253 --- /dev/null +++ b/src/xenia/gpu/texture_util.cc @@ -0,0 +1,168 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2018 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 + +#include "xenia/base/assert.h" +#include "xenia/base/math.h" + +namespace xe { +namespace gpu { +namespace texture_util { + +bool GetGuestMipBlocks(Dimension dimension, uint32_t width, uint32_t height, + uint32_t depth, TextureFormat format, uint32_t mip, + uint32_t& width_blocks_out, uint32_t& height_blocks_out, + uint32_t& depth_blocks_out) { + // Get mipmap size. + // TODO(Triang3l): Verify if mipmap storage actually needs to be power of two. + if (mip != 0) { + width = std::max(xe::next_pow2(width) >> mip, 1u); + if (dimension != Dimension::k1D) { + height = std::max(xe::next_pow2(height) >> mip, 1u); + if (dimension == Dimension::k3D) { + depth = std::max(xe::next_pow2(depth) >> mip, 1u); + } + } + } + + // Get the size in blocks rather than in pixels. + const FormatInfo* format_info = FormatInfo::Get(format); + width = xe::align(width, format_info->block_width) / format_info->block_width; + height = + xe::align(height, format_info->block_height) / format_info->block_height; + + // 32x32x4-align. + width_blocks_out = xe::align(width, 32u); + if (dimension != Dimension::k1D) { + height_blocks_out = xe::align(height, 32u); + if (dimension == Dimension::k3D) { + depth_blocks_out = xe::align(depth, 4u); + } else { + depth_blocks_out = 1; + } + } else { + height_blocks_out = 1; + } + return true; +} + +uint32_t GetGuestMipStorageSize(uint32_t width_blocks, uint32_t height_blocks, + uint32_t depth_blocks, bool is_tiled, + TextureFormat format, uint32_t* row_pitch_out) { + const FormatInfo* format_info = FormatInfo::Get(format); + uint32_t row_pitch = width_blocks * format_info->block_width * + format_info->block_height * 8 / + format_info->bits_per_pixel; + if (!is_tiled) { + row_pitch = xe::align(row_pitch, 256u); + } + if (row_pitch_out != nullptr) { + *row_pitch_out = row_pitch; + } + return xe::align(row_pitch * height_blocks * depth_blocks, 4096u); +} + +bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, + 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; +} + +} // namespace texture_util +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/texture_util.h b/src/xenia/gpu/texture_util.h new file mode 100644 index 000000000..d852430d8 --- /dev/null +++ b/src/xenia/gpu/texture_util.h @@ -0,0 +1,67 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2018 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_TEXTURE_UTIL_H_ +#define XENIA_GPU_TEXTURE_UTIL_H_ + +#include + +#include "xenia/base/math.h" +#include "xenia/gpu/texture_info.h" + +namespace xe { +namespace gpu { +namespace texture_util { + +// This namespace replaces texture_extent and most of texture_info for +// simplicity. + +// Calculates width, height and depth of the image backing the guest mipmap (or +// the base level if mip is 0). +bool GetGuestMipBlocks(Dimension dimension, uint32_t width, uint32_t height, + uint32_t depth, TextureFormat format, uint32_t mip, + uint32_t& width_blocks_out, uint32_t& height_blocks_out, + uint32_t& depth_blocks_out); + +// Calculates the number of bytes required to store a single mip level - width, +// height and depth must be obtained via GetGuestMipExtent. +uint32_t GetGuestMipStorageSize(uint32_t width_blocks, uint32_t height_blocks, + uint32_t depth_blocks, bool is_tiled, + TextureFormat format, uint32_t* row_pitch_out); + +// Gets the number of the mipmap level where the packed mips are stored. +inline uint32_t GetPackedMipLevel(uint32_t width, uint32_t height) { + uint32_t log2_size = xe::log2_ceil(std::min(width, height)); + return log2_size > 4 ? log2_size - 4 : 0; +} + +// Gets the offset of the mipmap within the tail in blocks, or zeros (and +// returns false) if the mip level is not packed. Width, height and depth are in +// texels. For non-3D textures, set depth to 1. +bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, + TextureFormat format, uint32_t mip, uint32_t& x_blocks, + uint32_t& y_blocks, uint32_t& z_blocks); + +// Calculates the maximum mipmap count for a texture. +inline uint32_t GetMaximumMipCount(uint32_t width, uint32_t height, + uint32_t depth, bool has_packed_mips) { + uint32_t size = std::max(width, height); + size = std::max(size, depth); + uint32_t smallest_mip = xe::log2_ceil(size); + if (has_packed_mips) { + smallest_mip = std::min(smallest_mip, GetPackedMipLevel(width, height)); + } + return smallest_mip + 1; +} + +} // namespace texture_util +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_TEXTURE_UTIL_H_