From 9b147d3f9c31bc6af1ce1281c52b02b338925e5a Mon Sep 17 00:00:00 2001
From: SachinVin <26602104+SachinVin@users.noreply.github.com>
Date: Tue, 2 Jan 2024 14:22:03 +0530
Subject: [PATCH] framebuffer_layout.cpp mini refactor (#7300)

* framebuffer_layout.cpp: simplify FrameLayoutFromResolutionScale

- upright_screen seems to only be swapped width and height calculation, so it is replaced with std::swap
- Get rid of call to GetCardboardSettings, The FrameLayoutFromResolutionScale function is used for Screenshots and Video Dumping where we dont need 3D effects

* framebuffer_layout.cpp: Combine SideFrameLayout and MobileLandscapeFrameLayout into variants of LargeFrameLayout

* framebuffer_layout.{cpp,h}: rename maxRectangle to MaxRectangle, plus

minor documentation update

* clang-format
---
 src/core/frontend/emu_window.cpp         |  17 +-
 src/core/frontend/framebuffer_layout.cpp | 387 +++++++++--------------
 src/core/frontend/framebuffer_layout.h   |  60 ++--
 3 files changed, 194 insertions(+), 270 deletions(-)

diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 38c2fa21a..63c446983 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -173,8 +173,7 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
     TouchPressed(framebuffer_x, framebuffer_y);
 }
 
-void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
-                                               bool is_portrait_mode) {
+void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) {
     Layout::FramebufferLayout layout;
 
     // If in portrait mode, only the MobilePortrait option really makes sense
@@ -200,7 +199,8 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
             layout =
                 Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
                                          Settings::values.upright_screen.GetValue(),
-                                         Settings::values.large_screen_proportion.GetValue());
+                                         Settings::values.large_screen_proportion.GetValue(),
+                                         Layout::VerticalAlignment::Bottom);
             break;
         case Settings::LayoutOption::HybridScreen:
             layout =
@@ -208,8 +208,10 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
                                            Settings::values.upright_screen.GetValue());
             break;
         case Settings::LayoutOption::SideScreen:
-            layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
-                                             Settings::values.upright_screen.GetValue());
+            layout =
+                Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                         Settings::values.upright_screen.GetValue(), 1.0f,
+                                         Layout::VerticalAlignment::Bottom);
             break;
 #ifndef ANDROID
         case Settings::LayoutOption::SeparateWindows:
@@ -222,8 +224,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
                                                        Settings::values.swap_screen.GetValue());
             break;
         case Settings::LayoutOption::MobileLandscape:
-            layout = Layout::MobileLandscapeFrameLayout(
-                width, height, Settings::values.swap_screen.GetValue(), 2.25f, false);
+            layout =
+                Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                         false, 2.25f, Layout::VerticalAlignment::Top);
             break;
         case Settings::LayoutOption::Default:
         default:
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 40b86fb68..11d2249ac 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -30,7 +30,7 @@ u32 FramebufferLayout::GetScalingRatio() const {
 
 // Finds the largest size subrectangle contained in window area that is confined to the aspect ratio
 template <class T>
-static Common::Rectangle<T> maxRectangle(Common::Rectangle<T> window_area,
+static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
                                          float screen_aspect_ratio) {
     float scale = std::min(static_cast<float>(window_area.GetWidth()),
                            window_area.GetHeight() / screen_aspect_ratio);
@@ -50,15 +50,15 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
     if (upright) {
         // Default layout gives equal screen sizes to the top and bottom screen
         screen_window_area = {0, 0, width / 2, height};
-        top_screen = maxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
-        bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
+        top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
+        bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
         // both screens width are taken into account by dividing by 2
         emulation_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO / 2;
     } else {
         // Default layout gives equal screen sizes to the top and bottom screen
         screen_window_area = {0, 0, width, height / 2};
-        top_screen = maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
-        bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
+        top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
+        bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
         // both screens height are taken into account by multiplying by 2
         emulation_aspect_ratio = TOP_SCREEN_ASPECT_RATIO * 2;
     }
@@ -71,7 +71,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
             // Recalculate the bottom screen to account for the height difference between right and
             // left
             screen_window_area = {0, 0, top_screen.GetWidth(), height};
-            bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
+            bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
             bot_screen =
                 bot_screen.TranslateY((top_screen.GetHeight() - bot_screen.GetHeight()) / 2);
             if (swapped) {
@@ -96,7 +96,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
             // Recalculate the bottom screen to account for the width difference between top and
             // bottom
             screen_window_area = {0, 0, width, top_screen.GetHeight()};
-            bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
+            bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
             bot_screen = bot_screen.TranslateX((top_screen.GetWidth() - bot_screen.GetWidth()) / 2);
             if (swapped) {
                 bot_screen = bot_screen.TranslateY(height / 2 - bot_screen.GetHeight());
@@ -124,8 +124,8 @@ FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool swapped)
     FramebufferLayout res{width, height, true, true, {}, {}};
     // Default layout gives equal screen sizes to the top and bottom screen
     Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
-    Common::Rectangle<u32> top_screen = maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
-    Common::Rectangle<u32> bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
+    Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
+    Common::Rectangle<u32> bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
 
     float window_aspect_ratio = static_cast<float>(height) / width;
     // both screens height are taken into account by multiplying by 2
@@ -151,48 +151,6 @@ FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool swapped)
     return res;
 }
 
-FramebufferLayout MobileLandscapeFrameLayout(u32 width, u32 height, bool swapped,
-                                             float scale_factor, bool center_vertical) {
-    ASSERT(width > 0);
-    ASSERT(height > 0);
-
-    FramebufferLayout res{width, height, true, true, {}, {}};
-    // Split the window into two parts. Give 4x width to the main screen and 1x width to the small
-    // To do that, find the total emulation box and maximize that based on window size
-    float window_aspect_ratio = static_cast<float>(height) / width;
-    float emulation_aspect_ratio =
-        swapped ? Core::kScreenBottomHeight * scale_factor /
-                      (Core::kScreenBottomWidth * scale_factor + Core::kScreenTopWidth)
-                : Core::kScreenTopHeight * scale_factor /
-                      (Core::kScreenTopWidth * scale_factor + Core::kScreenBottomWidth);
-    float large_screen_aspect_ratio = swapped ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
-    float small_screen_aspect_ratio = swapped ? TOP_SCREEN_ASPECT_RATIO : BOT_SCREEN_ASPECT_RATIO;
-
-    Common::Rectangle<u32> screen_window_area{0, 0, width, height};
-    Common::Rectangle<u32> total_rect = maxRectangle(screen_window_area, emulation_aspect_ratio);
-    Common::Rectangle<u32> large_screen = maxRectangle(total_rect, large_screen_aspect_ratio);
-    Common::Rectangle<u32> fourth_size_rect = total_rect.Scale(1.f / scale_factor);
-    Common::Rectangle<u32> small_screen = maxRectangle(fourth_size_rect, small_screen_aspect_ratio);
-
-    if (window_aspect_ratio < emulation_aspect_ratio) {
-        large_screen =
-            large_screen.TranslateX((screen_window_area.GetWidth() - total_rect.GetWidth()) / 2);
-    } else if (center_vertical) {
-        large_screen = large_screen.TranslateY((height - total_rect.GetHeight()) / 2);
-    }
-
-    // Shift the small screen to the bottom right corner
-    small_screen = small_screen.TranslateX(large_screen.right);
-    if (center_vertical) {
-        small_screen = small_screen.TranslateY(large_screen.GetHeight() + large_screen.top -
-                                               small_screen.GetHeight());
-    }
-
-    res.top_screen = swapped ? small_screen : large_screen;
-    res.bottom_screen = swapped ? large_screen : small_screen;
-    return res;
-}
-
 FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
     ASSERT(width > 0);
     ASSERT(height > 0);
@@ -205,13 +163,13 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
     Common::Rectangle<u32> bot_screen;
     float emulation_aspect_ratio;
     if (upright) {
-        top_screen = maxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
-        bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
+        top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
+        bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
         emulation_aspect_ratio =
             (swapped) ? BOT_SCREEN_UPRIGHT_ASPECT_RATIO : TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
     } else {
-        top_screen = maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
-        bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
+        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;
     }
 
@@ -232,7 +190,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
 }
 
 FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upright,
-                                   float scale_factor) {
+                                   float scale_factor, VerticalAlignment vertical_alignment) {
     ASSERT(width > 0);
     ASSERT(height > 0);
 
@@ -274,10 +232,10 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr
     }
 
     Common::Rectangle<u32> screen_window_area{0, 0, width, height};
-    Common::Rectangle<u32> total_rect = maxRectangle(screen_window_area, emulation_aspect_ratio);
-    Common::Rectangle<u32> large_screen = maxRectangle(total_rect, large_screen_aspect_ratio);
-    Common::Rectangle<u32> fourth_size_rect = total_rect.Scale(1.f / scale_factor);
-    Common::Rectangle<u32> small_screen = maxRectangle(fourth_size_rect, small_screen_aspect_ratio);
+    Common::Rectangle<u32> total_rect = MaxRectangle(screen_window_area, emulation_aspect_ratio);
+    Common::Rectangle<u32> large_screen = MaxRectangle(total_rect, large_screen_aspect_ratio);
+    Common::Rectangle<u32> scaled_rect = total_rect.Scale(1.f / scale_factor);
+    Common::Rectangle<u32> small_screen = MaxRectangle(scaled_rect, small_screen_aspect_ratio);
 
     if (window_aspect_ratio < emulation_aspect_ratio) {
         large_screen = large_screen.TranslateX((width - total_rect.GetWidth()) / 2);
@@ -286,13 +244,46 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr
     }
     if (upright) {
         large_screen = large_screen.TranslateY(small_screen.GetHeight());
-        small_screen = small_screen.TranslateX(large_screen.right - small_screen.GetWidth())
-                           .TranslateY(large_screen.top - small_screen.GetHeight());
+        small_screen = small_screen.TranslateY(large_screen.top - small_screen.GetHeight());
+        switch (vertical_alignment) {
+        case VerticalAlignment::Top:
+            // Shift the small screen to the top right corner
+            small_screen = small_screen.TranslateX(large_screen.left);
+            break;
+        case VerticalAlignment::Middle:
+            // Shift the small screen to the center right
+            small_screen = small_screen.TranslateX(
+                ((large_screen.GetWidth() - small_screen.GetWidth()) / 2) + large_screen.left);
+            break;
+        case VerticalAlignment::Bottom:
+            // Shift the small screen to the bottom right corner
+            small_screen = small_screen.TranslateX(large_screen.right - small_screen.GetWidth());
+            break;
+        default:
+            UNREACHABLE();
+            break;
+        }
+
     } else {
-        // Shift the small screen to the bottom right corner
-        small_screen =
-            small_screen.TranslateX(large_screen.right)
-                .TranslateY(large_screen.GetHeight() + large_screen.top - small_screen.GetHeight());
+        small_screen = small_screen.TranslateX(large_screen.right);
+        switch (vertical_alignment) {
+        case VerticalAlignment::Top:
+            // Shift the small screen to the top right corner
+            small_screen = small_screen.TranslateY(large_screen.top);
+            break;
+        case VerticalAlignment::Middle:
+            // Shift the small screen to the center right
+            small_screen = small_screen.TranslateY(
+                ((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top);
+            break;
+        case VerticalAlignment::Bottom:
+            // Shift the small screen to the bottom right corner
+            small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight());
+            break;
+        default:
+            UNREACHABLE();
+            break;
+        }
     }
     res.top_screen = swapped ? small_screen : large_screen;
     res.bottom_screen = swapped ? large_screen : small_screen;
@@ -331,11 +322,11 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
     }
 
     Common::Rectangle<u32> screen_window_area{0, 0, width, height};
-    Common::Rectangle<u32> total_rect = maxRectangle(screen_window_area, hybrid_area_aspect_ratio);
-    Common::Rectangle<u32> large_main_screen = maxRectangle(total_rect, main_screen_aspect_ratio);
+    Common::Rectangle<u32> total_rect = MaxRectangle(screen_window_area, hybrid_area_aspect_ratio);
+    Common::Rectangle<u32> large_main_screen = MaxRectangle(total_rect, main_screen_aspect_ratio);
     Common::Rectangle<u32> side_rect = total_rect.Scale(1.f / scale_factor);
-    Common::Rectangle<u32> small_top_screen = maxRectangle(side_rect, top_screen_aspect_ratio);
-    Common::Rectangle<u32> small_bottom_screen = maxRectangle(side_rect, bot_screen_aspect_ratio);
+    Common::Rectangle<u32> small_top_screen = MaxRectangle(side_rect, top_screen_aspect_ratio);
+    Common::Rectangle<u32> 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);
@@ -373,54 +364,6 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
     return res;
 }
 
-FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
-    ASSERT(width > 0);
-    ASSERT(height > 0);
-
-    FramebufferLayout res{width, height, true, true, {}, {}, !upright};
-
-    // Aspect ratio of both screens side by side
-    float emulation_aspect_ratio =
-        upright ? static_cast<float>(Core::kScreenTopWidth + Core::kScreenBottomWidth) /
-                      Core::kScreenTopHeight
-                : static_cast<float>(Core::kScreenTopHeight) /
-                      (Core::kScreenTopWidth + Core::kScreenBottomWidth);
-
-    float window_aspect_ratio = static_cast<float>(height) / width;
-    Common::Rectangle<u32> screen_window_area{0, 0, width, height};
-    // Find largest Rectangle that can fit in the window size with the given aspect ratio
-    Common::Rectangle<u32> screen_rect = maxRectangle(screen_window_area, emulation_aspect_ratio);
-    // Find sizes of top and bottom screen
-    Common::Rectangle<u32> top_screen =
-        upright ? maxRectangle(screen_rect, TOP_SCREEN_UPRIGHT_ASPECT_RATIO)
-                : maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
-    Common::Rectangle<u32> bot_screen =
-        upright ? maxRectangle(screen_rect, BOT_SCREEN_UPRIGHT_ASPECT_RATIO)
-                : maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
-
-    if (window_aspect_ratio < emulation_aspect_ratio) {
-        // Apply borders to the left and right sides of the window.
-        u32 shift_horizontal = (screen_window_area.GetWidth() - screen_rect.GetWidth()) / 2;
-        top_screen = top_screen.TranslateX(shift_horizontal);
-        bot_screen = bot_screen.TranslateX(shift_horizontal);
-    } else {
-        // Window is narrower than the emulation content => apply borders to the top and bottom
-        u32 shift_vertical = (screen_window_area.GetHeight() - screen_rect.GetHeight()) / 2;
-        top_screen = top_screen.TranslateY(shift_vertical);
-        bot_screen = bot_screen.TranslateY(shift_vertical);
-    }
-    if (upright) {
-        // Leave the top screen at the top if we are swapped.
-        res.top_screen = swapped ? top_screen : top_screen.TranslateY(bot_screen.GetHeight());
-        res.bottom_screen = swapped ? bot_screen.TranslateY(top_screen.GetHeight()) : bot_screen;
-    } else {
-        // Move the top screen to the right if we are swapped.
-        res.top_screen = swapped ? top_screen.TranslateX(bot_screen.GetWidth()) : top_screen;
-        res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateX(top_screen.GetWidth());
-    }
-    return res;
-}
-
 FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright) {
     // When is_secondary is true, we disable the top screen, and enable the bottom screen.
     // The same logic is found in the SingleFrameLayout using the is_swapped bool.
@@ -454,131 +397,103 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped) {
 }
 
 FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) {
-    FramebufferLayout layout;
     if (Settings::values.custom_layout.GetValue() == true) {
-        layout = CustomFrameLayout(std::max(Settings::values.custom_top_right.GetValue(),
-                                            Settings::values.custom_bottom_right.GetValue()),
-                                   std::max(Settings::values.custom_top_bottom.GetValue(),
-                                            Settings::values.custom_bottom_bottom.GetValue()),
-                                   Settings::values.swap_screen.GetValue());
-    } else {
-        int width, height;
-        switch (Settings::values.layout_option.GetValue()) {
-        case Settings::LayoutOption::SingleScreen:
+        return CustomFrameLayout(std::max(Settings::values.custom_top_right.GetValue(),
+                                          Settings::values.custom_bottom_right.GetValue()),
+                                 std::max(Settings::values.custom_top_bottom.GetValue(),
+                                          Settings::values.custom_bottom_bottom.GetValue()),
+                                 Settings::values.swap_screen.GetValue());
+    }
+
+    int width, height;
+    switch (Settings::values.layout_option.GetValue()) {
+    case Settings::LayoutOption::SingleScreen:
 #ifndef ANDROID
-        case Settings::LayoutOption::SeparateWindows:
+    case Settings::LayoutOption::SeparateWindows:
 #endif
-        {
-            const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
-            if (Settings::values.upright_screen.GetValue()) {
-                if (swap_screens) {
-                    width = Core::kScreenBottomHeight * res_scale;
-                    height = Core::kScreenBottomWidth * res_scale;
-                } else {
-                    width = Core::kScreenTopHeight * res_scale;
-                    height = Core::kScreenTopWidth * res_scale;
-                }
-            } else {
-                if (swap_screens) {
-                    width = Core::kScreenBottomWidth * res_scale;
-                    height = Core::kScreenBottomHeight * res_scale;
-                } else {
-                    width = Core::kScreenTopWidth * res_scale;
-                    height = Core::kScreenTopHeight * res_scale;
-                }
-            }
-            layout = SingleFrameLayout(width, height, swap_screens,
-                                       Settings::values.upright_screen.GetValue());
-            break;
-        }
-        case Settings::LayoutOption::LargeScreen:
-            if (Settings::values.upright_screen.GetValue()) {
-                if (Settings::values.swap_screen.GetValue()) {
-                    width = Core::kScreenBottomHeight * res_scale;
-                    height =
-                        (Core::kScreenBottomWidth +
-                         static_cast<int>(Core::kScreenTopWidth /
-                                          Settings::values.large_screen_proportion.GetValue())) *
-                        res_scale;
-                } else {
-                    width = Core::kScreenTopHeight * res_scale;
-                    height =
-                        (Core::kScreenTopWidth +
-                         static_cast<int>(Core::kScreenBottomWidth /
-                                          Settings::values.large_screen_proportion.GetValue())) *
-                        res_scale;
-                }
-            } else {
-                if (Settings::values.swap_screen.GetValue()) {
-                    width = (Core::kScreenBottomWidth +
-                             Core::kScreenTopWidth /
-                                 static_cast<int>(
-                                     Settings::values.large_screen_proportion.GetValue())) *
-                            res_scale;
-                    height = Core::kScreenBottomHeight * res_scale;
-                } else {
-                    width = (Core::kScreenTopWidth +
-                             Core::kScreenBottomWidth /
-                                 static_cast<int>(
-                                     Settings::values.large_screen_proportion.GetValue())) *
-                            res_scale;
-                    height = Core::kScreenTopHeight * res_scale;
-                }
-            }
-            layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
-                                      Settings::values.upright_screen.GetValue(),
-                                      Settings::values.large_screen_proportion.GetValue());
-            break;
-        case Settings::LayoutOption::SideScreen:
-            if (Settings::values.upright_screen.GetValue()) {
-                width = Core::kScreenTopHeight * res_scale;
-                height = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
-            } else {
-                width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
-                height = Core::kScreenTopHeight * res_scale;
-            }
-            layout = SideFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
-                                     Settings::values.upright_screen.GetValue());
-            break;
-        case Settings::LayoutOption::MobilePortrait:
+    {
+        const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
+        if (swap_screens) {
+            width = Core::kScreenBottomWidth * res_scale;
+            height = Core::kScreenBottomHeight * res_scale;
+        } else {
             width = Core::kScreenTopWidth * res_scale;
-            height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
-            layout =
-                MobilePortraitFrameLayout(width, height, Settings::values.swap_screen.GetValue());
-            break;
-        case Settings::LayoutOption::MobileLandscape:
-            if (Settings::values.swap_screen.GetValue()) {
-                width =
-                    (Core::kScreenBottomWidth + static_cast<int>(Core::kScreenTopWidth / 2.25f)) *
-                    res_scale;
-                height = Core::kScreenBottomHeight * res_scale;
-            } else {
-                width =
-                    (Core::kScreenTopWidth + static_cast<int>(Core::kScreenBottomWidth / 2.25f)) *
-                    res_scale;
-                height = Core::kScreenTopHeight * res_scale;
-            }
-            layout = MobileLandscapeFrameLayout(
-                width, height, Settings::values.swap_screen.GetValue(), 2.25f, false);
-            break;
-        case Settings::LayoutOption::Default:
-        default:
-            if (Settings::values.upright_screen.GetValue()) {
-                width = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
-                height = Core::kScreenTopWidth * res_scale;
-            } else {
-                width = Core::kScreenTopWidth * res_scale;
-                height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
-            }
-            layout = DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
-                                        Settings::values.upright_screen.GetValue());
-            break;
+            height = Core::kScreenTopHeight * res_scale;
         }
+        if (Settings::values.upright_screen.GetValue()) {
+            std::swap(width, height);
+        }
+        return SingleFrameLayout(width, height, swap_screens,
+                                 Settings::values.upright_screen.GetValue());
     }
-    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
-        layout = Layout::GetCardboardSettings(layout);
+
+    case Settings::LayoutOption::LargeScreen:
+        if (Settings::values.swap_screen.GetValue()) {
+            width = (Core::kScreenBottomWidth +
+                     Core::kScreenTopWidth /
+                         static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
+                    res_scale;
+            height = Core::kScreenBottomHeight * res_scale;
+        } else {
+            width = (Core::kScreenTopWidth +
+                     Core::kScreenBottomWidth /
+                         static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
+                    res_scale;
+            height = Core::kScreenTopHeight * res_scale;
+        }
+        if (Settings::values.upright_screen.GetValue()) {
+            std::swap(width, height);
+        }
+        return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                Settings::values.upright_screen.GetValue(),
+                                Settings::values.large_screen_proportion.GetValue(),
+                                VerticalAlignment::Bottom);
+
+    case Settings::LayoutOption::SideScreen:
+        width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
+        height = Core::kScreenTopHeight * res_scale;
+
+        if (Settings::values.upright_screen.GetValue()) {
+            std::swap(width, height);
+        }
+        return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                Settings::values.upright_screen.GetValue(), 1,
+                                VerticalAlignment::Middle);
+
+    case Settings::LayoutOption::MobilePortrait:
+        width = Core::kScreenTopWidth * res_scale;
+        height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
+        return MobilePortraitFrameLayout(width, height, Settings::values.swap_screen.GetValue());
+
+    case Settings::LayoutOption::MobileLandscape: {
+        constexpr float large_screen_proportion = 2.25f;
+        if (Settings::values.swap_screen.GetValue()) {
+            width = (Core::kScreenBottomWidth +
+                     static_cast<int>(Core::kScreenTopWidth / large_screen_proportion)) *
+                    res_scale;
+            height = Core::kScreenBottomHeight * res_scale;
+        } else {
+            width = (Core::kScreenTopWidth +
+                     static_cast<int>(Core::kScreenBottomWidth / large_screen_proportion)) *
+                    res_scale;
+            height = Core::kScreenTopHeight * res_scale;
+        }
+        return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), false,
+                                large_screen_proportion, VerticalAlignment::Top);
     }
-    return layout;
+
+    case Settings::LayoutOption::Default:
+    default:
+        width = Core::kScreenTopWidth * res_scale;
+        height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
+
+        if (Settings::values.upright_screen.GetValue()) {
+            std::swap(width, height);
+        }
+        return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                  Settings::values.upright_screen.GetValue());
+    }
+    UNREACHABLE();
 }
 
 FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 76549954f..f2ed553fd 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -20,6 +20,31 @@ enum class DisplayOrientation {
     PortraitFlipped,  // 3DS rotated 270 degrees counter-clockwise
 };
 
+/// Describes the vertical alignment of the top and bottom screens in LargeFrameLayout
+/// Top
+/// +-------------+-----+
+/// |             |     |
+/// |             +-----+
+/// |             |
+/// +-------------+
+/// Middle
+/// +-------------+
+/// |             +-----+
+/// |             |     |
+/// |             +-----+
+/// +-------------+
+/// Bottom
+/// +-------------+
+/// |             |
+/// |             +-----+
+/// |             |     |
+/// +-------------+-----+
+enum class VerticalAlignment {
+    Top,
+    Middle,
+    Bottom,
+};
+
 /// Describes the horizontal coordinates for the right eye screen when using Cardboard VR
 struct CardboardSettings {
     u32 top_screen_right_eye;
@@ -43,7 +68,7 @@ struct FramebufferLayout {
     CardboardSettings cardboard;
 
     /**
-     * Returns the ration of pixel size of the top screen, compared to the native size of the 3DS
+     * Returns the ratio of pixel size of the top screen, compared to the native size of the 3DS
      * screen.
      */
     u32 GetScalingRatio() const;
@@ -54,6 +79,7 @@ struct FramebufferLayout {
  * @param width Window framebuffer width in pixels
  * @param height Window framebuffer height in pixels
  * @param is_swapped if true, the bottom screen will be displayed above the top screen
+ * @param upright if true, the screens will be rotated 90 degrees anti-clockwise
  * @return Newly created FramebufferLayout object with default screen regions initialized
  */
 FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
@@ -67,25 +93,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, boo
  */
 FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool is_swapped);
 
-/**
- * Factory method for constructing a Frame with the a 4x size Top screen with a 1x size bottom
- * screen on the right
- * This is useful in particular because it matches well with a 1920x1080 resolution monitor
- * @param width Window framebuffer width in pixels
- * @param height Window framebuffer height in pixels
- * @param is_swapped if true, the bottom screen will be the large display
- * @param scale_factor Scale factor to use for bottom screen with respect to top screen
- * @param center_vertical When true, centers the top and bottom screens vertically
- * @return Newly created FramebufferLayout object with default screen regions initialized
- */
-FramebufferLayout MobileLandscapeFrameLayout(u32 width, u32 height, bool is_swapped,
-                                             float scale_factor, bool center_vertical);
-
 /**
  * Factory method for constructing a FramebufferLayout with only the top or bottom screen
  * @param width Window framebuffer width in pixels
  * @param height Window framebuffer height in pixels
  * @param is_swapped if true, the bottom screen will be displayed (and the top won't be displayed)
+ * @param upright if true, the screens will be rotated 90 degrees anti-clockwise
  * @return Newly created FramebufferLayout object with default screen regions initialized
  */
 FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
@@ -97,32 +110,25 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool
  * @param width Window framebuffer width in pixels
  * @param height Window framebuffer height in pixels
  * @param is_swapped if true, the bottom screen will be the large display
+ * @param upright if true, the screens will be rotated 90 degrees anti-clockwise
  * @param scale_factor The ratio between the large screen with respect to the smaller screen
+ * @param vertical_alignment The vertical alignment of the smaller screen relative to the larger
+ * screen
  * @return Newly created FramebufferLayout object with default screen regions initialized
  */
 FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool upright,
-                                   float scale_factor);
+                                   float scale_factor, VerticalAlignment vertical_alignment);
 /**
  * Factory method for constructing a frame with 2.5 times bigger top screen on the right,
  * and 1x top and bottom screen on the left
  * @param width Window framebuffer width in pixels
  * @param height Window framebuffer height in pixels
  * @param is_swapped if true, the bottom screen will be the large display
+ * @param upright if true, the screens will be rotated 90 degrees anti-clockwise
  * @return Newly created FramebufferLayout object with default screen regions initialized
  */
 FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright);
 
-/**
- * Factory method for constructing a Frame with the Top screen and bottom
- * screen side by side
- * This is useful for devices with small screens, like the GPDWin
- * @param width Window framebuffer width in pixels
- * @param height Window framebuffer height in pixels
- * @param is_swapped if true, the bottom screen will be the left display
- * @return Newly created FramebufferLayout object with default screen regions initialized
- */
-FramebufferLayout SideFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
-
 /**
  * Factory method for constructing a Frame with the Top screen and bottom
  * screen on separate windows