diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 1a1aa17589..941e17733f 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -187,6 +187,7 @@ filter_mode =
# 2: Large Screen Small Screen
# 3: Side by Side
# 4: Separate Windows
+# 5: Hybrid Screen
layout_option =
# Toggle custom layout (using the settings below) on or off.
diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui
index 08465ef295..83b85ba9a5 100644
--- a/src/citra_qt/configuration/configure_enhancements.ui
+++ b/src/citra_qt/configuration/configure_enhancements.ui
@@ -368,6 +368,11 @@
Separate Windows
+ -
+
+ Hybrid Screen
+
+
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 63d3cc45fb..c00d277fd7 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -387,6 +387,7 @@ void GMainWindow::InitializeWidgets() {
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Default);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen);
+ actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Hybrid_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows);
}
@@ -800,6 +801,7 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Screen_Layout_Default, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Single_Screen, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Large_Screen, &GMainWindow::ChangeScreenLayout);
+ connect_menu(ui->action_Screen_Layout_Hybrid_Screen, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Side_by_Side, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Separate_Windows, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Swap_Screens, &GMainWindow::OnSwapScreens);
@@ -1883,6 +1885,8 @@ void GMainWindow::ChangeScreenLayout() {
new_layout = Settings::LayoutOption::SingleScreen;
} else if (ui->action_Screen_Layout_Large_Screen->isChecked()) {
new_layout = Settings::LayoutOption::LargeScreen;
+ } else if (ui->action_Screen_Layout_Hybrid_Screen->isChecked()) {
+ new_layout = Settings::LayoutOption::HybridScreen;
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
new_layout = Settings::LayoutOption::SideScreen;
} else if (ui->action_Screen_Layout_Separate_Windows->isChecked()) {
@@ -1902,6 +1906,8 @@ void GMainWindow::ToggleScreenLayout() {
case Settings::LayoutOption::SingleScreen:
return Settings::LayoutOption::LargeScreen;
case Settings::LayoutOption::LargeScreen:
+ return Settings::LayoutOption::HybridScreen;
+ case Settings::LayoutOption::HybridScreen:
return Settings::LayoutOption::SideScreen;
case Settings::LayoutOption::SideScreen:
return Settings::LayoutOption::SeparateWindows;
@@ -2774,6 +2780,8 @@ void GMainWindow::SyncMenuUISettings() {
Settings::LayoutOption::SingleScreen);
ui->action_Screen_Layout_Large_Screen->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::LargeScreen);
+ ui->action_Screen_Layout_Hybrid_Screen->setChecked(Settings::values.layout_option.GetValue() ==
+ Settings::LayoutOption::HybridScreen);
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::SideScreen);
ui->action_Screen_Layout_Separate_Windows->setChecked(
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 3451395688..5573227500 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -134,6 +134,7 @@
+
@@ -504,6 +505,14 @@
Large Screen
+
+
+ true
+
+
+ Hybrid Screen
+
+
true
diff --git a/src/common/settings.h b/src/common/settings.h
index 37754b4951..67c7b182c8 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -36,6 +36,7 @@ enum class LayoutOption : u32 {
#ifndef ANDROID
SeparateWindows,
#endif
+ HybridScreen,
// Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
// the top of the frame, and the bottom screen is enlarged to match the top screen.
MobilePortrait,
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 37038d2d25..2d5dfde54f 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -200,6 +200,11 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue());
break;
+ case Settings::LayoutOption::HybridScreen:
+ layout =
+ Layout::HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
+ 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());
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 36a7c5de06..40b86fb688 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -299,6 +299,80 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr
return res;
}
+FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright) {
+ ASSERT(width > 0);
+ ASSERT(height > 0);
+
+ FramebufferLayout res{width, height, true, true, {}, {}, !upright, 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) / 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);
+ }
+
+ if (upright) {
+ hybrid_area_aspect_ratio = 1.f / hybrid_area_aspect_ratio;
+ main_screen_aspect_ratio = 1.f / main_screen_aspect_ratio;
+ top_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
+ bot_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
+ }
+
+ 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);
+ if (upright) {
+ large_main_screen = large_main_screen.TranslateY(small_bottom_screen.GetHeight());
+ // Shift small bottom screen to upper right corner
+ small_bottom_screen =
+ small_bottom_screen.TranslateX(large_main_screen.right - small_bottom_screen.GetWidth())
+ .TranslateY(large_main_screen.top - small_bottom_screen.GetHeight());
+
+ // Shift small top screen to upper left corner
+ small_top_screen = small_top_screen.TranslateX(large_main_screen.left)
+ .TranslateY(large_main_screen.top - small_bottom_screen.GetHeight());
+ } else {
+ // 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;
+ return res;
+}
+
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index ce11833618..76549954fa 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -37,6 +37,9 @@ struct FramebufferLayout {
Common::Rectangle bottom_screen;
bool is_rotated = true;
+ bool additional_screen_enabled;
+ Common::Rectangle additional_screen;
+
CardboardSettings cardboard;
/**
@@ -99,6 +102,15 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool
*/
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool upright,
float scale_factor);
+/**
+ * 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
+ * @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
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index bc897d73fd..9d48e23caa 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -942,6 +942,15 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
ApplySecondLayerOpacity();
DrawTopScreen(layout, top_screen);
}
+
+ if (layout.additional_screen_enabled) {
+ const auto& additional_screen = layout.additional_screen;
+ if (!Settings::values.swap_screen.GetValue()) {
+ DrawTopScreen(layout, additional_screen);
+ } else {
+ DrawBottomScreen(layout, additional_screen);
+ }
+ }
ResetSecondLayerOpacity();
}