Merge pull request #5043 from vitor-k/screen-rotate

Implement Upright/Book-style layout
This commit is contained in:
James Rowe 2020-01-20 18:38:06 -07:00 committed by GitHub
commit a0f9c795c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 408 additions and 115 deletions

View file

@ -151,6 +151,7 @@ void Config::ReadValues() {
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0));
Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false);
Settings::values.upright_screen = sdl2_config->GetBoolean("Layout", "upright_screen", false);
Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false);
Settings::values.custom_top_left =
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0));

View file

@ -183,6 +183,10 @@ custom_bottom_bottom =
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
swap_screen =
# Toggle upright orientation, for book style games.
# 0 (default): Off, 1: On
upright_screen =
# Dumps textures as PNG to dump/textures/[Title ID]/.
# 0 (default): Off, 1: On
dump_textures =

View file

@ -57,7 +57,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 20> default_hotkeys{
const std::array<UISettings::Shortcut, 21> default_hotkeys{
{{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral("\\"), Qt::ApplicationShortcut}},
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@ -70,6 +70,7 @@ const std::array<UISettings::Shortcut, 20> default_hotkeys{
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
@ -296,6 +297,7 @@ void Config::ReadLayoutValues() {
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt());
Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool();
Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool();
Settings::values.custom_layout = ReadSetting(QStringLiteral("custom_layout"), false).toBool();
Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt();
Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt();
@ -765,6 +767,7 @@ void Config::SaveLayoutValues() {
WriteSetting(QStringLiteral("filter_mode"), Settings::values.filter_mode, true);
WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option));
WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false);
WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false);
WriteSetting(QStringLiteral("custom_layout"), Settings::values.custom_layout, false);
WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0);
WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0);

View file

@ -53,6 +53,7 @@ void ConfigureEnhancements::SetConfiguration() {
ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
ui->swap_screen->setChecked(Settings::values.swap_screen);
ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->upright_screen->setChecked(Settings::values.upright_screen);
ui->toggle_dump_textures->setChecked(Settings::values.dump_textures);
ui->toggle_custom_textures->setChecked(Settings::values.custom_textures);
ui->toggle_preload_textures->setChecked(Settings::values.preload_textures);
@ -101,6 +102,7 @@ void ConfigureEnhancements::ApplyConfiguration() {
static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
Settings::values.swap_screen = ui->swap_screen->isChecked();
Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
Settings::values.upright_screen = ui->upright_screen->isChecked();
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();
Settings::values.custom_textures = ui->toggle_custom_textures->isChecked();
Settings::values.preload_textures = ui->toggle_preload_textures->isChecked();

View file

@ -239,6 +239,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="upright_screen">
<property name="text">
<string>Rotate Screens Upright</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>

View file

@ -410,6 +410,8 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey("Main Window", "Swap Screens", render_window),
&QShortcut::activated, ui.action_Screen_Layout_Swap_Screens, &QAction::trigger);
connect(hotkey_registry.GetHotkey("Main Window", "Rotate Screens Upright", render_window),
&QShortcut::activated, ui.action_Screen_Layout_Upright_Screens, &QAction::trigger);
connect(hotkey_registry.GetHotkey("Main Window", "Toggle Screen Layout", render_window),
&QShortcut::activated, this, &GMainWindow::ToggleScreenLayout);
connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
@ -607,6 +609,9 @@ void GMainWindow::ConnectMenuEvents() {
ui.action_Screen_Layout_Swap_Screens->setShortcut(
hotkey_registry.GetHotkey("Main Window", "Swap Screens", this)->key());
ui.action_Screen_Layout_Swap_Screens->setShortcutContext(Qt::WidgetWithChildrenShortcut);
ui.action_Screen_Layout_Upright_Screens->setShortcut(
hotkey_registry.GetHotkey("Main Window", "Rotate Screens Upright", this)->key());
ui.action_Screen_Layout_Upright_Screens->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
connect(ui.action_Screen_Layout_Default, &QAction::triggered, this,
&GMainWindow::ChangeScreenLayout);
@ -618,6 +623,8 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ChangeScreenLayout);
connect(ui.action_Screen_Layout_Swap_Screens, &QAction::triggered, this,
&GMainWindow::OnSwapScreens);
connect(ui.action_Screen_Layout_Upright_Screens, &QAction::triggered, this,
&GMainWindow::OnRotateScreens);
// Movie
connect(ui.action_Record_Movie, &QAction::triggered, this, &GMainWindow::OnRecordMovie);
@ -1435,6 +1442,11 @@ void GMainWindow::OnSwapScreens() {
Settings::Apply();
}
void GMainWindow::OnRotateScreens() {
Settings::values.upright_screen = ui.action_Screen_Layout_Upright_Screens->isChecked();
Settings::Apply();
}
void GMainWindow::OnCheats() {
CheatDialog cheat_dialog(this);
cheat_dialog.exec();
@ -2032,6 +2044,7 @@ void GMainWindow::SyncMenuUISettings() {
ui.action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
Settings::LayoutOption::SideScreen);
ui.action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
ui.action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
}
void GMainWindow::RetranslateStatusBar() {

View file

@ -187,6 +187,7 @@ private slots:
void ChangeScreenLayout();
void ToggleScreenLayout();
void OnSwapScreens();
void OnRotateScreens();
void OnCheats();
void ShowFullscreen();
void HideFullscreen();

View file

@ -109,6 +109,7 @@
<addaction name="action_Screen_Layout_Large_Screen"/>
<addaction name="action_Screen_Layout_Side_by_Side"/>
<addaction name="separator"/>
<addaction name="action_Screen_Layout_Upright_Screens"/>
<addaction name="action_Screen_Layout_Swap_Screens"/>
</widget>
<addaction name="action_Fullscreen"/>
@ -425,6 +426,14 @@
<string>Swap Screens</string>
</property>
</action>
<action name="action_Screen_Layout_Upright_Screens">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Rotate Upright</string>
</property>
</action>
<action name="action_Check_For_Updates">
<property name="text">
<string>Check for Updates</string>

View file

@ -118,6 +118,11 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) /
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
if (!framebuffer_layout.is_rotated) {
std::swap(touch_state->touch_x, touch_state->touch_y);
touch_state->touch_x = 1.f - touch_state->touch_x;
}
touch_state->touch_pressed = true;
}
@ -145,17 +150,21 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height)
} else {
switch (Settings::values.layout_option) {
case Settings::LayoutOption::SingleScreen:
layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen);
layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::LargeScreen:
layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen);
layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::SideScreen:
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen);
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::Default:
default:
layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen);
layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
}
}

View file

@ -15,9 +15,17 @@ static const float TOP_SCREEN_ASPECT_RATIO =
static_cast<float>(Core::kScreenTopHeight) / Core::kScreenTopWidth;
static const float BOT_SCREEN_ASPECT_RATIO =
static_cast<float>(Core::kScreenBottomHeight) / Core::kScreenBottomWidth;
static const float TOP_SCREEN_UPRIGHT_ASPECT_RATIO =
static_cast<float>(Core::kScreenTopWidth) / Core::kScreenTopHeight;
static const float BOT_SCREEN_UPRIGHT_ASPECT_RATIO =
static_cast<float>(Core::kScreenBottomWidth) / Core::kScreenBottomHeight;
u32 FramebufferLayout::GetScalingRatio() const {
return static_cast<u32>(((top_screen.GetWidth() - 1) / Core::kScreenTopWidth) + 1);
if (is_rotated) {
return static_cast<u32>(((top_screen.GetWidth() - 1) / Core::kScreenTopWidth) + 1);
} else {
return static_cast<u32>(((top_screen.GetWidth() - 1) / Core::kScreenTopHeight) + 1);
}
}
// Finds the largest size subrectangle contained in window area that is confined to the aspect ratio
@ -30,57 +38,108 @@ static Common::Rectangle<T> maxRectangle(Common::Rectangle<T> window_area,
static_cast<T>(std::round(scale * screen_aspect_ratio))};
}
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped) {
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
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);
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
Common::Rectangle<u32> screen_window_area;
Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bot_screen;
float emulation_aspect_ratio;
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);
// 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);
// both screens height are taken into account by multiplying by 2
emulation_aspect_ratio = TOP_SCREEN_ASPECT_RATIO * 2;
}
float window_aspect_ratio = static_cast<float>(height) / width;
// both screens height are taken into account by multiplying by 2
float emulation_aspect_ratio = TOP_SCREEN_ASPECT_RATIO * 2;
if (window_aspect_ratio < emulation_aspect_ratio) {
// Apply borders to the left and right sides of the window.
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);
// Window is wider than the emulation content => apply borders to the right and left sides
if (upright) {
// 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 =
bot_screen.TranslateY((top_screen.GetHeight() - bot_screen.GetHeight()) / 2);
if (swapped) {
bot_screen = bot_screen.TranslateX(width / 2 - bot_screen.GetWidth());
} else {
top_screen = top_screen.TranslateX(width / 2 - top_screen.GetWidth());
}
} else {
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 {
// Window is narrower than the emulation content => apply borders to the top and bottom
// 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 = bot_screen.TranslateX((top_screen.GetWidth() - bot_screen.GetWidth()) / 2);
if (swapped) {
bot_screen = bot_screen.TranslateY(height / 2 - bot_screen.GetHeight());
if (upright) {
top_screen = top_screen.TranslateY(
(screen_window_area.GetHeight() - top_screen.GetHeight()) / 2);
bot_screen = bot_screen.TranslateY(
(screen_window_area.GetHeight() - bot_screen.GetHeight()) / 2);
} else {
top_screen = top_screen.TranslateY(height / 2 - top_screen.GetHeight());
// 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 = bot_screen.TranslateX((top_screen.GetWidth() - bot_screen.GetWidth()) / 2);
if (swapped) {
bot_screen = bot_screen.TranslateY(height / 2 - bot_screen.GetHeight());
} else {
top_screen = top_screen.TranslateY(height / 2 - top_screen.GetHeight());
}
}
}
// Move the top screen to the bottom if we are swapped.
res.top_screen = swapped ? top_screen.TranslateY(height / 2) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateY(height / 2);
if (upright) {
// Move the top screen to the right if we are swapped.
res.top_screen = swapped ? top_screen.TranslateX(width / 2) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateX(width / 2);
} else {
// Move the top screen to the bottom if we are swapped.
res.top_screen = swapped ? top_screen.TranslateY(height / 2) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateY(height / 2);
}
return res;
}
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped) {
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
// The drawing code needs at least somewhat valid values for both screens
// so just calculate them both even if the other isn't showing.
FramebufferLayout res{width, height, !swapped, swapped, {}, {}};
FramebufferLayout res{width, height, !swapped, swapped, {}, {}, !upright};
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
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;
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);
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);
emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
}
float window_aspect_ratio = static_cast<float>(height) / width;
float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
if (window_aspect_ratio < emulation_aspect_ratio) {
top_screen =
@ -96,21 +155,42 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped) {
return res;
}
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped) {
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}};
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
// 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 * 4 /
(Core::kScreenBottomWidth * 4.0f + Core::kScreenTopWidth)
: Core::kScreenTopHeight * 4 /
(Core::kScreenTopWidth * 4.0f + 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;
float emulation_aspect_ratio;
float large_screen_aspect_ratio;
float small_screen_aspect_ratio;
if (upright) {
if (swapped) {
emulation_aspect_ratio = (Core::kScreenBottomWidth * 4.0f + Core::kScreenTopWidth) /
(Core::kScreenBottomHeight * 4);
large_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
small_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
} else {
emulation_aspect_ratio = (Core::kScreenTopWidth * 4.0f + Core::kScreenBottomWidth) /
(Core::kScreenTopHeight * 4);
large_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
small_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
}
} else {
if (swapped) {
emulation_aspect_ratio = Core::kScreenBottomHeight * 4 /
(Core::kScreenBottomWidth * 4.0f + Core::kScreenTopWidth);
large_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO;
small_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO;
} else {
emulation_aspect_ratio = Core::kScreenTopHeight * 4 /
(Core::kScreenTopWidth * 4.0f + Core::kScreenBottomWidth);
large_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO;
small_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);
@ -119,35 +199,49 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped) {
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);
large_screen = large_screen.TranslateX((width - total_rect.GetWidth()) / 2);
} else {
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)
.TranslateY(large_screen.GetHeight() + large_screen.top - small_screen.GetHeight());
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());
} 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());
}
res.top_screen = swapped ? small_screen : large_screen;
res.bottom_screen = swapped ? large_screen : small_screen;
return res;
}
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped) {
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}};
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
// Aspect ratio of both screens side by side
const float emulation_aspect_ratio = static_cast<float>(Core::kScreenTopHeight) /
(Core::kScreenTopWidth + Core::kScreenBottomWidth);
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 = maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
Common::Rectangle<u32> bot_screen = maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
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.
@ -160,9 +254,15 @@ FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped) {
top_screen = top_screen.TranslateY(shift_vertical);
bot_screen = bot_screen.TranslateY(shift_vertical);
}
// 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());
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;
}
@ -170,7 +270,7 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
ASSERT(width > 0);
ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}};
FramebufferLayout res{width, height, true, true, {}, {}, !Settings::values.upright_screen};
Common::Rectangle<u32> top_screen{
Settings::values.custom_top_left, Settings::values.custom_top_top,
@ -194,35 +294,69 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
int width, height;
switch (Settings::values.layout_option) {
case Settings::LayoutOption::SingleScreen:
if (Settings::values.swap_screen) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
if (Settings::values.upright_screen) {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomHeight * res_scale;
height = Core::kScreenBottomWidth * res_scale;
} else {
width = Core::kScreenTopHeight * res_scale;
height = Core::kScreenTopWidth * res_scale;
}
} else {
width = Core::kScreenTopWidth * res_scale;
height = Core::kScreenTopHeight * res_scale;
if (Settings::values.swap_screen) {
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, Settings::values.swap_screen);
layout = SingleFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::LargeScreen:
if (Settings::values.swap_screen) {
width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
height = Core::kScreenBottomHeight * res_scale;
if (Settings::values.upright_screen) {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomHeight * res_scale;
height = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
} else {
width = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
}
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
height = Core::kScreenTopHeight * res_scale;
if (Settings::values.swap_screen) {
width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
}
layout = LargeFrameLayout(width, height, Settings::values.swap_screen);
layout = LargeFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
layout = SideFrameLayout(width, height, Settings::values.swap_screen);
if (Settings::values.upright_screen) {
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,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::Default:
default:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen);
if (Settings::values.upright_screen) {
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,
Settings::values.upright_screen);
break;
}
}

View file

@ -16,6 +16,7 @@ struct FramebufferLayout {
bool bottom_screen_enabled;
Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bottom_screen;
bool is_rotated = true;
/**
* Returns the ration of pixel size of the top screen, compared to the native size of the 3DS
@ -31,7 +32,7 @@ struct FramebufferLayout {
* @param is_swapped if true, the bottom screen will be displayed above the top screen
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped);
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/**
* Factory method for constructing a FramebufferLayout with only the top or bottom screen
@ -40,7 +41,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped);
* @param is_swapped if true, the bottom screen will be displayed (and the top won't be displayed)
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped);
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/**
* Factory method for constructing a Frame with the a 4x size Top screen with a 1x size bottom
@ -51,7 +52,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped);
* @param is_swapped if true, the bottom screen will be the large display
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped);
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/**
* Factory method for constructing a Frame with the Top screen and bottom
@ -62,7 +63,7 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped);
* @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);
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/**
* Factory method for constructing a custom FramebufferLayout

View file

@ -87,6 +87,7 @@ void LogSettings() {
LogSetting("Stereoscopy_Factor3d", Settings::values.factor_3d);
LogSetting("Layout_LayoutOption", static_cast<int>(Settings::values.layout_option));
LogSetting("Layout_SwapScreen", Settings::values.swap_screen);
LogSetting("Layout_UprightScreen", Settings::values.upright_screen);
LogSetting("Utility_DumpTextures", Settings::values.dump_textures);
LogSetting("Utility_CustomTextures", Settings::values.custom_textures);
LogSetting("Audio_EnableDspLle", Settings::values.enable_dsp_lle);

View file

@ -150,6 +150,7 @@ struct Values {
LayoutOption layout_option;
bool swap_screen;
bool upright_screen;
bool custom_layout;
u16 custom_top_left;
u16 custom_top_top;

View file

@ -781,6 +781,35 @@ void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, floa
state.Apply();
}
void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w,
float h) {
const auto& texcoords = screen_info.display_texcoords;
const std::array<ScreenRectVertex, 4> vertices = {{
ScreenRectVertex(x, y, texcoords.bottom, texcoords.right),
ScreenRectVertex(x + w, y, texcoords.top, texcoords.right),
ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left),
ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left),
}};
u16 scale_factor = VideoCore::GetResolutionScaleFactor();
glUniform4f(uniform_i_resolution, screen_info.texture.width * scale_factor,
screen_info.texture.height * scale_factor,
1.0 / (screen_info.texture.width * scale_factor),
1.0 / (screen_info.texture.height * scale_factor));
glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h);
state.texture_units[0].texture_2d = screen_info.display_texture;
state.texture_units[0].sampler = filter_sampler.handle;
state.Apply();
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
state.texture_units[0].texture_2d = 0;
state.texture_units[0].sampler = 0;
state.Apply();
}
/**
* Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD
* rotation.
@ -819,6 +848,40 @@ void RendererOpenGL::DrawSingleScreenStereoRotated(const ScreenInfo& screen_info
state.Apply();
}
void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
const ScreenInfo& screen_info_r, float x, float y,
float w, float h) {
const auto& texcoords = screen_info_l.display_texcoords;
const std::array<ScreenRectVertex, 4> vertices = {{
ScreenRectVertex(x, y, texcoords.bottom, texcoords.right),
ScreenRectVertex(x + w, y, texcoords.top, texcoords.right),
ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left),
ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left),
}};
u16 scale_factor = VideoCore::GetResolutionScaleFactor();
glUniform4f(uniform_i_resolution, screen_info_l.texture.width * scale_factor,
screen_info_l.texture.height * scale_factor,
1.0 / (screen_info_l.texture.width * scale_factor),
1.0 / (screen_info_l.texture.height * scale_factor));
glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h);
state.texture_units[0].texture_2d = screen_info_l.display_texture;
state.texture_units[1].texture_2d = screen_info_r.display_texture;
state.texture_units[0].sampler = filter_sampler.handle;
state.texture_units[1].sampler = filter_sampler.handle;
state.Apply();
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
state.texture_units[0].texture_2d = 0;
state.texture_units[1].texture_2d = 0;
state.texture_units[0].sampler = 0;
state.texture_units[1].sampler = 0;
state.Apply();
}
/**
* Draws the emulated screens to the emulator window.
*/
@ -866,44 +929,85 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
glUniform1i(uniform_layer, 0);
if (layout.top_screen_enabled) {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left, (float)top_screen.top,
(float)top_screen.GetWidth(), (float)top_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2,
(float)top_screen.top, (float)top_screen.GetWidth() / 2,
(float)top_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreenRotated(screen_infos[1],
((float)top_screen.left / 2) + ((float)layout.width / 2),
(float)top_screen.top, (float)top_screen.GetWidth() / 2,
(float)top_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereoRotated(screen_infos[0], screen_infos[1], (float)top_screen.left,
(float)top_screen.top, (float)top_screen.GetWidth(),
(float)top_screen.GetHeight());
if (layout.is_rotated) {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left,
(float)top_screen.top, (float)top_screen.GetWidth(),
(float)top_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2,
(float)top_screen.top, (float)top_screen.GetWidth() / 2,
(float)top_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreenRotated(screen_infos[1],
((float)top_screen.left / 2) + ((float)layout.width / 2),
(float)top_screen.top, (float)top_screen.GetWidth() / 2,
(float)top_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereoRotated(
screen_infos[0], screen_infos[1], (float)top_screen.left, (float)top_screen.top,
(float)top_screen.GetWidth(), (float)top_screen.GetHeight());
}
} else {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreen(screen_infos[0], (float)top_screen.left, (float)top_screen.top,
(float)top_screen.GetWidth(), (float)top_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreen(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top,
(float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreen(screen_infos[1],
((float)top_screen.left / 2) + ((float)layout.width / 2),
(float)top_screen.top, (float)top_screen.GetWidth() / 2,
(float)top_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereo(screen_infos[0], screen_infos[1], (float)top_screen.left,
(float)top_screen.top, (float)top_screen.GetWidth(),
(float)top_screen.GetHeight());
}
}
}
glUniform1i(uniform_layer, 0);
if (layout.bottom_screen_enabled) {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left,
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left / 2,
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
(float)bottom_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreenRotated(screen_infos[2],
((float)bottom_screen.left / 2) + ((float)layout.width / 2),
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
(float)bottom_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereoRotated(screen_infos[2], screen_infos[2],
(float)bottom_screen.left, (float)bottom_screen.top,
(float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
if (layout.is_rotated) {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left,
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreenRotated(
screen_infos[2], (float)bottom_screen.left / 2, (float)bottom_screen.top,
(float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreenRotated(
screen_infos[2], ((float)bottom_screen.left / 2) + ((float)layout.width / 2),
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
(float)bottom_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereoRotated(screen_infos[2], screen_infos[2],
(float)bottom_screen.left, (float)bottom_screen.top,
(float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
}
} else {
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
DrawSingleScreen(screen_infos[2], (float)bottom_screen.left,
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
DrawSingleScreen(screen_infos[2], (float)bottom_screen.left / 2,
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
(float)bottom_screen.GetHeight());
glUniform1i(uniform_layer, 1);
DrawSingleScreen(screen_infos[2],
((float)bottom_screen.left / 2) + ((float)layout.width / 2),
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
(float)bottom_screen.GetHeight());
} else if (stereo_single_screen) {
DrawSingleScreenStereo(screen_infos[2], screen_infos[2], (float)bottom_screen.left,
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
(float)bottom_screen.GetHeight());
}
}
}
}

View file

@ -77,9 +77,12 @@ private:
const GPU::Regs::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout);
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
const ScreenInfo& screen_info_r, float x, float y, float w,
float h);
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
float x, float y, float w, float h);
void UpdateFramerate();
// Loads framebuffer from emulated memory into the display information structure