[GPU] Unified function for mip level bounds, BaseMap mip filter, small refactoring
This commit is contained in:
parent
0437488853
commit
47eee5e1c3
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue