rpcs3/rpcs3/Emu/RSX/Common/surface_utils.h
2019-06-14 16:19:52 +03:00

450 lines
11 KiB
C++

#pragma once
#include "Utilities/types.h"
#include "Utilities/geometry.h"
#include "Utilities/address_range.h"
#include "TextureUtils.h"
#include "../rsx_utils.h"
namespace rsx
{
enum surface_state_flags : u32
{
ready = 0,
erase_bkgnd = 1
};
template <typename surface_type>
struct surface_overlap_info_t
{
surface_type surface = nullptr;
u32 base_address = 0;
bool is_depth = false;
bool is_clipped = false;
u16 src_x = 0;
u16 src_y = 0;
u16 dst_x = 0;
u16 dst_y = 0;
u16 width = 0;
u16 height = 0;
areai get_src_area() const
{
return coordi{ {src_x, src_y}, {width, height} };
}
areai get_dst_area() const
{
return coordi{ {dst_x, dst_y}, {width, height} };
}
};
template <typename surface_type>
struct deferred_clipped_region
{
u16 src_x, src_y, dst_x, dst_y, width, height;
f32 transfer_scale_x, transfer_scale_y;
surface_type target;
surface_type source;
template <typename T>
deferred_clipped_region<T> cast() const
{
deferred_clipped_region<T> ret;
ret.src_x = src_x;
ret.src_y = src_y;
ret.dst_x = dst_x;
ret.dst_y = dst_y;
ret.width = width;
ret.height = height;
ret.transfer_scale_x = transfer_scale_x;
ret.transfer_scale_y = transfer_scale_y;
ret.target = (T)(target);
ret.source = (T)(source);
return ret;
}
operator bool() const
{
return (source != nullptr);
}
template <typename T>
void init_transfer(T target_surface)
{
if (!width)
{
// Perform intersection here
const auto region = rsx::get_transferable_region(target_surface);
width = std::get<0>(region);
height = std::get<1>(region);
transfer_scale_x = f32(std::get<2>(region)) / width;
transfer_scale_y = f32(std::get<3>(region)) / height;
target = target_surface;
}
}
areai src_rect() const
{
verify(HERE), width;
return { src_x, src_y, src_x + width, src_y + height };
}
areai dst_rect() const
{
verify(HERE), width;
return { dst_x, dst_y, dst_x + u16(width * transfer_scale_x + 0.5f), dst_y + u16(height * transfer_scale_y + 0.5f) };
}
};
template <typename image_storage_type>
struct render_target_descriptor
{
u64 last_use_tag = 0; // tag indicating when this block was last confirmed to have been written to
std::array<std::pair<u32, u64>, 5> memory_tag_samples;
// Obsolete, requires updating
deferred_clipped_region<image_storage_type> old_contents{};
// Surface properties
u16 rsx_pitch = 0;
u16 native_pitch = 0;
u16 surface_width = 0;
u16 surface_height = 0;
u8 spp = 1;
u8 samples_x = 1;
u8 samples_y = 1;
flags32_t memory_usage_flags = surface_usage_flags::unknown;
flags32_t state_flags = surface_state_flags::ready;
union
{
rsx::surface_color_format gcm_color_format;
rsx::surface_depth_format gcm_depth_format;
}
format_info;
render_target_descriptor() {}
virtual ~render_target_descriptor()
{
if (old_contents)
{
// Cascade resource derefs
LOG_ERROR(RSX, "Resource was destroyed whilst holding a resource reference!");
}
}
virtual image_storage_type get_surface(rsx::surface_access access_type) = 0;
virtual bool is_depth_surface() const = 0;
virtual void release_ref(image_storage_type) const = 0;
virtual u16 get_surface_width(rsx::surface_metrics metrics = rsx::surface_metrics::pixels) const
{
switch (metrics)
{
case rsx::surface_metrics::samples:
return surface_width * samples_x;
case rsx::surface_metrics::pixels:
return surface_width;
case rsx::surface_metrics::bytes:
return rsx_pitch;
default:
fmt::throw_exception("Unknown surface metric %d", u32(metrics));
}
}
virtual u16 get_surface_height(rsx::surface_metrics metrics = rsx::surface_metrics::pixels) const
{
switch (metrics)
{
case rsx::surface_metrics::samples:
case rsx::surface_metrics::bytes:
return surface_height * samples_y;
case rsx::surface_metrics::pixels:
return surface_height;
default:
fmt::throw_exception("Unknown surface metric %d", u32(metrics));
}
}
virtual u16 get_rsx_pitch() const
{
return rsx_pitch;
}
virtual u16 get_native_pitch() const
{
return native_pitch;
}
u8 get_bpp() const
{
return u8(get_native_pitch() / get_surface_width());
}
u8 get_spp() const
{
return spp;
}
void set_aa_mode(rsx::surface_antialiasing aa)
{
switch (aa)
{
case rsx::surface_antialiasing::center_1_sample:
samples_x = samples_y = spp = 1;
break;
case rsx::surface_antialiasing::diagonal_centered_2_samples:
samples_x = spp = 2;
samples_y = 1;
break;
case rsx::surface_antialiasing::square_centered_4_samples:
case rsx::surface_antialiasing::square_rotated_4_samples:
samples_x = samples_y = 2;
spp = 4;
break;
default:
fmt::throw_exception("Unknown AA mode 0x%x", (u32)aa);
}
}
void set_format(rsx::surface_color_format format)
{
format_info.gcm_color_format = format;
}
void set_format(rsx::surface_depth_format format)
{
format_info.gcm_depth_format = format;
}
rsx::surface_color_format get_surface_color_format()
{
return format_info.gcm_color_format;
}
rsx::surface_depth_format get_surface_depth_format()
{
return format_info.gcm_depth_format;
}
bool dirty() const
{
return (state_flags != rsx::surface_state_flags::ready) || old_contents;
}
bool test() const
{
if (dirty())
{
// TODO
// Should RCB or mem-sync (inherit previous mem) to init memory
LOG_TODO(RSX, "Resource used before memory initialization");
}
// Tags are tested in an X pattern
for (const auto &tag : memory_tag_samples)
{
if (!tag.first)
break;
if (tag.second != *reinterpret_cast<u64*>(vm::g_sudo_addr + tag.first))
return false;
}
return true;
}
void clear_rw_barrier()
{
release_ref(old_contents.source);
old_contents = {};
}
template<typename T>
void set_old_contents(T* other)
{
verify(HERE), !old_contents;
if (!other || other->get_rsx_pitch() != this->get_rsx_pitch())
{
old_contents = {};
return;
}
old_contents = {};
old_contents.source = other;
other->add_ref();
}
template<typename T>
void set_old_contents_region(const T& region, bool normalized)
{
if (old_contents)
{
// This can happen when doing memory splits
auto old_surface = static_cast<decltype(region.source)>(old_contents.source);
if (old_surface->last_use_tag > region.source->last_use_tag)
{
return;
}
clear_rw_barrier();
}
// NOTE: This method will not perform pitch verification!
verify(HERE), !old_contents, region.source, region.source != this;
old_contents = region.template cast<image_storage_type>();
region.source->add_ref();
// Reverse normalization process if needed
if (normalized)
{
const u16 bytes_to_texels_x = region.source->get_bpp() * region.source->samples_x;
const u16 rows_to_texels_y = region.source->samples_y;
old_contents.src_x /= bytes_to_texels_x;
old_contents.src_y /= rows_to_texels_y;
old_contents.width /= bytes_to_texels_x;
old_contents.height /= rows_to_texels_y;
const u16 bytes_to_texels_x2 = (get_bpp() * samples_x);
const u16 rows_to_texels_y2 = samples_y;
old_contents.dst_x /= bytes_to_texels_x2;
old_contents.dst_y /= rows_to_texels_y2;
old_contents.transfer_scale_x = f32(bytes_to_texels_x) / bytes_to_texels_x2;
old_contents.transfer_scale_y = f32(rows_to_texels_y) / rows_to_texels_y2;
}
// Apply resolution scale if needed
if (g_cfg.video.resolution_scale_percent != 100)
{
auto src_width = rsx::apply_resolution_scale(old_contents.width, true, old_contents.source->width());
auto src_height = rsx::apply_resolution_scale(old_contents.height, true, old_contents.source->height());
auto dst_width = rsx::apply_resolution_scale(old_contents.width, true, old_contents.target->width());
auto dst_height = rsx::apply_resolution_scale(old_contents.height, true, old_contents.target->height());
old_contents.transfer_scale_x *= f32(dst_width) / src_width;
old_contents.transfer_scale_y *= f32(dst_height) / src_height;
old_contents.width = src_width;
old_contents.height = src_height;
old_contents.src_x = rsx::apply_resolution_scale(old_contents.src_x, false, old_contents.source->width());
old_contents.src_y = rsx::apply_resolution_scale(old_contents.src_y, false, old_contents.source->height());
old_contents.dst_x = rsx::apply_resolution_scale(old_contents.dst_x, false, old_contents.target->width());
old_contents.dst_y = rsx::apply_resolution_scale(old_contents.dst_y, false, old_contents.target->height());
}
}
void queue_tag(u32 address)
{
for (unsigned i = 0; i < memory_tag_samples.size(); ++i)
{
if (LIKELY(i))
memory_tag_samples[i].first = 0;
else
memory_tag_samples[i].first = address; // Top left
}
const u32 pitch = get_native_pitch();
if (UNLIKELY(pitch < 16))
{
// Not enough area to gather samples if pitch is too small
return;
}
// Top right corner
memory_tag_samples[1].first = address + pitch - 8;
if (const u32 h = get_surface_height(); h > 1)
{
// Last row
const u32 pitch2 = get_rsx_pitch();
const u32 last_row_offset = pitch2 * (h - 1);
memory_tag_samples[2].first = address + last_row_offset; // Bottom left corner
memory_tag_samples[3].first = address + last_row_offset + pitch - 8; // Bottom right corner
// Centroid
const u32 center_row_offset = pitch2 * (h / 2);
memory_tag_samples[4].first = address + center_row_offset + pitch / 2;
}
}
void sync_tag()
{
for (auto &tag : memory_tag_samples)
{
if (!tag.first)
break;
tag.second = *reinterpret_cast<u64*>(vm::g_sudo_addr + tag.first);
}
}
void on_write(u64 write_tag = 0)
{
if (write_tag)
{
// Update use tag if requested
last_use_tag = write_tag;
}
// Tag unconditionally without introducing new data
sync_tag();
// HACK!! This should be cleared through memory barriers only
state_flags = rsx::surface_state_flags::ready;
if (old_contents.source)
{
clear_rw_barrier();
}
}
// Returns the rect area occupied by this surface expressed as an 8bpp image with no AA
areau get_normalized_memory_area() const
{
const u16 internal_width = get_surface_width(rsx::surface_metrics::bytes);
const u16 internal_height = get_surface_height(rsx::surface_metrics::bytes);
return { 0, 0, internal_width, internal_height };
}
rsx::address_range get_memory_range() const
{
const u32 internal_height = get_surface_height(rsx::surface_metrics::samples);
return rsx::address_range::start_length(memory_tag_samples[0].first, internal_height * get_rsx_pitch());
}
template <typename T>
void transform_samples_to_pixels(area_base<T>& area)
{
if (LIKELY(spp == 1)) return;
area.x1 /= samples_x;
area.x2 /= samples_x;
area.y1 /= samples_y;
area.y2 /= samples_y;
}
template <typename T>
void transform_samples_to_pixels(T& x1, T& x2, T& y1, T& y2)
{
if (LIKELY(spp == 1)) return;
x1 /= samples_x;
x2 /= samples_x;
y1 /= samples_y;
y2 /= samples_y;
}
};
}