diff --git a/src/xenia/gpu/d3d12/texture_cache.cc b/src/xenia/gpu/d3d12/texture_cache.cc index cc8fb5d88..e5919466a 100644 --- a/src/xenia/gpu/d3d12/texture_cache.cc +++ b/src/xenia/gpu/d3d12/texture_cache.cc @@ -1587,17 +1587,10 @@ TextureCache::SamplerParameters TextureCache::GetSamplerParameters( parameters.clamp_z = fetch.clamp_z; parameters.border_color = fetch.border_color; - uint32_t mip_min_level = fetch.mip_min_level; - uint32_t mip_max_level = fetch.mip_max_level; - uint32_t base_page = fetch.base_address & 0x1FFFF; - uint32_t mip_page = fetch.mip_address & 0x1FFFF; - if (base_page == 0 || base_page == mip_page) { - // Games should clamp mip level in this case anyway, but just for safety. - mip_min_level = std::max(mip_min_level, 1u); - } - if (mip_page == 0) { - mip_max_level = 0; - } + uint32_t mip_min_level, mip_max_level; + texture_util::GetSubresourcesFromFetchConstant( + fetch, nullptr, nullptr, nullptr, nullptr, nullptr, &mip_min_level, + &mip_max_level, binding.mip_filter); parameters.mip_min_level = mip_min_level; parameters.mip_max_level = std::max(mip_max_level, mip_min_level); parameters.lod_bias = fetch.lod_bias; @@ -1627,7 +1620,6 @@ TextureCache::SamplerParameters TextureCache::GetSamplerParameters( ? fetch.mip_filter : binding.mip_filter; parameters.mip_linear = mip_filter == TextureFilter::kLinear; - // TODO(Triang3l): Investigate mip_filter TextureFilter::kBaseMap. } return parameters; @@ -1649,7 +1641,6 @@ void TextureCache::WriteSampler(SamplerParameters parameters, D3D12_FILTER_TYPE d3d_filter_mip = parameters.mip_linear ? D3D12_FILTER_TYPE_LINEAR : D3D12_FILTER_TYPE_POINT; - // TODO(Triang3l): Investigate mip_filter TextureFilter::kBaseMap. desc.Filter = D3D12_ENCODE_BASIC_FILTER( d3d_filter_min, d3d_filter_mag, d3d_filter_mip, D3D12_FILTER_REDUCTION_TYPE_STANDARD); @@ -2119,76 +2110,31 @@ void TextureCache::BindingInfoFromFetchConstant( return; } - // Validate the dimensions, get the size and clamp the maximum mip level. - Dimension dimension = Dimension(fetch.dimension); - uint32_t width, height, depth; - switch (dimension) { - case Dimension::k1D: - if (fetch.tiled || fetch.stacked || fetch.packed_mips) { - assert_always(); - XELOGGPU( - "1D texture has unsupported properties - ignoring! " - "Report the game to Xenia developers"); - return; - } - width = fetch.size_1d.width + 1; - if (width > 8192) { - assert_always(); - XELOGGPU( - "1D texture is too wide (%u) - ignoring! " - "Report the game to Xenia developers", - width); - } - height = 1; - depth = 1; - break; - case Dimension::k2D: - width = fetch.size_stack.width + 1; - height = fetch.size_stack.height + 1; - depth = fetch.stacked ? fetch.size_stack.depth + 1 : 1; - break; - case Dimension::k3D: - width = fetch.size_3d.width + 1; - height = fetch.size_3d.height + 1; - depth = fetch.size_3d.depth + 1; - break; - case Dimension::kCube: - width = fetch.size_2d.width + 1; - height = fetch.size_2d.height + 1; - depth = 6; - break; - } - uint32_t mip_max_level = texture_util::GetSmallestMipLevel( - width, height, dimension == Dimension::k3D ? depth : 1, false); - mip_max_level = std::min(mip_max_level, fetch.mip_max_level); - - // Normalize and check the addresses. - uint32_t base_page = fetch.base_address & 0x1FFFF; - uint32_t mip_page = mip_max_level != 0 ? fetch.mip_address & 0x1FFFF : 0; - // Special case for streaming. Games such as Banjo-Kazooie: Nuts & Bolts - // specify the same address for both the base level and the mips and set - // mip_min_index to 1 until the texture is actually loaded - this is the way - // recommended by a GPU hang error message found in game executables. In this - // case we assume that the base level is not loaded yet. - // TODO(Triang3l): Ignore the base level completely if min_mip_level is not 0 - // once we start reusing textures with zero base address to reduce memory - // usage. - if (base_page == mip_page) { - base_page = 0; - } + uint32_t width, height, depth_or_faces; + uint32_t base_page, mip_page, mip_max_level; + texture_util::GetSubresourcesFromFetchConstant( + fetch, &width, &height, &depth_or_faces, &base_page, &mip_page, nullptr, + &mip_max_level); if (base_page == 0 && mip_page == 0) { // No texture data at all. return; } + if (fetch.dimension == Dimension::k1D && width > 8192) { + XELOGE( + "1D texture is too wide (%u) - ignoring! " + "Report the game to Xenia developers", + width); + return; + } TextureFormat format = GetBaseFormat(fetch.format); key_out.base_page = base_page; key_out.mip_page = mip_page; - key_out.dimension = dimension; + key_out.dimension = fetch.dimension; key_out.width = width; key_out.height = height; - key_out.depth = depth; + key_out.depth = depth_or_faces; key_out.mip_max_level = mip_max_level; key_out.tiled = fetch.tiled; key_out.packed_mips = fetch.packed_mips; @@ -2278,6 +2224,8 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { uint64_t map_key = key.GetMapKey(); // Try to find an existing texture. + // TODO(Triang3l): Reuse a texture with mip_page unchanged, but base_page + // previously 0, now not 0, to save memory - common case in streaming. auto found_range = textures_.equal_range(map_key); for (auto iter = found_range.first; iter != found_range.second; ++iter) { Texture* found_texture = iter->second; diff --git a/src/xenia/gpu/texture_info.cc b/src/xenia/gpu/texture_info.cc index 6fe0a4daa..9070048e0 100644 --- a/src/xenia/gpu/texture_info.cc +++ b/src/xenia/gpu/texture_info.cc @@ -36,7 +36,7 @@ bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch, info.format = fetch.format; info.endianness = fetch.endianness; - info.dimension = static_cast(fetch.dimension); + info.dimension = fetch.dimension; info.width = info.height = info.depth = 0; info.is_stacked = false; switch (info.dimension) { @@ -46,13 +46,10 @@ bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch, assert_true(!fetch.stacked); break; case Dimension::k2D: - if (!fetch.stacked) { - info.width = fetch.size_2d.width; - info.height = fetch.size_2d.height; - } else { - info.width = fetch.size_stack.width; - info.height = fetch.size_stack.height; - info.depth = fetch.size_stack.depth; + info.width = fetch.size_2d.width; + info.height = fetch.size_2d.height; + if (fetch.stacked) { + info.depth = fetch.size_2d.stack_depth; info.is_stacked = true; } break; @@ -63,9 +60,10 @@ bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch, assert_true(!fetch.stacked); break; case Dimension::kCube: - info.width = fetch.size_stack.width; - info.height = fetch.size_stack.height; - info.depth = fetch.size_stack.depth; + info.width = fetch.size_2d.width; + info.height = fetch.size_2d.height; + assert_true(fetch.size_2d.stack_depth == 5); + info.depth = fetch.size_2d.stack_depth; assert_true(!fetch.stacked); break; default: diff --git a/src/xenia/gpu/texture_util.cc b/src/xenia/gpu/texture_util.cc index c0bc74001..f6b96a883 100644 --- a/src/xenia/gpu/texture_util.cc +++ b/src/xenia/gpu/texture_util.cc @@ -18,12 +18,108 @@ namespace xe { namespace gpu { namespace texture_util { +void GetSubresourcesFromFetchConstant( + const xenos::xe_gpu_texture_fetch_t& fetch, uint32_t* width_out, + uint32_t* height_out, uint32_t* depth_or_faces_out, uint32_t* base_page_out, + uint32_t* mip_page_out, uint32_t* mip_min_level_out, + uint32_t* mip_max_level_out, TextureFilter sampler_mip_filter) { + uint32_t width = 0, height = 0, depth_or_faces = 0; + switch (fetch.dimension) { + case Dimension::k1D: + assert_false(fetch.stacked); + assert_false(fetch.tiled); + assert_false(fetch.packed_mips); + width = fetch.size_1d.width; + break; + case Dimension::k2D: + width = fetch.size_2d.width; + height = fetch.size_2d.height; + depth_or_faces = fetch.stacked ? fetch.size_2d.stack_depth : 0; + break; + case Dimension::k3D: + assert_false(fetch.stacked); + width = fetch.size_3d.width; + height = fetch.size_3d.height; + depth_or_faces = fetch.size_3d.depth; + break; + case Dimension::kCube: + assert_false(fetch.stacked); + assert_true(fetch.size_2d.stack_depth == 5); + width = fetch.size_2d.width; + height = fetch.size_2d.height; + depth_or_faces = 5; + break; + } + ++width; + ++height; + ++depth_or_faces; + if (width_out) { + *width_out = width; + } + if (height_out) { + *height_out = height; + } + if (depth_or_faces_out) { + *depth_or_faces_out = depth_or_faces; + } + + uint32_t size_mip_max_level = GetSmallestMipLevel( + width, height, fetch.dimension == Dimension::k3D ? depth_or_faces : 1, + false); + TextureFilter mip_filter = sampler_mip_filter == TextureFilter::kUseFetchConst + ? fetch.mip_filter + : sampler_mip_filter; + + uint32_t base_page = fetch.base_address & 0x1FFFF; + uint32_t mip_page = fetch.mip_address & 0x1FFFF; + + uint32_t mip_min_level, mip_max_level; + if (mip_filter == TextureFilter::kBaseMap || mip_page == 0) { + mip_min_level = 0; + mip_max_level = 0; + } else { + mip_min_level = std::min(fetch.mip_min_level, size_mip_max_level); + mip_max_level = std::max(std::min(fetch.mip_max_level, size_mip_max_level), + mip_min_level); + } + if (mip_max_level != 0) { + // Special case for streaming. Games such as Banjo-Kazooie: Nuts & Bolts + // specify the same address for both the base level and the mips and set + // mip_min_index to 1 until the texture is actually loaded - this is the way + // recommended by a GPU hang error message found in game executables. In + // this case we assume that the base level is not loaded yet. + if (base_page == mip_page) { + base_page = 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; + } +} + void 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) { @@ -171,10 +267,10 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, void GetTextureTotalSize(Dimension dimension, uint32_t width, uint32_t height, uint32_t depth, TextureFormat format, bool is_tiled, bool packed_mips, uint32_t mip_max_level, - uint32_t* base_size, uint32_t* mip_size) { + uint32_t* base_size_out, uint32_t* mip_size_out) { bool is_3d = dimension == Dimension::k3D; uint32_t width_blocks, height_blocks, depth_blocks; - if (base_size != nullptr) { + if (base_size_out) { GetGuestMipBlocks(dimension, width, height, depth, format, 0, width_blocks, height_blocks, depth_blocks); uint32_t size = GetGuestMipSliceStorageSize( @@ -182,9 +278,9 @@ void GetTextureTotalSize(Dimension dimension, uint32_t width, uint32_t height, if (!is_3d) { size *= depth; } - *base_size = size; + *base_size_out = size; } - if (mip_size != nullptr) { + if (mip_size_out) { mip_max_level = std::min( mip_max_level, GetSmallestMipLevel(width, height, is_3d ? depth : 1, packed_mips)); @@ -199,7 +295,7 @@ void GetTextureTotalSize(Dimension dimension, uint32_t width, uint32_t height, } size += level_size; } - *mip_size = size; + *mip_size_out = size; } } diff --git a/src/xenia/gpu/texture_util.h b/src/xenia/gpu/texture_util.h index 72bfe31f7..ecd99b07b 100644 --- a/src/xenia/gpu/texture_util.h +++ b/src/xenia/gpu/texture_util.h @@ -22,6 +22,16 @@ namespace texture_util { // This namespace replaces texture_extent and most of texture_info for // simplicity. +// Extracts the size from the fetch constant, and also cleans up addresses and +// mip range based on real presence of the base level and mips. Returns 6 faces +// for cube textures. +void GetSubresourcesFromFetchConstant( + const xenos::xe_gpu_texture_fetch_t& fetch, uint32_t* width_out, + uint32_t* height_out, uint32_t* depth_or_faces_out, uint32_t* base_page_out, + uint32_t* mip_page_out, uint32_t* mip_min_level_out, + uint32_t* mip_max_level_out, + TextureFilter sampler_mip_filter = TextureFilter::kUseFetchConst); + // Calculates width, height and depth of the image backing the guest mipmap (or // the base level if mip is 0). void GetGuestMipBlocks(Dimension dimension, uint32_t width, uint32_t height, @@ -71,7 +81,7 @@ inline uint32_t GetSmallestMipLevel(uint32_t width, uint32_t height, void GetTextureTotalSize(Dimension dimension, uint32_t width, uint32_t height, uint32_t depth, TextureFormat format, bool is_tiled, bool packed_mips, uint32_t mip_max_level, - uint32_t* base_size, uint32_t* mip_size); + uint32_t* base_size_out, uint32_t* mip_size_out); int32_t GetTiledOffset2D(int32_t x, int32_t y, uint32_t width, uint32_t bpb_log2); diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index 592a6684a..fa67e3c69 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -112,7 +112,7 @@ enum class TextureSign : uint32_t { enum class TextureFilter : uint32_t { kPoint = 0, kLinear = 1, - kBaseMap = 2, // Only applicable for mip-filter. + kBaseMap = 2, // Only applicable for mip-filter - always fetch from level 0. kUseFetchConst = 3, }; @@ -133,6 +133,8 @@ enum class BorderColor : uint32_t { k_ACBCRY_BLACK = 3, }; +// For the tfetch instruction, not the fetch constant - slightly different +// meaning, as stacked textures are stored as 2D, but fetched using tfetch3D. enum class TextureDimension : uint32_t { k1D = 0, k2D = 1, @@ -661,13 +663,11 @@ XEPACKEDUNION(xe_gpu_texture_fetch_t, { struct { uint32_t width : 13; uint32_t height : 13; - uint32_t : 6; + // Should be 0 for k2D and 5 for kCube if not stacked, but not very + // meaningful in this case, preferably should be ignored for + // non-stacked. + uint32_t stack_depth : 6; } size_2d; - struct { - uint32_t width : 13; - uint32_t height : 13; - uint32_t depth : 6; - } size_stack; struct { uint32_t width : 11; uint32_t height : 11; @@ -700,7 +700,7 @@ XEPACKEDUNION(xe_gpu_texture_fetch_t, { uint32_t force_bc_w_to_max : 1; // +2 uint32_t tri_clamp : 2; // +3 int32_t aniso_bias : 4; // +5 - uint32_t dimension : 2; // +9 + Dimension dimension : 2; // +9 uint32_t packed_mips : 1; // +11 uint32_t mip_address : 20; // +12 mip address >> 12 });