diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index ee087b9dd..987fd4302 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -68,7 +68,8 @@ enum class IntSetting( ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2), DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0), TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200), - PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0); + PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0), + ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 29bab03d1..c075aadde 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1083,6 +1083,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue ) ) + add( + SingleChoiceSetting( + IntSetting.ASPECT_RATIO, + R.string.emulation_aspect_ratio, + 0, + R.array.aspectRatioNames, + R.array.aspectRatioValues, + IntSetting.ASPECT_RATIO.key, + IntSetting.ASPECT_RATIO.defaultValue, + isEnabled = IntSetting.SCREEN_LAYOUT.int == 1, + ) + ) add( SingleChoiceSetting( IntSetting.SMALL_SCREEN_POSITION, diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index e7d878294..ed24f3c8e 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -197,6 +197,7 @@ void Config::ReadValues() { ReadSetting("Layout", Settings::values.custom_bottom_x); ReadSetting("Layout", Settings::values.custom_bottom_y); ReadSetting("Layout", Settings::values.custom_bottom_width); + ReadSetting("Layout", Settings::values.aspect_ratio); ReadSetting("Layout", Settings::values.custom_bottom_height); ReadSetting("Layout", Settings::values.cardboard_screen_size); ReadSetting("Layout", Settings::values.cardboard_x_shift); diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index f1204291f..543b59913 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -63,6 +63,24 @@ 7 + + @string/aspect_ratio_default + @string/aspect_ratio_16_9 + @string/aspect_ratio_4_3 + @string/aspect_ratio_21_9_fullscreen + @string/aspect_ratio_16_10_fullscreen_stretched + @string/aspect_ratio_stretch + + + 0 + 1 + 2 + 3 + 4 + 5 + + + @string/auto_select @string/system_region_jpn diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0aeebf286..4c50488de 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -326,6 +326,12 @@ Reverse Landscape Portrait Reverse Portrait + Default + 16:9 + 4:3 + 21:9 + 16:10 + Stretch Clear @@ -411,6 +417,7 @@ D-Pad Sliding Open Settings Open Cheats + Aspect Ratio Landscape Screen Layout Portrait Screen Layout Large Screen diff --git a/src/common/settings.h b/src/common/settings.h index d061add3c..ed6d438bd 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -106,6 +106,15 @@ enum class TextureSampling : u32 { Linear = 2, }; +enum class AspectRatio : u32 { + Default = 0, + R16_9 = 1, + R4_3 = 2, + R21_9 = 3, + R16_10 = 4, + Stretch = 5, +}; + namespace NativeButton { enum Values { @@ -522,7 +531,7 @@ struct Values { Setting custom_bottom_width{640, "custom_bottom_width"}; Setting custom_bottom_height{480, "custom_bottom_height"}; Setting custom_second_layer_opacity{100, "custom_second_layer_opacity"}; - + SwitchableSetting aspect_ratio{AspectRatio::Default, "aspect_ratio"}; SwitchableSetting screen_top_stretch{false, "screen_top_stretch"}; Setting screen_top_leftright_padding{0, "screen_top_leftright_padding"}; Setting screen_top_topbottom_padding{0, "screen_top_topbottom_padding"}; diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 8b284b30c..4a1cbf3e5 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -27,11 +27,11 @@ u32 FramebufferLayout::GetScalingRatio() const { // Finds the largest size subrectangle contained in window area that is confined to the aspect ratio template static Common::Rectangle MaxRectangle(Common::Rectangle window_area, - float screen_aspect_ratio) { + float window_aspect_ratio) { float scale = std::min(static_cast(window_area.GetWidth()), - window_area.GetHeight() / screen_aspect_ratio); + window_area.GetHeight() / window_aspect_ratio); return Common::Rectangle{0, 0, static_cast(std::round(scale)), - static_cast(std::round(scale * screen_aspect_ratio))}; + static_cast(std::round(scale * window_aspect_ratio))}; } FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool upright) { @@ -76,15 +76,41 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up Common::Rectangle screen_window_area{0, 0, width, height}; Common::Rectangle top_screen; Common::Rectangle bot_screen; - float emulation_aspect_ratio; + + // TODO: This is kind of gross, make it platform agnostic. -OS +#ifdef ANDROID + const float window_aspect_ratio = static_cast(height) / width; + const auto aspect_ratio_setting = Settings::values.aspect_ratio.GetValue(); + + float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO; + switch (aspect_ratio_setting) { + case Settings::AspectRatio::Default: + break; + case Settings::AspectRatio::Stretch: + emulation_aspect_ratio = window_aspect_ratio; + break; + default: + emulation_aspect_ratio = res.GetAspectRatioValue(aspect_ratio_setting); + } + + top_screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); + bot_screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); + + if (window_aspect_ratio < emulation_aspect_ratio) { + top_screen = + top_screen.TranslateX((screen_window_area.GetWidth() - top_screen.GetWidth()) / 2); + bot_screen = + bot_screen.TranslateX((screen_window_area.GetWidth() - bot_screen.GetWidth()) / 2); + } else { + top_screen = top_screen.TranslateY((height - top_screen.GetHeight()) / 2); + bot_screen = bot_screen.TranslateY((height - bot_screen.GetHeight()) / 2); + } +#else top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO); bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO); - emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO; const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) || (Settings::values.screen_bottom_stretch.GetValue() && swapped); - const float window_aspect_ratio = static_cast(height) / width; - if (stretched) { top_screen = {Settings::values.screen_top_leftright_padding.GetValue(), Settings::values.screen_top_topbottom_padding.GetValue(), @@ -94,15 +120,12 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up Settings::values.screen_bottom_topbottom_padding.GetValue(), width - Settings::values.screen_bottom_leftright_padding.GetValue(), height - Settings::values.screen_bottom_topbottom_padding.GetValue()}; - } else if (window_aspect_ratio < emulation_aspect_ratio) { - top_screen = - top_screen.TranslateX((screen_window_area.GetWidth() - top_screen.GetWidth()) / 2); - bot_screen = - bot_screen.TranslateX((screen_window_area.GetWidth() - bot_screen.GetWidth()) / 2); } else { top_screen = top_screen.TranslateY((height - top_screen.GetHeight()) / 2); bot_screen = bot_screen.TranslateY((height - bot_screen.GetHeight()) / 2); } +#endif + res.top_screen = top_screen; res.bottom_screen = bot_screen; if (upright) { @@ -125,9 +148,8 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr FramebufferLayout res{width, height, true, true, {}, {}, !upright}; // Split the window into two parts. Give proportional width to the smaller screen // To do that, find the total emulation box and maximize that based on window size - const float window_aspect_ratio = static_cast(height) / width; - float emulation_aspect_ratio; u32 gap = (u32)(Settings::values.screen_gap.GetValue() * scale_factor); + float large_height = swapped ? Core::kScreenBottomHeight * scale_factor : Core::kScreenTopHeight * scale_factor; float small_height = @@ -137,7 +159,8 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr float small_width = static_cast(swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth); - float emulation_width, emulation_height; + float emulation_width; + float emulation_height; if (vertical) { // width is just the larger size at this point emulation_width = std::max(large_width, small_width); @@ -147,11 +170,15 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr emulation_height = std::max(large_height, small_height); } - emulation_aspect_ratio = emulation_height / emulation_width; + const float window_aspect_ratio = static_cast(height) / width; + const float emulation_aspect_ratio = emulation_height / emulation_width; Common::Rectangle screen_window_area{0, 0, width, height}; Common::Rectangle total_rect = MaxRectangle(screen_window_area, emulation_aspect_ratio); - const float scale_amount = total_rect.GetHeight() * 1.f / emulation_height * 1.f; + // TODO: Wtf does this `scale_amount` value represent? -OS + const float scale_amount = static_cast(total_rect.GetHeight()) / emulation_height; + gap = static_cast(static_cast(gap) * scale_amount); + Common::Rectangle large_screen = Common::Rectangle{total_rect.left, total_rect.top, static_cast(large_width * scale_amount + total_rect.left), @@ -168,7 +195,6 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr // shift the large screen so it is at the top position of the bounding rectangle large_screen = large_screen.TranslateY((height - total_rect.GetHeight()) / 2); } - gap = static_cast(static_cast(gap) * scale_amount); switch (small_screen_position) { case Settings::SmallScreenPosition::TopRight: @@ -676,4 +702,21 @@ std::pair GetMinimumSizeFromLayout(Settings::LayoutOption la } } +float FramebufferLayout::GetAspectRatioValue(Settings::AspectRatio aspect_ratio) { + switch (aspect_ratio) { + case Settings::AspectRatio::R16_9: + return 9.0f / 16.0f; + case Settings::AspectRatio::R4_3: + return 3.0f / 4.0f; + case Settings::AspectRatio::R21_9: + return 9.0f / 21.0f; + case Settings::AspectRatio::R16_10: + return 10.0f / 16.0f; + default: + LOG_ERROR(Frontend, "Unknown aspect ratio enum value: {}", + static_cast::type>(aspect_ratio)); + return 1.0f; // Arbitrary fallback value + } +} + } // namespace Layout diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 81501decd..8c315f515 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -44,6 +44,8 @@ struct FramebufferLayout { * screen. */ u32 GetScalingRatio() const; + + static float GetAspectRatioValue(Settings::AspectRatio aspect_ratio); }; /**