diff --git a/rpcs3/Emu/Cell/Modules/cellVideoOut.cpp b/rpcs3/Emu/Cell/Modules/cellVideoOut.cpp index 3ee43b7aa5..6d13e24356 100644 --- a/rpcs3/Emu/Cell/Modules/cellVideoOut.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVideoOut.cpp @@ -203,7 +203,7 @@ error_code cellVideoOutConfigure(u32 videoOut, vm::ptrresolutionId); + cellSysutil.notice("Selected video configuration: resolutionId=0x%x, aspect=0x%x, format=0x%x", config->resolutionId, config->aspect, config->format); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellVideoOut.h b/rpcs3/Emu/Cell/Modules/cellVideoOut.h index cf7c3c2082..6661a39044 100644 --- a/rpcs3/Emu/Cell/Modules/cellVideoOut.h +++ b/rpcs3/Emu/Cell/Modules/cellVideoOut.h @@ -34,7 +34,7 @@ enum CellVideoOutResolutionId : s32 CELL_VIDEO_OUT_RESOLUTION_1280x1080 = 0xc, CELL_VIDEO_OUT_RESOLUTION_960x1080 = 0xd, CELL_VIDEO_OUT_RESOLUTION_720_3D_FRAME_PACKING = 0x81, - CELL_VIDEO_OUT_RESOLUTION_1024x720_3D_FRAME_PACKING = 0x88, + CELL_VIDEO_OUT_RESOLUTION_1024x720_3D_FRAME_PACKING = 0x88, CELL_VIDEO_OUT_RESOLUTION_960x720_3D_FRAME_PACKING = 0x89, CELL_VIDEO_OUT_RESOLUTION_800x720_3D_FRAME_PACKING = 0x8a, CELL_VIDEO_OUT_RESOLUTION_640x720_3D_FRAME_PACKING = 0x8b, diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index e33e1481f2..a296cd27bf 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -137,7 +137,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) if (!buffer_pitch) buffer_pitch = buffer_width * avconfig.get_bpp(); - const u32 video_frame_height = (!avconfig._3d? avconfig.resolution_y : (avconfig.resolution_y - 30) / 2); + const u32 video_frame_height = (!avconfig._3d ? avconfig.resolution_y : (avconfig.resolution_y - 30) / 2); buffer_width = std::min(buffer_width, avconfig.resolution_x); buffer_height = std::min(buffer_height, video_frame_height); } @@ -200,25 +200,12 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) // Calculate blit coordinates coordi aspect_ratio; - sizei csize(width, height); + const sizei csize(width, height); sizei new_size = csize; if (!g_cfg.video.stretch_to_display_area) { - const double aq = 1. * buffer_width / buffer_height; - const double rq = 1. * new_size.width / new_size.height; - const double q = aq / rq; - - if (q > 1.0) - { - new_size.height = static_cast(new_size.height / q); - aspect_ratio.y = (csize.height - new_size.height) / 2; - } - else if (q < 1.0) - { - new_size.width = static_cast(new_size.width * q); - aspect_ratio.x = (csize.width - new_size.width) / 2; - } + avconfig.downscale_to_aspect_ratio(aspect_ratio.x, aspect_ratio.y, new_size.width, new_size.height); } aspect_ratio.size = new_size; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 0e33b06cb8..e2ce65c641 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -551,25 +551,12 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) // Calculate output dimensions. Done after swapchain acquisition in case it was recreated. coordi aspect_ratio; - sizei csize = static_cast(m_swapchain_dims); + const sizei csize = static_cast(m_swapchain_dims); sizei new_size = csize; if (!g_cfg.video.stretch_to_display_area) { - const double aq = 1. * buffer_width / buffer_height; - const double rq = 1. * new_size.width / new_size.height; - const double q = aq / rq; - - if (q > 1.0) - { - new_size.height = static_cast(new_size.height / q); - aspect_ratio.y = (csize.height - new_size.height) / 2; - } - else if (q < 1.0) - { - new_size.width = static_cast(new_size.width * q); - aspect_ratio.x = (csize.width - new_size.width) / 2; - } + avconfig.downscale_to_aspect_ratio(aspect_ratio.x, aspect_ratio.y, new_size.width, new_size.height); } aspect_ratio.size = new_size; diff --git a/rpcs3/Emu/RSX/rsx_utils.cpp b/rpcs3/Emu/RSX/rsx_utils.cpp index 27e780be4a..8da3088b5d 100644 --- a/rpcs3/Emu/RSX/rsx_utils.cpp +++ b/rpcs3/Emu/RSX/rsx_utils.cpp @@ -2,6 +2,7 @@ #include "rsx_utils.h" #include "rsx_methods.h" #include "Emu/RSX/GCM.h" +#include "Emu/Cell/Modules/cellVideoOut.h" #include "Overlays/overlays.h" #ifdef _MSC_VER @@ -102,6 +103,94 @@ namespace rsx } } + u32 avconf::get_compatible_gcm_format() const + { + switch (format) + { + default: + rsx_log.error("Invalid AV format 0x%x", format); + [[fallthrough]]; + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8: + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8B8G8R8: + return CELL_GCM_TEXTURE_A8R8G8B8; + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_R16G16B16X16_FLOAT: + return CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT; + } + } + + u8 avconf::get_bpp() const + { + switch (format) + { + default: + rsx_log.error("Invalid AV format 0x%x", format); + [[fallthrough]]; + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8: + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8B8G8R8: + return 4; + case CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_R16G16B16X16_FLOAT: + return 8; + } + } + + double avconf::get_aspect_ratio() const + { + video_aspect v_aspect; + switch (aspect) + { + case CELL_VIDEO_OUT_ASPECT_16_9: v_aspect = video_aspect::_16_9; break; + case CELL_VIDEO_OUT_ASPECT_4_3: v_aspect = video_aspect::_4_3; break; + case CELL_VIDEO_OUT_ASPECT_AUTO: + default: v_aspect = g_cfg.video.aspect_ratio; break; + } + + double aspect_ratio; + switch (v_aspect) + { + case video_aspect::_4_3: aspect_ratio = 4.0 / 3.0; break; + case video_aspect::_16_9: aspect_ratio = 16.0 / 9.0; break; + } + return aspect_ratio; + } + + void avconf::upscale_to_aspect_ratio(int& width, int& height) const + { + if (width == 0 || height == 0) return; + + const double old_aspect = 1. * width / height; + const double scaling_factor = get_aspect_ratio() / old_aspect; + + if (scaling_factor > 1.0) + { + width = static_cast(width * scaling_factor); + } + else if (scaling_factor < 1.0) + { + height = static_cast(height / scaling_factor); + } + } + + void avconf::downscale_to_aspect_ratio(int& x, int& y, int& width, int& height) const + { + if (width == 0 || height == 0) return; + + const double old_aspect = 1. * width / height; + const double scaling_factor = get_aspect_ratio() / old_aspect; + + if (scaling_factor > 1.0) + { + const int new_height = static_cast(height / scaling_factor); + y = (height - new_height) / 2; + height = new_height; + } + else if (scaling_factor < 1.0) + { + const int new_width = static_cast(width * scaling_factor); + x = (width - new_width) / 2; + width = new_width; + } + } + #ifdef TEXTURE_CACHE_DEBUG tex_cache_checker_t tex_cache_checker = {}; #endif diff --git a/rpcs3/Emu/RSX/rsx_utils.h b/rpcs3/Emu/RSX/rsx_utils.h index caa8f29b0b..7a7190e75a 100644 --- a/rpcs3/Emu/RSX/rsx_utils.h +++ b/rpcs3/Emu/RSX/rsx_utils.h @@ -161,35 +161,12 @@ namespace rsx u32 resolution_y = 720; // Y RES atomic_t state = 0; // 1 after cellVideoOutConfigure was called - u32 get_compatible_gcm_format() const - { - switch (format) - { - default: - rsx_log.error("Invalid AV format 0x%x", format); - [[fallthrough]]; - case 0: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8: - case 1: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8B8G8R8: - return CELL_GCM_TEXTURE_A8R8G8B8; - case 2: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_R16G16B16X16_FLOAT: - return CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT; - } - } + u32 get_compatible_gcm_format() const; + u8 get_bpp() const; + double get_aspect_ratio() const; - u8 get_bpp() const - { - switch (format) - { - default: - rsx_log.error("Invalid AV format 0x%x", format); - [[fallthrough]]; - case 0: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8: - case 1: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8B8G8R8: - return 4; - case 2: // CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_R16G16B16X16_FLOAT: - return 8; - } - } + void upscale_to_aspect_ratio(int& width, int& height) const; + void downscale_to_aspect_ratio(int& x, int& y, int& width, int& height) const; }; struct blit_src_info diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index d146b19633..7d6dfc1781 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -10,6 +10,7 @@ #include "Emu/system_progress.hpp" #include "Emu/IdManager.h" #include "Emu/Cell/Modules/cellScreenshot.h" +#include "Emu/Cell/Modules/cellVideoOut.h" #include "Emu/RSX/rsx_utils.h" #include @@ -618,16 +619,32 @@ void gs_frame::take_screenshot(std::vector data, const u32 sshot_width, cons text[num_text].key = const_cast("Comment"); text[num_text].text = const_cast(game_comment.c_str()); - std::vector rows(sshot_height); - for (usz y = 0; y < sshot_height; y++) - rows[y] = sshot_data_alpha.data() + y * sshot_width * 4; + // Create image from data + QImage img(sshot_data_alpha.data(), sshot_width, sshot_height, sshot_width * 4, QImage::Format_RGBA8888); + + // Scale image if necessary + int new_width = img.width(); + int new_height = img.height(); + auto& avconf = g_fxo->get(); + avconf.upscale_to_aspect_ratio(new_width, new_height); + + if (new_width != img.width() || new_height != img.height()) + { + img = img.scaled(QSize(new_width, new_height), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation); + img.convertTo(QImage::Format_RGBA8888); // The current Qt version changes the image format during smooth scaling, so we have to change it back. + } + + // Create row pointers for libpng + std::vector rows(img.height()); + for (int y = 0; y < img.height(); y++) + rows[y] = img.scanLine(y); std::vector encoded_png; const auto write_png = [&]() { const scoped_png_ptrs ptrs; - png_set_IHDR(ptrs.write_ptr, ptrs.info_ptr, sshot_width, sshot_height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR(ptrs.write_ptr, ptrs.info_ptr, img.width(), img.height(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_text(ptrs.write_ptr, ptrs.info_ptr, text, 6); png_set_rows(ptrs.write_ptr, ptrs.info_ptr, &rows[0]); png_set_write_fn(ptrs.write_ptr, &encoded_png, [](png_structp png_ptr, png_bytep data, png_size_t length) @@ -660,10 +677,13 @@ void gs_frame::take_screenshot(std::vector data, const u32 sshot_width, cons // Games choose the overlay file and the offset based on the current video resolution. // We need to scale the overlay if our resolution scaling causes the image to have a different size. - auto& avconf = g_fxo->get(); - // TODO: handle wacky PS3 resolutions (without resolution scaling) - if (avconf.resolution_x != sshot_width || avconf.resolution_y != sshot_height) + // Scale the resolution first (as seen before with the image) + new_width = avconf.resolution_x; + new_height = avconf.resolution_y; + avconf.upscale_to_aspect_ratio(new_width, new_height); + + if (new_width != img.width() || new_height != img.height()) { const int scale = rsx::get_resolution_scale_percent(); const int x = (scale * manager.overlay_offset_x) / 100; @@ -679,16 +699,16 @@ void gs_frame::take_screenshot(std::vector data, const u32 sshot_width, cons overlay_img = overlay_img.scaled(QSize(width, height), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation); } - if (manager.overlay_offset_x < static_cast(sshot_width) && - manager.overlay_offset_y < static_cast(sshot_height) && + if (manager.overlay_offset_x < static_cast(img.width()) && + manager.overlay_offset_y < static_cast(img.height()) && manager.overlay_offset_x + overlay_img.width() > 0 && manager.overlay_offset_y + overlay_img.height() > 0) { - QImage screenshot_img(rows[0], sshot_width, sshot_height, QImage::Format_RGBA8888); + QImage screenshot_img(rows[0], img.width(), img.height(), QImage::Format_RGBA8888); QPainter painter(&screenshot_img); painter.drawImage(manager.overlay_offset_x, manager.overlay_offset_y, overlay_img); - std::memcpy(rows[0], screenshot_img.constBits(), static_cast(sshot_height) * screenshot_img.bytesPerLine()); + std::memcpy(rows[0], screenshot_img.constBits(), screenshot_img.sizeInBytes()); screenshot_log.success("Applied screenshot overlay '%s'", cell_sshot_overlay_path); }