#pragma once #include "Utilities/GSL.h" #include "../GCM.h" #include namespace rsx { namespace utility { std::vector get_rtt_indexes(surface_target color_target); size_t get_aligned_pitch(surface_color_format format, u32 width); size_t get_packed_pitch(surface_color_format format, u32 width); } /** * Helper for surface (ie color and depth stencil render target) management. * It handles surface creation and storage. Backend should only retrieve pointer to surface. * It provides 2 methods get_texture_from_*_if_applicable that should be used when an app * wants to sample a previous surface. * Please note that the backend is still responsible for creating framebuffer/descriptors * and need to inform surface_store everytime surface format/size/addresses change. * * Since it's a template it requires a trait with the followings: * - type surface_storage_type which is a structure containing texture. * - type surface_type which is a pointer to storage_type or a reference. * - type command_list_type that can be void for backend without command list * - type download_buffer_object used by issue_download_command and map_downloaded_buffer functions to handle sync * * - a member function static surface_type(const surface_storage_type&) that returns underlying surface pointer from a storage type. * - 2 member functions static surface_storage_type create_new_surface(u32 address, Surface_color_format/Surface_depth_format format, size_t width, size_t height,...) * used to create a new surface_storage_type holding surface from passed parameters. * - a member function static prepare_rtt_for_drawing(command_list, surface_type) that makes a sampleable surface a color render target one. * - a member function static prepare_rtt_for_drawing(command_list, surface_type) that makes a render target surface a sampleable one. * - a member function static prepare_ds_for_drawing that does the same for depth stencil surface. * - a member function static prepare_ds_for_sampling that does the same for depth stencil surface. * - a member function static bool rtt_has_format_width_height(const surface_storage_type&, Surface_color_format surface_color_format, size_t width, size_t height) * that checks if the given surface has the given format and size * - a member function static bool ds_has_format_width_height that does the same for ds * - a member function static download_buffer_object issue_download_command(surface_type, Surface_color_format color_format, size_t width, size_t height,...) * that generates command to download the given surface to some mappable buffer. * - a member function static issue_depth_download_command that does the same for depth surface * - a member function static issue_stencil_download_command that does the same for stencil surface * - a member function gsl::span map_downloaded_buffer(download_buffer_object, ...) that maps a download_buffer_object * - a member function static unmap_downloaded_buffer that unmaps it. */ template struct surface_store { template void copy_pitched_src_to_dst(gsl::span dest, gsl::span src, size_t src_pitch_in_bytes, size_t width, size_t height) { for (int row = 0; row < height; row++) { for (unsigned col = 0; col < width; col++) dest[col] = src[col]; src = src.subspan(src_pitch_in_bytes / sizeof(U)); dest = dest.subspan(width); } } protected: using surface_storage_type = typename Traits::surface_storage_type; using surface_type = typename Traits::surface_type; using command_list_type = typename Traits::command_list_type; using download_buffer_object = typename Traits::download_buffer_object; std::unordered_map m_render_targets_storage = {}; std::unordered_map m_depth_stencil_storage = {}; public: std::array, 4> m_bound_render_targets = {}; std::tuple m_bound_depth_stencil = {}; std::list invalidated_resources; surface_store() = default; ~surface_store() = default; surface_store(const surface_store&) = delete; protected: /** * If render target already exists at address, issue state change operation on cmdList. * Otherwise create one with width, height, clearColor info. * returns the corresponding render target resource. */ template gsl::not_null bind_address_as_render_targets( command_list_type command_list, u32 address, surface_color_format color_format, size_t width, size_t height, Args&&... extra_params) { auto It = m_render_targets_storage.find(address); // TODO: Fix corner cases // This doesn't take overlapping surface(s) into account. surface_storage_type old_surface_storage; surface_storage_type new_surface_storage; surface_type old_surface = nullptr; surface_type new_surface = nullptr; if (It != m_render_targets_storage.end()) { surface_storage_type &rtt = It->second; if (Traits::rtt_has_format_width_height(rtt, color_format, width, height)) { Traits::prepare_rtt_for_drawing(command_list, Traits::get(rtt)); return Traits::get(rtt); } old_surface = Traits::get(rtt); old_surface_storage = std::move(rtt); m_render_targets_storage.erase(address); } //Search invalidated resources for a suitable surface for (auto It = invalidated_resources.begin(); It != invalidated_resources.end(); It++) { auto &rtt = *It; if (Traits::rtt_has_format_width_height(rtt, color_format, width, height, true)) { new_surface_storage = std::move(rtt); if (old_surface) //Exchange this surface with the invalidated one rtt = std::move(old_surface_storage); else //rtt is now empty - erase it invalidated_resources.erase(It); new_surface = Traits::get(new_surface_storage); Traits::invalidate_rtt_surface_contents(command_list, new_surface, old_surface, true); Traits::prepare_rtt_for_drawing(command_list, new_surface); break; } } if (old_surface != nullptr && new_surface == nullptr) //This was already determined to be invalid and is excluded from testing above invalidated_resources.push_back(std::move(old_surface_storage)); if (new_surface != nullptr) { //New surface was found among existing surfaces m_render_targets_storage[address] = std::move(new_surface_storage); return new_surface; } m_render_targets_storage[address] = Traits::create_new_surface(address, color_format, width, height, old_surface, std::forward(extra_params)...); return Traits::get(m_render_targets_storage[address]); } template gsl::not_null bind_address_as_depth_stencil( command_list_type command_list, u32 address, surface_depth_format depth_format, size_t width, size_t height, Args&&... extra_params) { surface_storage_type old_surface_storage; surface_storage_type new_surface_storage; surface_type old_surface = nullptr; surface_type new_surface = nullptr; auto It = m_depth_stencil_storage.find(address); if (It != m_depth_stencil_storage.end()) { surface_storage_type &ds = It->second; if (Traits::ds_has_format_width_height(ds, depth_format, width, height)) { Traits::prepare_ds_for_drawing(command_list, Traits::get(ds)); return Traits::get(ds); } old_surface = Traits::get(ds); old_surface_storage = std::move(ds); m_depth_stencil_storage.erase(address); } //Search invalidated resources for a suitable surface for (auto It = invalidated_resources.begin(); It != invalidated_resources.end(); It++) { auto &ds = *It; if (Traits::ds_has_format_width_height(ds, depth_format, width, height, true)) { new_surface_storage = std::move(ds); if (old_surface) //Exchange this surface with the invalidated one ds = std::move(old_surface_storage); else invalidated_resources.erase(It); new_surface = Traits::get(new_surface_storage); Traits::prepare_ds_for_drawing(command_list, new_surface); Traits::invalidate_depth_surface_contents(command_list, new_surface, old_surface, true); break; } } if (old_surface != nullptr && new_surface == nullptr) //This was already determined to be invalid and is excluded from testing above invalidated_resources.push_back(std::move(old_surface_storage)); if (new_surface != nullptr) { //New surface was found among existing surfaces m_depth_stencil_storage[address] = std::move(new_surface_storage); return new_surface; } m_depth_stencil_storage[address] = Traits::create_new_surface(address, depth_format, width, height, old_surface, std::forward(extra_params)...); return Traits::get(m_depth_stencil_storage[address]); } public: /** * Update bound color and depth surface. * Must be called everytime surface format, clip, or addresses changes. */ template void prepare_render_target( command_list_type command_list, surface_color_format color_format, surface_depth_format depth_format, u32 clip_horizontal_reg, u32 clip_vertical_reg, surface_target set_surface_target, const std::array &surface_addresses, u32 address_z, Args&&... extra_params) { u32 clip_width = clip_horizontal_reg; u32 clip_height = clip_vertical_reg; // u32 clip_x = clip_horizontal_reg; // u32 clip_y = clip_vertical_reg; // Make previous RTTs sampleable for (std::tuple &rtt : m_bound_render_targets) { if (std::get<1>(rtt) != nullptr) Traits::prepare_rtt_for_sampling(command_list, std::get<1>(rtt)); rtt = std::make_tuple(0, nullptr); } // Create/Reuse requested rtts for (u8 surface_index : utility::get_rtt_indexes(set_surface_target)) { if (surface_addresses[surface_index] == 0) continue; m_bound_render_targets[surface_index] = std::make_tuple(surface_addresses[surface_index], bind_address_as_render_targets(command_list, surface_addresses[surface_index], color_format, clip_width, clip_height, std::forward(extra_params)...)); } // Same for depth buffer if (std::get<1>(m_bound_depth_stencil) != nullptr) Traits::prepare_ds_for_sampling(command_list, std::get<1>(m_bound_depth_stencil)); m_bound_depth_stencil = std::make_tuple(0, nullptr); if (!address_z) return; m_bound_depth_stencil = std::make_tuple(address_z, bind_address_as_depth_stencil(command_list, address_z, depth_format, clip_width, clip_height, std::forward(extra_params)...)); } /** * Search for given address in stored color surface and returns it if size/format match. * Return an empty surface_type otherwise. */ surface_type get_texture_from_render_target_if_applicable(u32 address) { // TODO: Handle texture that overlaps one (or several) surface. // Handle texture conversion // FIXME: Disgaea 3 loading screen seems to use a subset of a surface. It's not properly handled here. // Note: not const because conversions/resolve/... can happen auto It = m_render_targets_storage.find(address); if (It != m_render_targets_storage.end()) return Traits::get(It->second); return surface_type(); } /** * Search for given address in stored depth stencil surface and returns it if size/format match. * Return an empty surface_type otherwise. */ surface_type get_texture_from_depth_stencil_if_applicable(u32 address) { // TODO: Same as above although there wasn't any game using corner case for DS yet. auto It = m_depth_stencil_storage.find(address); if (It != m_depth_stencil_storage.end()) return Traits::get(It->second); return surface_type(); } /** * Get bound color surface raw data. */ template std::array, 4> get_render_targets_data( surface_color_format color_format, size_t width, size_t height, Args&& ...args ) { std::array download_data = {}; // Issue download commands for (int i = 0; i < 4; i++) { if (std::get<0>(m_bound_render_targets[i]) == 0) continue; surface_type surface_resource = std::get<1>(m_bound_render_targets[i]); download_data[i] = std::move( Traits::issue_download_command(surface_resource, color_format, width, height, std::forward(args)...) ); } std::array, 4> result = {}; // Sync and copy data for (int i = 0; i < 4; i++) { if (std::get<0>(m_bound_render_targets[i]) == 0) continue; gsl::span raw_src = Traits::map_downloaded_buffer(download_data[i], std::forward(args)...); size_t src_pitch = utility::get_aligned_pitch(color_format, ::narrow(width)); size_t dst_pitch = utility::get_packed_pitch(color_format, ::narrow(width)); result[i].resize(dst_pitch * height); // Note: MSVC + GSL doesn't support span -> span for non const span atm // thus manual conversion switch (color_format) { case surface_color_format::a8b8g8r8: case surface_color_format::x8b8g8r8_o8b8g8r8: case surface_color_format::x8b8g8r8_z8b8g8r8: case surface_color_format::a8r8g8b8: case surface_color_format::x8r8g8b8_o8r8g8b8: case surface_color_format::x8r8g8b8_z8r8g8b8: case surface_color_format::x32: { gsl::span> dst_span{ (be_t*)result[i].data(), ::narrow(dst_pitch * height / sizeof(be_t)) }; copy_pitched_src_to_dst(dst_span, gsl::as_span(raw_src), src_pitch, width, height); break; } case surface_color_format::b8: { gsl::span dst_span{ (u8*)result[i].data(), ::narrow(dst_pitch * height / sizeof(u8)) }; copy_pitched_src_to_dst(dst_span, gsl::as_span(raw_src), src_pitch, width, height); break; } case surface_color_format::g8b8: case surface_color_format::r5g6b5: case surface_color_format::x1r5g5b5_o1r5g5b5: case surface_color_format::x1r5g5b5_z1r5g5b5: { gsl::span> dst_span{ (be_t*)result[i].data(), ::narrow(dst_pitch * height / sizeof(be_t)) }; copy_pitched_src_to_dst(dst_span, gsl::as_span(raw_src), src_pitch, width, height); break; } // Note : may require some big endian swap case surface_color_format::w32z32y32x32: { gsl::span dst_span{ (u128*)result[i].data(), ::narrow(dst_pitch * height / sizeof(u128)) }; copy_pitched_src_to_dst(dst_span, gsl::as_span(raw_src), src_pitch, width, height); break; } case surface_color_format::w16z16y16x16: { gsl::span dst_span{ (u64*)result[i].data(), ::narrow(dst_pitch * height / sizeof(u64)) }; copy_pitched_src_to_dst(dst_span, gsl::as_span(raw_src), src_pitch, width, height); break; } } Traits::unmap_downloaded_buffer(download_data[i], std::forward(args)...); } return result; } /** * Get bound color surface raw data. */ template std::array, 2> get_depth_stencil_data( surface_depth_format depth_format, size_t width, size_t height, Args&& ...args ) { std::array, 2> result = {}; if (std::get<0>(m_bound_depth_stencil) == 0) return result; size_t row_pitch = align(width * 4, 256); download_buffer_object stencil_data = {}; download_buffer_object depth_data = Traits::issue_depth_download_command(std::get<1>(m_bound_depth_stencil), depth_format, width, height, std::forward(args)...); if (depth_format == surface_depth_format::z24s8) stencil_data = std::move(Traits::issue_stencil_download_command(std::get<1>(m_bound_depth_stencil), width, height, std::forward(args)...)); gsl::span depth_buffer_raw_src = Traits::map_downloaded_buffer(depth_data, std::forward(args)...); if (depth_format == surface_depth_format::z16) { result[0].resize(width * height * 2); gsl::span dest{ (u16*)result[0].data(), ::narrow(width * height) }; copy_pitched_src_to_dst(dest, gsl::as_span(depth_buffer_raw_src), row_pitch, width, height); } if (depth_format == surface_depth_format::z24s8) { result[0].resize(width * height * 4); gsl::span dest{ (u32*)result[0].data(), ::narrow(width * height) }; copy_pitched_src_to_dst(dest, gsl::as_span(depth_buffer_raw_src), row_pitch, width, height); } Traits::unmap_downloaded_buffer(depth_data, std::forward(args)...); if (depth_format == surface_depth_format::z16) return result; gsl::span stencil_buffer_raw_src = Traits::map_downloaded_buffer(stencil_data, std::forward(args)...); result[1].resize(width * height); gsl::span dest{ (u8*)result[1].data(), ::narrow(width * height) }; copy_pitched_src_to_dst(dest, gsl::as_span(stencil_buffer_raw_src), align(width, 256), width, height); Traits::unmap_downloaded_buffer(stencil_data, std::forward(args)...); return result; } /** * Invalidates cached surface data and marks surface contents as deleteable * Called at the end of a frame (workaround, need to find the proper invalidate command) */ void invalidate_surface_cache_data(command_list_type command_list) { for (auto &rtt : m_render_targets_storage) Traits::invalidate_rtt_surface_contents(command_list, Traits::get(std::get<1>(rtt)), nullptr, false); for (auto &ds : m_depth_stencil_storage) Traits::invalidate_depth_surface_contents(command_list, Traits::get(std::get<1>(ds)), nullptr, true); } }; }