rpcsx/rpcs3/Emu/RSX/Common/texture_cache_helpers.h
kd-11 a936e43ff6 rsx: Fixup for slice gathering for structures with multiple mipmap levels
- TODO: Proper multi-level assembly for non-2D structures
2019-10-17 18:18:00 +03:00

667 lines
20 KiB
C++

#pragma once
#include "../rsx_utils.h"
#include "TextureUtils.h"
namespace rsx
{
using texture_channel_remap_t = std::pair<std::array<u8, 4>, std::array<u8, 4>>;
// Defines pixel operation to be performed on a surface before it is ready for use
enum surface_transform : u32
{
identity = 0, // Nothing
argb_to_bgra = 1, // Swap ARGB to BGRA (endian swap)
coordinate_transform = 2 // Incoming source coordinates may generated based on the format of the secondary (dest) surface. Recalculate them before use.
};
template<typename image_resource_type>
struct copy_region_descriptor_base
{
image_resource_type src;
flags32_t xform;
u8 level;
u16 src_x;
u16 src_y;
u16 dst_x;
u16 dst_y;
u16 dst_z;
u16 src_w;
u16 src_h;
u16 dst_w;
u16 dst_h;
};
// Deferred texture processing commands
enum class deferred_request_command : u32
{
nop = 0, // Nothing
copy_image_static, // Copy image and cache the results
copy_image_dynamic, // Copy image but do not cache the results
cubemap_gather, // Provided list of sections generates a cubemap
cubemap_unwrap, // One large texture provided to be partitioned into a cubemap
atlas_gather, // Provided list of sections generates a texture atlas
_3d_gather, // Provided list of sections generates a 3D array
_3d_unwrap, // One large texture provided to be partitioned into a 3D array
mipmap_gather // Provided list of sections to be reassembled as mipmap levels of the same texture
};
struct image_section_attributes_t
{
u32 address;
u32 gcm_format;
u16 width;
u16 height;
u16 depth;
u16 pitch;
u16 slice_h;
u8 bpp;
};
struct blit_op_result
{
bool succeeded = false;
bool is_depth = false;
u32 real_dst_address = 0;
u32 real_dst_size = 0;
blit_op_result(bool success) : succeeded(success)
{}
inline address_range to_address_range() const
{
return address_range::start_length(real_dst_address, real_dst_size);
}
};
struct texture_cache_search_options
{
u8 lookup_mask = 0xff;
bool is_compressed_format = false;
bool skip_texture_barriers = false;
bool skip_texture_merge = false;
bool prefer_surface_cache = false;
};
namespace texture_cache_helpers
{
static inline bool is_gcm_depth_format(u32 format)
{
switch (format)
{
case CELL_GCM_TEXTURE_DEPTH16:
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
case CELL_GCM_TEXTURE_DEPTH24_D8:
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
return true;
default:
return false;
}
}
static inline u32 get_compatible_depth_format(u32 gcm_format)
{
switch (gcm_format)
{
case CELL_GCM_TEXTURE_DEPTH24_D8:
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
case CELL_GCM_TEXTURE_A8R8G8B8:
return CELL_GCM_TEXTURE_DEPTH24_D8;
case CELL_GCM_TEXTURE_DEPTH16:
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
case CELL_GCM_TEXTURE_X16:
//case CELL_GCM_TEXTURE_A4R4G4B4:
//case CELL_GCM_TEXTURE_G8B8:
//case CELL_GCM_TEXTURE_A1R5G5B5:
//case CELL_GCM_TEXTURE_R5G5B5A1:
//case CELL_GCM_TEXTURE_R5G6B5:
//case CELL_GCM_TEXTURE_R6G5B5:
return CELL_GCM_TEXTURE_DEPTH16;
}
LOG_ERROR(RSX, "Unsupported depth conversion (0x%X)", gcm_format);
return gcm_format;
}
static inline bool is_compressed_gcm_format(u32 format)
{
switch (format)
{
default:
return false;
case CELL_GCM_TEXTURE_COMPRESSED_DXT1:
case CELL_GCM_TEXTURE_COMPRESSED_DXT23:
case CELL_GCM_TEXTURE_COMPRESSED_DXT45:
case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8:
case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8:
case CELL_GCM_TEXTURE_COMPRESSED_HILO8:
case CELL_GCM_TEXTURE_COMPRESSED_HILO_S8:
return true;
}
}
static inline format_type get_format_class(u32 gcm_format)
{
switch (gcm_format)
{
default:
return format_type::color;
case CELL_GCM_TEXTURE_DEPTH16:
case CELL_GCM_TEXTURE_DEPTH24_D8:
return format_type::depth_uint;
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
return format_type::depth_float;
}
}
template<typename section_storage_type, typename copy_region_type, typename surface_store_list_type>
void gather_texture_slices(
std::vector<copy_region_type>& out,
const surface_store_list_type& fbos,
const std::vector<section_storage_type*>& local,
const image_section_attributes_t& attr,
u16 count, bool is_depth)
{
// Need to preserve sorting order
struct sort_helper
{
u64 tag; // Timestamp
u32 list; // List source, 0 = fbo, 1 = local
u32 index; // Index in list
};
std::vector<sort_helper> sort_list;
if (!fbos.empty() && !local.empty())
{
// Generate sorting tree if both resources are available and overlapping
sort_list.reserve(fbos.size() + local.size());
for (u32 index = 0; index < fbos.size(); ++index)
{
sort_list.push_back({ fbos[index].surface->last_use_tag, 0, index });
}
for (u32 index = 0; index < local.size(); ++index)
{
if (local[index]->get_context() != rsx::texture_upload_context::blit_engine_dst)
continue;
sort_list.push_back({ local[index]->last_write_tag, 1, index });
}
std::sort(sort_list.begin(), sort_list.end(), [](const auto& a, const auto& b)
{
return (a.tag < b.tag);
});
}
auto add_rtt_resource = [&](auto& section, u16 slice)
{
if (section.is_depth != is_depth)
{
// TODO
return;
}
const u32 slice_begin = (slice * attr.slice_h);
const u32 slice_end = (slice_begin + attr.height);
const u32 section_end = section.dst_area.y + section.dst_area.height;
if (section.dst_area.y >= slice_end || section_end <= slice_begin)
{
// Belongs to a different slice
return;
}
// How much of this slice to read?
int rebased = int(section.dst_area.y) - slice_begin;
const auto src_x = section.src_area.x;
const auto dst_x = section.dst_area.x;
auto src_y = section.src_area.y;
auto dst_y = section.dst_area.y;
if (rebased < 0)
{
const u16 delta = u16(-rebased);
src_y += delta;
dst_y += delta;
}
verify(HERE), dst_y >= slice_begin;
dst_y = (dst_y - slice_begin);
const auto h = std::min(section_end, slice_end) - section.dst_area.y;
const auto src_width = rsx::apply_resolution_scale(section.src_area.width, true);
const auto src_height = rsx::apply_resolution_scale(h, true);
const auto dst_width = rsx::apply_resolution_scale(section.dst_area.width, true);
const auto dst_height = src_height;
out.push_back
({
section.surface->get_surface(rsx::surface_access::read),
surface_transform::identity,
0,
rsx::apply_resolution_scale(src_x, true),
rsx::apply_resolution_scale(src_y, true),
rsx::apply_resolution_scale(dst_x, true),
rsx::apply_resolution_scale(dst_y, true),
slice,
src_width, src_height,
dst_width, dst_height
});
};
auto add_local_resource = [&](auto& section, u32 address, u16 slice, bool scaling = true)
{
if (section->is_depth_texture() != is_depth)
{
// TODO
return;
}
// Intersect this resource with the original one
const auto section_bpp = get_format_block_size_in_bytes(section->get_gcm_format());
const auto normalized_width = (section->get_width() * section_bpp) / attr.bpp;
const auto clipped = rsx::intersect_region(
section->get_section_base(), normalized_width, section->get_height(), section_bpp, /* parent region (extractee) */
address, attr.width, attr.slice_h, attr.bpp, /* child region (extracted) */
attr.pitch);
// Rect intersection test
// TODO: Make the intersection code cleaner with proper 2D regions
if (std::get<0>(clipped).x >= section->get_width())
{
// Overlap lies outside the image area!
return;
}
const auto slice_begin = u32(slice * attr.slice_h);
const auto slice_end = u32(slice_begin + attr.height);
const auto dst_y = std::get<1>(clipped).y;
const auto dst_h = std::get<2>(clipped).height;
const auto section_end = dst_y + dst_h;
if (dst_y >= slice_end || section_end <= slice_begin)
{
// Belongs to a different slice
return;
}
const u16 dst_w = (u16)std::get<2>(clipped).width;
const u16 src_w = u16(dst_w * attr.bpp) / section_bpp;
const u16 height = (u16)std::get<2>(clipped).height;
if (scaling)
{
// Since output is upscaled, also upscale on dst
out.push_back
({
section->get_raw_texture(),
surface_transform::identity,
0,
(u16)std::get<0>(clipped).x,
(u16)std::get<0>(clipped).y,
rsx::apply_resolution_scale((u16)std::get<1>(clipped).x, true),
rsx::apply_resolution_scale((u16)std::get<1>(clipped).y, true),
slice,
src_w,
height,
rsx::apply_resolution_scale(dst_w, true),
rsx::apply_resolution_scale(height, true),
});
}
else
{
out.push_back
({
section->get_raw_texture(),
surface_transform::identity,
0,
(u16)std::get<0>(clipped).x,
(u16)std::get<0>(clipped).y,
(u16)std::get<1>(clipped).x,
(u16)std::get<1>(clipped).y,
0,
src_w,
height,
dst_w,
height,
});
}
};
u32 current_address = attr.address;
u16 current_src_offset = 0;
u16 current_dst_offset = 0;
u32 slice_size = (attr.pitch * attr.slice_h);
out.reserve(count);
u16 found_slices = 0;
for (u16 slice = 0; slice < count; ++slice)
{
auto num_surface = out.size();
if (LIKELY(local.empty()))
{
for (auto& section : fbos)
{
add_rtt_resource(section, slice);
}
}
else if (fbos.empty())
{
for (auto& section : local)
{
add_local_resource(section, current_address, slice, false);
}
}
else
{
for (const auto& e : sort_list)
{
if (e.list == 0)
{
add_rtt_resource(fbos[e.index], slice);
}
else
{
add_local_resource(local[e.index], current_address, slice);
}
}
}
current_address += slice_size;
if (out.size() != num_surface)
{
found_slices++;
}
}
if (found_slices < count)
{
if (found_slices > 0)
{
//TODO: Gather remaining sides from the texture cache or upload from cpu (too slow?)
LOG_ERROR(RSX, "Could not gather all required slices for cubemap/3d generation");
}
else
{
LOG_WARNING(RSX, "Could not gather textures into an atlas; using CPU fallback...");
}
}
}
template<typename render_target_type>
bool check_framebuffer_resource(
render_target_type texptr,
const image_section_attributes_t& attr,
texture_dimension_extended extended_dimension)
{
if (!rsx::pitch_compatible(texptr, attr.pitch, attr.height))
{
return false;
}
const auto surface_width = texptr->get_surface_width(rsx::surface_metrics::samples);
const auto surface_height = texptr->get_surface_height(rsx::surface_metrics::samples);
switch (extended_dimension)
{
case rsx::texture_dimension_extended::texture_dimension_1d:
return (surface_width >= attr.width);
case rsx::texture_dimension_extended::texture_dimension_2d:
return (surface_width >= attr.width && surface_height >= attr.height);
case rsx::texture_dimension_extended::texture_dimension_3d:
return (surface_width >= attr.width && surface_height >= (attr.slice_h * attr.depth));
case rsx::texture_dimension_extended::texture_dimension_cubemap:
return (surface_width == attr.height && surface_width >= attr.width && surface_height >= (attr.slice_h * 6));
}
return false;
}
template <typename sampled_image_descriptor, typename commandbuffer_type, typename render_target_type>
sampled_image_descriptor process_framebuffer_resource_fast(commandbuffer_type& cmd,
render_target_type texptr,
const image_section_attributes_t& attr,
const size2f& scale,
texture_dimension_extended extended_dimension,
u32 encoded_remap, const texture_channel_remap_t& decoded_remap,
bool surface_is_rop_target,
bool force_convert)
{
texptr->read_barrier(cmd);
const auto surface_width = texptr->get_surface_width(rsx::surface_metrics::samples);
const auto surface_height = texptr->get_surface_height(rsx::surface_metrics::samples);
bool is_depth = texptr->is_depth_surface();
auto attr2 = attr;
if (const bool gcm_format_is_depth = is_gcm_depth_format(attr2.gcm_format);
gcm_format_is_depth != is_depth)
{
if (force_convert)
{
is_depth = gcm_format_is_depth;
}
else
{
attr2.gcm_format = get_compatible_depth_format(attr2.gcm_format);
}
// Always make sure the conflict is resolved!
verify(HERE), is_gcm_depth_format(attr2.gcm_format) == is_depth;
}
if (LIKELY(extended_dimension == rsx::texture_dimension_extended::texture_dimension_2d ||
extended_dimension == rsx::texture_dimension_extended::texture_dimension_1d))
{
if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_1d)
{
verify(HERE), attr.height == 1;
}
if ((surface_is_rop_target && g_cfg.video.strict_rendering_mode) ||
attr2.width < surface_width ||
attr2.height < surface_height ||
force_convert)
{
const auto scaled_w = rsx::apply_resolution_scale(attr2.width, true);
const auto scaled_h = rsx::apply_resolution_scale(attr2.height, true);
const auto format_class = (force_convert) ? get_format_class(attr2.gcm_format) : texptr->get_format_type();
const auto command = surface_is_rop_target ? deferred_request_command::copy_image_dynamic : deferred_request_command::copy_image_static;
attr2.width = scaled_w;
attr2.height = scaled_h;
return { texptr->get_surface(rsx::surface_access::read), command, attr2, {},
texture_upload_context::framebuffer_storage, format_class, scale,
extended_dimension, decoded_remap };
}
return{ texptr->get_view(encoded_remap, decoded_remap), texture_upload_context::framebuffer_storage,
texptr->get_format_type(), scale, rsx::texture_dimension_extended::texture_dimension_2d, surface_is_rop_target };
}
const auto scaled_w = rsx::apply_resolution_scale(attr2.width, true);
const auto scaled_h = rsx::apply_resolution_scale(attr2.height, true);
if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d)
{
return{ texptr->get_surface(rsx::surface_access::read), deferred_request_command::_3d_unwrap,
attr2, {},
texture_upload_context::framebuffer_storage, texptr->get_format_type(), scale,
rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap };
}
verify(HERE), extended_dimension == rsx::texture_dimension_extended::texture_dimension_cubemap;
attr2.width = scaled_w;
attr2.height = scaled_h;
return{ texptr->get_surface(rsx::surface_access::read), deferred_request_command::cubemap_unwrap,
attr2, {},
texture_upload_context::framebuffer_storage, texptr->get_format_type(), scale,
rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap };
}
template <typename sampled_image_descriptor, typename surface_store_list_type, typename section_storage_type>
sampled_image_descriptor merge_cache_resources(
const surface_store_list_type& fbos, const std::vector<section_storage_type*>& local,
const image_section_attributes_t& attr,
const size2f& scale,
texture_dimension_extended extended_dimension,
u32 encoded_remap, const texture_channel_remap_t& decoded_remap,
int select_hint = -1)
{
verify(HERE), (select_hint & 0x1) == select_hint;
bool is_depth;
auto attr2 = attr;
if (is_depth = (select_hint == 0) ? fbos.back().is_depth : local.back()->is_depth_texture();
is_depth)
{
if (const auto suggested_format = get_compatible_depth_format(attr.gcm_format);
!is_gcm_depth_format(suggested_format))
{
// Failed!
is_depth = false;
}
else
{
attr2.gcm_format = suggested_format;
}
}
// If this method was called, there is no easy solution, likely means atlas gather is needed
auto scaled_w = rsx::apply_resolution_scale(attr2.width, true);
auto scaled_h = rsx::apply_resolution_scale(attr2.height, true);
const auto format_class = get_format_class(attr2.gcm_format);
if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_cubemap)
{
attr2.width = scaled_w;
attr2.height = scaled_h;
attr2.depth = 1;
sampled_image_descriptor desc = { nullptr, deferred_request_command::cubemap_gather,
attr2, {},
texture_upload_context::framebuffer_storage, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap };
gather_texture_slices(desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth);
return desc;
}
else if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d && attr.depth > 1)
{
attr2.width = scaled_w;
attr2.height = scaled_h;
sampled_image_descriptor desc = { nullptr, deferred_request_command::_3d_gather,
attr2, {},
texture_upload_context::framebuffer_storage, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap };
gather_texture_slices(desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth);
return desc;
}
if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_1d)
{
verify(HERE), attr.height == 1;
}
if (!fbos.empty())
{
attr2.width = scaled_w;
attr2.height = scaled_h;
}
sampled_image_descriptor result = { nullptr, deferred_request_command::atlas_gather,
attr2, {}, texture_upload_context::framebuffer_storage, format_class,
scale, rsx::texture_dimension_extended::texture_dimension_2d, decoded_remap };
gather_texture_slices(result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth);
result.simplify();
return result;
}
template<typename sampled_image_descriptor, typename copy_region_descriptor_type>
bool append_mipmap_level(
std::vector<copy_region_descriptor_type>& sections, // Destination list
const sampled_image_descriptor& level, // Descriptor for the image level being checked
const image_section_attributes_t& attr, // Attributes of image level
u8 mipmap_level, // Level index
bool apply_upscaling, // Whether to upscale the results or not
const image_section_attributes_t& level0_attr) // Attributes of the first mipmap level
{
if (level.image_handle)
{
copy_region_descriptor_type mip{};
mip.src = level.image_handle->image();
mip.xform = surface_transform::coordinate_transform;
mip.level = mipmap_level;
mip.dst_w = attr.width;
mip.dst_h = attr.height;
if (level.upload_context == rsx::texture_upload_context::framebuffer_storage)
{
mip.src_w = rsx::apply_resolution_scale(attr.width, true);
mip.src_h = rsx::apply_resolution_scale(attr.height, true);
}
else
{
mip.src_w = attr.width;
mip.src_h = attr.height;
}
sections.push_back(mip);
}
else
{
switch (level.external_subresource_desc.op)
{
case deferred_request_command::copy_image_dynamic:
case deferred_request_command::copy_image_static:
{
copy_region_descriptor_type mip{};
mip.src = level.external_subresource_desc.external_handle;
mip.xform = surface_transform::coordinate_transform;
mip.level = mipmap_level;
mip.src_w = level.external_subresource_desc.width;
mip.src_h = level.external_subresource_desc.height;
mip.dst_w = attr.width;
mip.dst_h = attr.height;
sections.push_back(mip);
break;
}
default:
{
// TODO
return false;
}
}
}
// Check for upscaling if requested
if (apply_upscaling)
{
auto& mip = sections.back();
mip.dst_w = rsx::apply_resolution_scale(mip.dst_w, true, level0_attr.width);
mip.dst_h = rsx::apply_resolution_scale(mip.dst_h, true, level0_attr.height);
}
return true;
}
};
}