From fae3aee9e0b6bc7f193bccf71a4c34a06237e4aa Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:41:14 +0300 Subject: [PATCH 1/4] Video: The `% 4` that was done on the rendering resolution was only meant to be done when recording videos (due to encoding limitations) but one case was missed (this had no consequences really, as it was just in the code that automatically resizes the window). The hardcoded `4` has been replaced with `VIDEO_ENCODER_LMC` for clarity. --- Source/Core/VideoCommon/Present.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index b89c9fa32d..4d17a6519b 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -23,6 +23,9 @@ std::unique_ptr g_presenter; +// The video encoder needs the image to be a multiple of x samples. +static constexpr int VIDEO_ENCODER_LCM = 4; + namespace VideoCommon { static float AspectToWidescreen(float aspect) @@ -441,11 +444,14 @@ void Presenter::UpdateDrawRectangle() crop_width = win_width; } - // ensure divisibility by 4 to make it compatible with all the video encoders if (g_frame_dumper->IsFrameDumping()) { - draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; - draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; + // ensure divisibility by "VIDEO_ENCODER_LCM" to make it compatible with all the video encoders. + // Note that this is theoretically only necessary when recording videos and not screenshots. + draw_width = + std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % VIDEO_ENCODER_LCM; + draw_height = + std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % VIDEO_ENCODER_LCM; } m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); @@ -482,10 +488,13 @@ std::tuple Presenter::CalculateOutputDimensions(int width, int height) width = static_cast(std::ceil(scaled_width)); height = static_cast(std::ceil(scaled_height)); - // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video - // encoders, so do that here too to match it - width -= width % 4; - height -= height % 4; + if (g_frame_dumper->IsFrameDumping()) + { + // UpdateDrawRectangle() makes sure that the rendered image is divisible by "VIDEO_ENCODER_LCM" + // for video encoders, so do that here too to match it + width -= width % VIDEO_ENCODER_LCM; + height -= height % VIDEO_ENCODER_LCM; + } return std::make_tuple(width, height); } From cb34d1aafeca3c49412a666760c71bad434ee948 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:43:45 +0300 Subject: [PATCH 2/4] Video: There was always a black line around one of the 4 edges (top/left/bottom/right) of the window because the final output size wasn't calculated right (unless the aspect ratio was set to stretch) --- Source/Core/VideoCommon/Present.cpp | 58 ++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 4d17a6519b..5229b26f74 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -33,6 +33,39 @@ static float AspectToWidescreen(float aspect) return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); } +static std::tuple FindClosestIntegerResolution(float width, float height, + float aspect_ratio) +{ + // We can't round both the x and y resolution as that might generate an aspect ratio + // further away from the target one, we also can't either ceil or floor both sides, + // so we find the combination or flooring and ceiling that is closest to the target ar. + const int ceiled_width = static_cast(std::ceil(width)); + const int ceiled_height = static_cast(std::ceil(height)); + const int floored_width = static_cast(std::floor(width)); + const int floored_height = static_cast(std::floor(height)); + + int int_width = floored_width; + int int_height = floored_height; + + float min_aspect_ratio_distance = std::numeric_limits::max(); + for (const int new_width : std::array{ceiled_width, floored_width}) + { + for (const int new_height : std::array{ceiled_height, floored_height}) + { + const float new_aspect_ratio = static_cast(new_width) / new_height; + const float aspect_ratio_distance = std::abs((new_aspect_ratio / aspect_ratio) - 1.f); + if (aspect_ratio_distance < min_aspect_ratio_distance) + { + min_aspect_ratio_distance = aspect_ratio_distance; + int_width = new_width; + int_height = new_height; + } + } + } + + return std::make_tuple(int_width, int_height); +} + Presenter::Presenter() { m_config_changed = @@ -414,11 +447,12 @@ void Presenter::UpdateDrawRectangle() // The rendering window size const float win_width = static_cast(m_backbuffer_width); const float win_height = static_cast(m_backbuffer_height); + const float win_aspect_ratio = win_width / win_height; // FIXME: this breaks at very low widget sizes // Make ControllerInterface aware of the render window region actually being used // to adjust mouse cursor inputs. - g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); + g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / win_aspect_ratio); float draw_width = draw_aspect_ratio; float draw_height = 1; @@ -427,7 +461,7 @@ void Presenter::UpdateDrawRectangle() auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); // scale the picture to fit the rendering window - if (win_width / win_height >= crop_width / crop_height) + if (win_aspect_ratio >= crop_width / crop_height) { // the window is flatter than the picture draw_width *= win_height / crop_height; @@ -444,6 +478,9 @@ void Presenter::UpdateDrawRectangle() crop_width = win_width; } + int int_draw_width; + int int_draw_height; + if (g_frame_dumper->IsFrameDumping()) { // ensure divisibility by "VIDEO_ENCODER_LCM" to make it compatible with all the video encoders. @@ -452,12 +489,21 @@ void Presenter::UpdateDrawRectangle() std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % VIDEO_ENCODER_LCM; draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % VIDEO_ENCODER_LCM; + int_draw_width = static_cast(draw_width); + int_draw_height = static_cast(draw_height); + } + else + { + const auto int_draw_res = + FindClosestIntegerResolution(draw_width, draw_height, win_aspect_ratio); + int_draw_width = std::get<0>(int_draw_res); + int_draw_height = std::get<1>(int_draw_res); } - m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); - m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); - m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); - m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); + m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - int_draw_width / 2.0)); + m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - int_draw_height / 2.0)); + m_target_rectangle.right = m_target_rectangle.left + int_draw_width; + m_target_rectangle.bottom = m_target_rectangle.top + int_draw_height; } std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, From 6c7f34d5da28475a4d1eb9e67ffd1617cbd501b3 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:45:01 +0300 Subject: [PATCH 3/4] Video: The `Auto-Adjust Window Size` setting was calculating the window size based on the resolution of the window in the previous frame if we used the "stretch" aspect ratio setting, so it's result would be self influence in a loop and behave unreliably (e.g. when changing resolution between Auto/Native/2x the automatic window scaling would behave randomly) --- Source/Core/VideoCommon/Present.cpp | 60 ++++++++++++++++++++++------- Source/Core/VideoCommon/Present.h | 11 ++++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 5229b26f74..53d0d737d0 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -280,9 +280,12 @@ Presenter::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const return std::make_tuple(left_rc, right_rc); } -float Presenter::CalculateDrawAspectRatio() const +float Presenter::CalculateDrawAspectRatio(bool allow_stretch) const { - const auto aspect_mode = g_ActiveConfig.aspect_mode; + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; // If stretch is enabled, we prefer the aspect ratio of the window. if (aspect_mode == AspectMode::Stretch) @@ -367,9 +370,15 @@ u32 Presenter::AutoIntegralScale() const u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); } + void Presenter::SetWindowSize(int width, int height) { - const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height); + // While trying to guess the best window resolution, we can't allow it to use the + // "AspectMode::Stretch" setting because that would self influence the output result, + // given it would be based on the previous frame resolution + const bool allow_stretch = false; + const auto [out_width, out_height] = + g_presenter->CalculateOutputDimensions(width, height, allow_stretch); // Track the last values of width/height to avoid sending a window resize event every frame. if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) @@ -377,13 +386,18 @@ void Presenter::SetWindowSize(int width, int height) m_last_window_request_width = out_width; m_last_window_request_height = out_height; + // Pass in the suggested window size. This might not always be acknowledged. Host_RequestRenderWindowSize(out_width, out_height); } // Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. -std::tuple Presenter::ApplyStandardAspectCrop(float width, float height) const +std::tuple Presenter::ApplyStandardAspectCrop(float width, float height, + bool allow_stretch) const { - const auto aspect_mode = g_ActiveConfig.aspect_mode; + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) return {width, height}; @@ -506,14 +520,14 @@ void Presenter::UpdateDrawRectangle() m_target_rectangle.bottom = m_target_rectangle.top + int_draw_height; } -std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, - const int height) const +std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, const int height, + bool allow_stretch) const { // Scale either the width or height depending the content aspect ratio. // This way we preserve as much resolution as possible when scaling. float scaled_width = static_cast(width); float scaled_height = static_cast(height); - const float draw_aspect = CalculateDrawAspectRatio(); + const float draw_aspect = CalculateDrawAspectRatio(allow_stretch); if (scaled_width / scaled_height >= draw_aspect) scaled_height = scaled_width / draw_aspect; else @@ -521,18 +535,38 @@ std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, return std::make_tuple(scaled_width, scaled_height); } -std::tuple Presenter::CalculateOutputDimensions(int width, int height) const +std::tuple Presenter::CalculateOutputDimensions(int width, int height, + bool allow_stretch) const { width = std::max(width, 1); height = std::max(height, 1); - auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); + auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height, allow_stretch); // Apply crop if enabled. - std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); + std::tie(scaled_width, scaled_height) = + ApplyStandardAspectCrop(scaled_width, scaled_height, allow_stretch); - width = static_cast(std::ceil(scaled_width)); - height = static_cast(std::ceil(scaled_height)); + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; + + // Find the closest integer aspect ratio, + // this avoids a small black line from being drawn on one of the four edges + if (!g_ActiveConfig.bCrop && aspect_mode != AspectMode::Stretch) + { + const float draw_aspect_ratio = CalculateDrawAspectRatio(allow_stretch); + const auto [int_width, int_height] = + FindClosestIntegerResolution(scaled_width, scaled_height, draw_aspect_ratio); + width = int_width; + height = int_height; + } + else + { + width = static_cast(std::ceil(scaled_width)); + height = static_cast(std::ceil(scaled_height)); + } if (g_frame_dumper->IsFrameDumping()) { diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index 14d2104a65..c8bbf44257 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -58,7 +58,7 @@ public: void UpdateDrawRectangle(); - float CalculateDrawAspectRatio() const; + float CalculateDrawAspectRatio(bool allow_stretch = true) const; // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source // rectangle if it is greater. Works even if the source and target rectangles don't have a @@ -103,9 +103,12 @@ private: void ProcessFrameDumping(u64 ticks) const; - std::tuple CalculateOutputDimensions(int width, int height) const; - std::tuple ApplyStandardAspectCrop(float width, float height) const; - std::tuple ScaleToDisplayAspectRatio(int width, int height) const; + std::tuple CalculateOutputDimensions(int width, int height, + bool allow_stretch = true) const; + std::tuple ApplyStandardAspectCrop(float width, float height, + bool allow_stretch = true) const; + std::tuple ScaleToDisplayAspectRatio(int width, int height, + bool allow_stretch = true) const; // Use this to convert a single target rectangle to two stereo rectangles std::tuple, MathUtil::Rectangle> From 8bca9a864fc1739aced3c91ea7dfeef3f5516eca Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:45:19 +0300 Subject: [PATCH 4/4] Video: The `Auto` internal resolution scaling wasn't working correctly if the window weird aspect ratios (e.g. 32:9), beacuse it would account for the the portion of the image that will show black bars into the calcuations to find the best matching resolution --- Source/Core/VideoCommon/Present.cpp | 31 ++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 53d0d737d0..a190231faa 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -365,9 +365,34 @@ void* Presenter::GetNewSurfaceHandle() u32 Presenter::AutoIntegralScale() const { - // Calculate a scale based on the window size - u32 width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width; - u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; + const float efb_aspect_ratio = static_cast(EFB_WIDTH) / EFB_HEIGHT; + const float target_aspect_ratio = + static_cast(m_target_rectangle.GetWidth()) / m_target_rectangle.GetHeight(); + + u32 target_width; + u32 target_height; + + // Instead of using the entire window (back buffer) resolution, + // find the portion of it that will actually contain the EFB output, + // and ignore the portion that will likely have black bars. + if (target_aspect_ratio >= efb_aspect_ratio) + { + target_height = m_target_rectangle.GetHeight(); + target_width = static_cast( + std::round((static_cast(m_target_rectangle.GetWidth()) / target_aspect_ratio) * + efb_aspect_ratio)); + } + else + { + target_width = m_target_rectangle.GetWidth(); + target_height = static_cast( + std::round((static_cast(m_target_rectangle.GetHeight()) * target_aspect_ratio) / + efb_aspect_ratio)); + } + + // Calculate a scale based on the adjusted window size + u32 width = EFB_WIDTH * target_width / m_last_xfb_width; + u32 height = EFB_HEIGHT * target_height / m_last_xfb_height; return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); }