[GPU] Unified function for mip level bounds, BaseMap mip filter, small refactoring

This commit is contained in:
Triang3l 2020-02-18 23:31:15 +03:00
parent 0437488853
commit 47eee5e1c3
5 changed files with 150 additions and 98 deletions

View File

@ -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;

View File

@ -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<Dimension>(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:

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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
});