rpcsx/rpcs3/Emu/RSX/Common/surface_utils.h
2019-07-17 13:29:42 +03:00

576 lines
14 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,
require_resolve = 2,
require_unresolve = 4
};
enum surface_sample_layout : u32
{
null = 0,
ps3 = 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);
auto src_w = std::get<0>(region);
auto src_h = std::get<1>(region);
auto dst_w = std::get<2>(region);
auto dst_h = std::get<3>(region);
// Apply resolution scale if needed
if (g_cfg.video.resolution_scale_percent != 100)
{
auto src = static_cast<T>(source);
src_w = rsx::apply_resolution_scale(src_w, true, src->get_surface_width(rsx::surface_metrics::pixels));
src_h = rsx::apply_resolution_scale(src_h, true, src->get_surface_height(rsx::surface_metrics::pixels));
dst_w = rsx::apply_resolution_scale(dst_w, true, target_surface->get_surface_width(rsx::surface_metrics::pixels));
dst_h = rsx::apply_resolution_scale(dst_h, true, target_surface->get_surface_height(rsx::surface_metrics::pixels));
}
width = src_w;
height = src_h;
transfer_scale_x = f32(dst_w) / src_w;
transfer_scale_y = f32(dst_h) / src_h;
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;
std::vector<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;
std::unique_ptr<typename std::remove_pointer<image_storage_type>::type> resolve_surface;
surface_sample_layout sample_layout = surface_sample_layout::null;
flags32_t memory_usage_flags = surface_usage_flags::unknown;
flags32_t state_flags = surface_state_flags::ready;
flags32_t msaa_flags = surface_state_flags::ready;
flags32_t stencil_init_flags = 0;
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.empty())
{
// 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 native_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(rsx::surface_metrics::samples));
}
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_spp(u8 count)
{
switch (count)
{
case 1:
samples_x = samples_y = spp = 1;
break;
case 2:
samples_x = spp = 2;
samples_y = 1;
break;
case 4:
samples_x = samples_y = 2;
spp = 4;
break;
default:
fmt::throw_exception("Unexpected sample count 0x%x", count);
}
}
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.empty();
}
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()
{
for (auto &e : old_contents)
{
release_ref(e.source);
}
old_contents.clear();
}
template <typename T>
u32 prepare_rw_barrier_for_transfer(T *target)
{
if (old_contents.size() <= 1)
return 0;
// Sort here before doing transfers since surfaces may have been updated in the meantime
std::sort(old_contents.begin(), old_contents.end(), [](auto& a, auto &b)
{
auto _a = static_cast<T*>(a.source);
auto _b = static_cast<T*>(b.source);
return (_a->last_use_tag < _b->last_use_tag);
});
// Try and optimize by omitting possible overlapped transfers
for (size_t i = old_contents.size() - 1; i > 0 /* Intentional */; i--)
{
old_contents[i].init_transfer(target);
const auto dst_area = old_contents[i].dst_rect();
if (unsigned(dst_area.x2) == target->width() && unsigned(dst_area.y2) == target->height() &&
!dst_area.x1 && !dst_area.y1)
{
// This transfer will overwrite everything older
return u32(i);
}
}
return 0;
}
template<typename T>
void set_old_contents(T* other)
{
verify(HERE), old_contents.empty();
if (!other || other->get_rsx_pitch() != this->get_rsx_pitch())
{
return;
}
old_contents.emplace_back();
old_contents.back().source = other;
other->add_ref();
}
template<typename T>
void set_old_contents_region(const T& region, bool normalized)
{
// NOTE: This method will not perform pitch verification!
verify(HERE), region.source, region.source != static_cast<decltype(region.source)>(this);
old_contents.push_back(region.template cast<image_storage_type>());
auto &slice = old_contents.back();
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;
slice.src_x /= bytes_to_texels_x;
slice.src_y /= rows_to_texels_y;
slice.width /= bytes_to_texels_x;
slice.height /= rows_to_texels_y;
const u16 bytes_to_texels_x2 = (get_bpp() * samples_x);
const u16 rows_to_texels_y2 = samples_y;
slice.dst_x /= bytes_to_texels_x2;
slice.dst_y /= rows_to_texels_y2;
slice.transfer_scale_x = f32(bytes_to_texels_x) / bytes_to_texels_x2;
slice.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(slice.width, true, slice.source->width());
auto src_height = rsx::apply_resolution_scale(slice.height, true, slice.source->height());
auto dst_width = rsx::apply_resolution_scale(slice.width, true, slice.target->width());
auto dst_height = rsx::apply_resolution_scale(slice.height, true, slice.target->height());
slice.transfer_scale_x *= f32(dst_width) / src_width;
slice.transfer_scale_y *= f32(dst_height) / src_height;
slice.width = src_width;
slice.height = src_height;
slice.src_x = rsx::apply_resolution_scale(slice.src_x, false, slice.source->width());
slice.src_y = rsx::apply_resolution_scale(slice.src_y, false, slice.source->height());
slice.dst_x = rsx::apply_resolution_scale(slice.dst_x, false, slice.target->width());
slice.dst_y = rsx::apply_resolution_scale(slice.dst_y, false, slice.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, rsx::surface_state_flags resolve_flags = surface_state_flags::require_resolve)
{
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 (spp > 1 && sample_layout != surface_sample_layout::null)
{
msaa_flags = resolve_flags;
}
if (!old_contents.empty())
{
clear_rw_barrier();
}
}
void on_write_copy(u64 write_tag = 0, bool keep_optimizations = false)
{
on_write(write_tag, rsx::surface_state_flags::require_unresolve);
if (!keep_optimizations && is_depth_surface())
{
// A successful write-copy occured, cannot guarantee flat contents in stencil area
stencil_init_flags |= (1 << 9);
}
}
void on_invalidate_children()
{
if (resolve_surface)
{
msaa_flags = rsx::surface_state_flags::require_resolve;
}
}
// 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_pixels_to_samples(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;
}
template <typename T>
void transform_pixels_to_samples(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;
}
template<typename T>
void transform_blit_coordinates(rsx::surface_access access_type, area_base<T>& region)
{
if (spp == 1 || sample_layout == rsx::surface_sample_layout::ps3)
return;
verify(HERE), access_type != rsx::surface_access::write;
transform_samples_to_pixels(region);
}
};
}