From 325562093487ec1137f51a1025dc0a731366770b Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 26 Feb 2026 04:33:13 -0800 Subject: [PATCH] Implement integer scaling (#1400) --- .../features/settings/model/BooleanSetting.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 9 + src/android/app/src/main/jni/config.cpp | 1 + src/android/app/src/main/jni/default_ini.h | 4 + .../app/src/main/res/values/strings.xml | 2 + src/citra_qt/configuration/config.cpp | 2 + .../configuration/configure_enhancements.cpp | 6 + .../configuration/configure_enhancements.h | 3 +- .../configuration/configure_enhancements.ui | 12 +- src/common/settings.cpp | 2 + src/common/settings.h | 1 + src/core/frontend/framebuffer_layout.cpp | 197 +++++++----------- 12 files changed, 121 insertions(+), 119 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index f06324251..f1ed493f1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -34,6 +34,7 @@ enum class BooleanSetting( LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false), NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true), LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true), + USE_INTEGER_SCALING("use_integer_scaling",Settings.SECTION_RENDERER, false), SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false), DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true), DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false), 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 1326401d5..13c1d2d96 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 @@ -898,6 +898,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.RESOLUTION_FACTOR.key, IntSetting.RESOLUTION_FACTOR.defaultValue ) + ) + add( + SwitchSetting( + BooleanSetting.USE_INTEGER_SCALING, + R.string.use_integer_scaling, + R.string.use_integer_scaling_description, + BooleanSetting.USE_INTEGER_SCALING.key, + BooleanSetting.USE_INTEGER_SCALING.defaultValue + ) ) add( SwitchSetting( diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 6d310fea6..785f9c21a 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -168,6 +168,7 @@ void Config::ReadValues() { Settings::values.pp_shader_name = sdl2_config->GetString("Renderer", "pp_shader_name", default_shader); ReadSetting("Renderer", Settings::values.filter_mode); + ReadSetting("Renderer", Settings::values.use_integer_scaling); ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 08eaf3283..fc86079ad 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -148,6 +148,10 @@ use_disk_shader_cache = # factor for the 3DS resolution resolution_factor = +# Use Integer Scaling when the layout allows +# 0: Off (default), 1: On +use_integer_scaling = + # Turns on the frame limiter, which will limit frames output to the target game speed # 0: Off, 1: On (default) use_frame_limit = diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 18959b3a8..fa542ea74 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -250,6 +250,8 @@ Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches Linear Filtering Enables linear filtering, which causes game visuals to appear smoother. + Integer Scaling + Scales the screens with an integer multiplier of the original 3DS screen. For layouts with two different screen sizes, the largest screen is integer-scaled. Texture Filter Enhances the visuals of applications by applying a filter to textures. The supported filters are Anime4K Ultrafast, Bicubic, ScaleForce, xBRZ freescale, and MMPX. Delay Game Render Thread diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index ddb4eebd6..35abe81ef 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -700,6 +700,7 @@ void QtConfig::ReadRendererValues() { ReadGlobalSetting(Settings::values.use_vsync); ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection); ReadGlobalSetting(Settings::values.resolution_factor); + ReadGlobalSetting(Settings::values.use_integer_scaling); ReadGlobalSetting(Settings::values.frame_limit); ReadGlobalSetting(Settings::values.turbo_limit); @@ -1241,6 +1242,7 @@ void QtConfig::SaveRendererValues() { WriteGlobalSetting(Settings::values.use_vsync); WriteGlobalSetting(Settings::values.use_display_refresh_rate_detection); WriteGlobalSetting(Settings::values.resolution_factor); + WriteGlobalSetting(Settings::values.use_integer_scaling); WriteGlobalSetting(Settings::values.frame_limit); WriteGlobalSetting(Settings::values.turbo_limit); diff --git a/src/citra_qt/configuration/configure_enhancements.cpp b/src/citra_qt/configuration/configure_enhancements.cpp index aba7a7ac8..62afcbb77 100644 --- a/src/citra_qt/configuration/configure_enhancements.cpp +++ b/src/citra_qt/configuration/configure_enhancements.cpp @@ -63,6 +63,7 @@ void ConfigureEnhancements::SetConfiguration() { static_cast(Settings::values.mono_render_option.GetValue())); updateShaders(Settings::values.render_3d.GetValue()); ui->toggle_linear_filter->setChecked(Settings::values.filter_mode.GetValue()); + ui->use_integer_scaling->setChecked(Settings::values.use_integer_scaling.GetValue()); ui->toggle_dump_textures->setChecked(Settings::values.dump_textures.GetValue()); ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue()); ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue()); @@ -127,6 +128,8 @@ void ConfigureEnhancements::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.filter_mode, ui->toggle_linear_filter, linear_filter); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_integer_scaling, + ui->use_integer_scaling, use_integer_scaling); ConfigurationShared::ApplyPerGameSetting(&Settings::values.texture_filter, ui->texture_filter_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.dump_textures, @@ -148,6 +151,7 @@ void ConfigureEnhancements::SetupPerGameUI() { ui->widget_resolution->setEnabled(Settings::values.resolution_factor.UsingGlobal()); ui->widget_texture_filter->setEnabled(Settings::values.texture_filter.UsingGlobal()); ui->toggle_linear_filter->setEnabled(Settings::values.filter_mode.UsingGlobal()); + ui->use_integer_scaling->setEnabled(Settings::values.use_integer_scaling.UsingGlobal()); ui->toggle_dump_textures->setEnabled(Settings::values.dump_textures.UsingGlobal()); ui->toggle_custom_textures->setEnabled(Settings::values.custom_textures.UsingGlobal()); ui->toggle_preload_textures->setEnabled(Settings::values.preload_textures.UsingGlobal()); @@ -166,6 +170,8 @@ void ConfigureEnhancements::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->toggle_linear_filter, Settings::values.filter_mode, linear_filter); + ConfigurationShared::SetColoredTristate( + ui->use_integer_scaling, Settings::values.use_integer_scaling, use_integer_scaling); ConfigurationShared::SetColoredTristate(ui->toggle_dump_textures, Settings::values.dump_textures, dump_textures); ConfigurationShared::SetColoredTristate(ui->toggle_custom_textures, diff --git a/src/citra_qt/configuration/configure_enhancements.h b/src/citra_qt/configuration/configure_enhancements.h index c9f3449b1..70d6a6673 100644 --- a/src/citra_qt/configuration/configure_enhancements.h +++ b/src/citra_qt/configuration/configure_enhancements.h @@ -1,4 +1,4 @@ -// Copyright 2019 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -39,6 +39,7 @@ private: std::unique_ptr ui; ConfigurationShared::CheckState linear_filter; + ConfigurationShared::CheckState use_integer_scaling; ConfigurationShared::CheckState dump_textures; ConfigurationShared::CheckState custom_textures; ConfigurationShared::CheckState preload_textures; diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui index ebf8520c4..dc3c3deaf 100644 --- a/src/citra_qt/configuration/configure_enhancements.ui +++ b/src/citra_qt/configuration/configure_enhancements.ui @@ -6,7 +6,7 @@ 0 0 - 440 + 639 950 @@ -110,6 +110,16 @@ + + + + Use Integer Scaling + + + <html><head/><body><p>Use Integer Scaling</p><p>Enforces that the larger screen in all layouts is an integer scale of the 240px height of the original 3DS screen.</p></body></html> + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 564090d0b..d0a08b587 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -97,6 +97,7 @@ void LogSettings() { log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue()); log_setting("Renderer_UseShaderJit", values.use_shader_jit.GetValue()); log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue()); + log_setting("Renderer_UseIntegerScaling", values.use_integer_scaling.GetValue()); log_setting("Renderer_FrameLimit", values.frame_limit.GetValue()); log_setting("Renderer_VSyncNew", values.use_vsync.GetValue()); log_setting("Renderer_PostProcessingShader", values.pp_shader_name.GetValue()); @@ -206,6 +207,7 @@ void RestoreGlobalState(bool is_powered_on) { values.shaders_accurate_mul.SetGlobal(true); values.use_vsync.SetGlobal(true); values.resolution_factor.SetGlobal(true); + values.use_integer_scaling.SetGlobal(true); values.frame_limit.SetGlobal(true); values.texture_filter.SetGlobal(true); values.texture_sampling.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 709ba0cf8..178db0019 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -524,6 +524,7 @@ struct Values { true, "use_display_refresh_rate_detection"}; Setting use_shader_jit{true, "use_shader_jit"}; SwitchableSetting resolution_factor{1, 0, 10, "resolution_factor"}; + SwitchableSetting use_integer_scaling{false, "use_integer_scaling"}; SwitchableSetting frame_limit{100, 0, 1000, "frame_limit"}; SwitchableSetting turbo_limit{200, 0, 1000, "turbo_limit"}; SwitchableSetting texture_filter{TextureFilter::NoFilter, "texture_filter"}; diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 392db8867..918c1454f 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -25,13 +25,33 @@ u32 FramebufferLayout::GetScalingRatio() const { } // Finds the largest size subrectangle contained in window area that is confined to the aspect ratio +// aligned to the upper-left corner of the bounding rectangle template static Common::Rectangle MaxRectangle(Common::Rectangle window_area, float window_aspect_ratio) { float scale = std::min(static_cast(window_area.GetWidth()), window_area.GetHeight() / window_aspect_ratio); - return Common::Rectangle{0, 0, static_cast(std::round(scale)), - static_cast(std::round(scale * window_aspect_ratio))}; + return Common::Rectangle{ + window_area.left, window_area.top, window_area.left + static_cast(std::round(scale)), + window_area.top + static_cast(std::round(scale * window_aspect_ratio))}; +} + +// overload of the above that takes an inner rectangle instead of an aspect ratio, and can be +// limited to integer scaling if desired +template +static Common::Rectangle MaxRectangle(Common::Rectangle bounding_window, + Common::Rectangle inner_window, + bool use_integer = false) { + float scale = + std::min(static_cast(bounding_window.GetWidth()) / inner_window.GetWidth(), + static_cast(bounding_window.GetHeight()) / inner_window.GetHeight()); + if (use_integer && scale >= 1.0) { + scale = std::floor(scale); + } + return Common::Rectangle(bounding_window.left, bounding_window.top, + bounding_window.left + static_cast(inner_window.GetWidth() * scale), + bounding_window.top + + static_cast(inner_window.GetHeight() * scale)); } FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool upright) { @@ -74,41 +94,31 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up FramebufferLayout res{width, height, !swapped, swapped, {}, {}, !upright}; Common::Rectangle screen_window_area{0, 0, width, height}; - Common::Rectangle top_screen; - Common::Rectangle bot_screen; + Common::Rectangle top_screen{0, 0, Core::kScreenTopWidth, Core::kScreenTopHeight}; + Common::Rectangle bot_screen{0, 0, Core::kScreenBottomWidth, Core::kScreenBottomHeight}; - // TODO: This is kind of gross, make it platform agnostic. -OS -#ifdef ANDROID - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(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: + // this is the only one where we allow integer scaling to apply + // also the only option on desktop + top_screen = MaxRectangle(screen_window_area, top_screen, + Settings::values.use_integer_scaling.GetValue()); + bot_screen = MaxRectangle(screen_window_area, bot_screen, + Settings::values.use_integer_scaling.GetValue()); break; case Settings::AspectRatio::Stretch: - emulation_aspect_ratio = window_aspect_ratio; + top_screen = MaxRectangle(screen_window_area, window_aspect_ratio); + bot_screen = MaxRectangle(screen_window_area, window_aspect_ratio); break; default: - emulation_aspect_ratio = res.GetAspectRatioValue(aspect_ratio_setting); + float emulation_aspect_ratio = FramebufferLayout::GetAspectRatioValue(aspect_ratio_setting); + top_screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); + bot_screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); } - 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); - const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) || (Settings::values.screen_bottom_stretch.GetValue() && swapped); if (stretched) { @@ -126,7 +136,6 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up bot_screen = bot_screen.TranslateX((width - bot_screen.GetWidth()) / 2) .TranslateY((height - bot_screen.GetHeight()) / 2); } -#endif res.top_screen = top_screen; res.bottom_screen = bot_screen; @@ -150,19 +159,17 @@ 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 - u32 gap = (u32)(Settings::values.screen_gap.GetValue() * scale_factor); + u32 gap = (u32)(Settings::values.screen_gap.GetValue()); - float large_height = - swapped ? Core::kScreenBottomHeight * scale_factor : Core::kScreenTopHeight * scale_factor; - float small_height = - static_cast(swapped ? Core::kScreenTopHeight : Core::kScreenBottomHeight); - float large_width = - swapped ? Core::kScreenBottomWidth * scale_factor : Core::kScreenTopWidth * scale_factor; - float small_width = - static_cast(swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth); + u32 large_height = swapped ? Core::kScreenBottomHeight : Core::kScreenTopHeight; + u32 small_height = static_cast(swapped ? Core::kScreenTopHeight / scale_factor + : Core::kScreenBottomHeight / scale_factor); + u32 large_width = swapped ? Core::kScreenBottomWidth : Core::kScreenTopWidth; + u32 small_width = static_cast(swapped ? Core::kScreenTopWidth / scale_factor + : Core::kScreenBottomWidth / scale_factor); - float emulation_width; - float emulation_height; + u32 emulation_width; + u32 emulation_height; if (vertical) { // width is just the larger size at this point emulation_width = std::max(large_width, small_width); @@ -172,12 +179,13 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr emulation_height = std::max(large_height, small_height); } - const float window_aspect_ratio = static_cast(height) / static_cast(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); - // TODO: Wtf does this `scale_amount` value represent? -OS + Common::Rectangle total_rect{0, 0, emulation_width, emulation_height}; + total_rect = MaxRectangle(screen_window_area, total_rect, + Settings::values.use_integer_scaling.GetValue()); + total_rect = total_rect.TranslateX((width - total_rect.GetWidth()) / 2) + .TranslateY((height - total_rect.GetHeight()) / 2); + const float scale_amount = static_cast(total_rect.GetHeight()) / emulation_height; gap = static_cast(static_cast(gap) * scale_amount); @@ -190,61 +198,50 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr static_cast(small_width * scale_amount + total_rect.left), static_cast(small_height * scale_amount + total_rect.top)}; - if (window_aspect_ratio < emulation_aspect_ratio) { - // shift the large screen so it is at the left position of the bounding rectangle - large_screen = large_screen.TranslateX((width - total_rect.GetWidth()) / 2); - } else { - // 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); - } - switch (small_screen_position) { case Settings::SmallScreenPosition::TopRight: // Shift the small screen to the top right corner - small_screen = small_screen.TranslateX(large_screen.right + gap); - small_screen = small_screen.TranslateY(large_screen.top); + small_screen = small_screen.TranslateX(large_screen.GetWidth() + gap); + small_screen = small_screen.TranslateY(large_screen.top - small_screen.top); break; case Settings::SmallScreenPosition::MiddleRight: // Shift the small screen to the center right - small_screen = small_screen.TranslateX(large_screen.right + gap); - small_screen = small_screen.TranslateY( - ((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top); + small_screen = small_screen.TranslateX(large_screen.GetWidth() + gap); + small_screen = + small_screen.TranslateY(((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + + large_screen.top - small_screen.top); break; case Settings::SmallScreenPosition::BottomRight: // Shift the small screen to the bottom right corner - small_screen = small_screen.TranslateX(large_screen.right + gap); - small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight()); + small_screen = small_screen.TranslateX(large_screen.GetWidth() + gap); + small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.bottom); break; case Settings::SmallScreenPosition::TopLeft: - // shift the small screen to the upper left then shift the large screen to its right - small_screen = small_screen.TranslateX(large_screen.left); + // shift the large screen to the upper right of the small screen large_screen = large_screen.TranslateX(small_screen.GetWidth() + gap); - small_screen = small_screen.TranslateY(large_screen.top); break; case Settings::SmallScreenPosition::MiddleLeft: // shift the small screen to the middle left and shift the large screen to its right - small_screen = small_screen.TranslateX(large_screen.left); large_screen = large_screen.TranslateX(small_screen.GetWidth() + gap); - small_screen = small_screen.TranslateY( - ((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top); + small_screen = + small_screen.TranslateY(((large_screen.GetHeight() - small_screen.GetHeight()) / 2)); break; case Settings::SmallScreenPosition::BottomLeft: // shift the small screen to the bottom left and shift the large screen to its right - small_screen = small_screen.TranslateX(large_screen.left); large_screen = large_screen.TranslateX(small_screen.GetWidth() + gap); - small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight()); + small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.bottom); break; case Settings::SmallScreenPosition::AboveLarge: - // shift the large screen down and the bottom screen above it - small_screen = small_screen.TranslateY(large_screen.top); + // shift the large screen down large_screen = large_screen.TranslateY(small_screen.GetHeight() + gap); // If the "large screen" is actually smaller, center it if (large_screen.GetWidth() < total_rect.GetWidth()) { large_screen = large_screen.TranslateX((total_rect.GetWidth() - large_screen.GetWidth()) / 2); } - small_screen = small_screen.TranslateX(large_screen.left + large_screen.GetWidth() / 2 - - small_screen.GetWidth() / 2); + small_screen = + small_screen.TranslateX((large_screen.left - total_rect.left) + + large_screen.GetWidth() / 2 - small_screen.GetWidth() / 2); break; case Settings::SmallScreenPosition::BelowLarge: // shift the bottom_screen down and then over to the center @@ -253,9 +250,10 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr large_screen = large_screen.TranslateX((total_rect.GetWidth() - large_screen.GetWidth()) / 2); } - small_screen = small_screen.TranslateY(large_screen.bottom + gap); - small_screen = small_screen.TranslateX(large_screen.left + large_screen.GetWidth() / 2 - - small_screen.GetWidth() / 2); + small_screen = small_screen.TranslateY(large_screen.GetHeight() + gap); + small_screen = + small_screen.TranslateX((large_screen.left - total_rect.left) + + large_screen.GetWidth() / 2 - small_screen.GetWidth() / 2); break; default: UNREACHABLE(); @@ -276,54 +274,19 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u if (upright) { std::swap(width, height); } - FramebufferLayout res{width, height, true, true, {}, {}, !upright, false, true, {}}; // Split the window into two parts. Give 2.25x width to the main screen, // and make a bar on the right side with 1x width top screen and 1.25x width bottom screen // To do that, find the total emulation box and maximize that based on window size - const float window_aspect_ratio = static_cast(height) / static_cast(width); - const float scale_factor = 2.25f; - - float main_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO; - float hybrid_area_aspect_ratio = 27.f / 65; - float top_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO; - float bot_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO; - - if (swapped) { - main_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO; - hybrid_area_aspect_ratio = - Core::kScreenBottomHeight * scale_factor / - (Core::kScreenBottomWidth * scale_factor + Core::kScreenTopWidth); - } - - Common::Rectangle screen_window_area{0, 0, width, height}; - Common::Rectangle total_rect = MaxRectangle(screen_window_area, hybrid_area_aspect_ratio); - Common::Rectangle large_main_screen = MaxRectangle(total_rect, main_screen_aspect_ratio); - Common::Rectangle side_rect = total_rect.Scale(1.f / scale_factor); - Common::Rectangle small_top_screen = MaxRectangle(side_rect, top_screen_aspect_ratio); - Common::Rectangle small_bottom_screen = MaxRectangle(side_rect, bot_screen_aspect_ratio); - - if (window_aspect_ratio < hybrid_area_aspect_ratio) { - large_main_screen = large_main_screen.TranslateX((width - total_rect.GetWidth()) / 2); - } else { - large_main_screen = large_main_screen.TranslateY((height - total_rect.GetHeight()) / 2); - } - - // Scale the bottom screen so it's width is the same as top screen - small_bottom_screen = small_bottom_screen.Scale(1.25f); - - // Shift the small bottom screen to the bottom right corner - small_bottom_screen = small_bottom_screen.TranslateX(large_main_screen.right) - .TranslateY(large_main_screen.GetHeight() + large_main_screen.top - - small_bottom_screen.GetHeight()); - - // Shift small top screen to upper right corner - small_top_screen = - small_top_screen.TranslateX(large_main_screen.right).TranslateY(large_main_screen.top); - - res.top_screen = small_top_screen; - res.additional_screen = swapped ? small_bottom_screen : large_main_screen; - res.bottom_screen = swapped ? large_main_screen : small_bottom_screen; + const float scale_factor = swapped ? 2.25 : 1.8; + const Settings::SmallScreenPosition pos = swapped ? Settings::SmallScreenPosition::TopRight + : Settings::SmallScreenPosition::BottomRight; + FramebufferLayout res = LargeFrameLayout(width, height, swapped, upright, scale_factor, pos); + const Common::Rectangle main = swapped ? res.bottom_screen : res.top_screen; + const Common::Rectangle small = swapped ? res.top_screen : res.bottom_screen; + res.additional_screen = Common::Rectangle{small.left, swapped ? small.bottom : main.top, + small.right, swapped ? main.bottom : small.top}; + res.additional_screen_enabled = true; if (upright) { return reverseLayout(res); } else {