From 69fdb03dc237c49890092e514a68ae89e4e37aa4 Mon Sep 17 00:00:00 2001 From: M4 <70843316+shadowbeat070@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:59:56 +0100 Subject: [PATCH] Launcher display options (#1938) * launcher: add minimize and maximize buttons to the main window The main window is a QDialog, which on Windows only gets a close button. * graphics: keep the game's aspect ratio when presenting to the window The guest frame was blitted across the whole swapchain image, so a window or fullscreen display whose shape differs from the frame stretched the picture. Fit the frame inside the window instead and clear the remaining bars to black. The video-out overlay layer is drawn into the same rectangle so it stays aligned with the frame underneath. * launcher: accept custom screen resolutions such as 21:9 The resolution was a fixed enum of four 16:9 sizes. Make the combo box editable so any WIDTHxHEIGHT between 320 and 16384 can be entered, and add ultrawide presets (2560x1080, 3440x1440, 3840x1600, 5120x2160). Settings saved by older launchers stored the enum key ("R1920X1080"); NormalizeResolution still accepts that form, so they load unchanged. * launcher: add a Stretch to window option Presentation now keeps the game's aspect ratio with black bars. Add --stretch to fill the whole window as before, exposed in the launcher as a "Stretch to window" checkbox under General, off by default. * launcher: make the settings dialog resizable and its tabs scrollable The dialog had no maximize button and pinned its minimum width to its initial 900 px, and without scroll areas it could not be made shorter than its content. Add a maximize button (not minimize, since the dialog is modal), drop the width pin, and wrap each tab page in a frameless scroll area. With the scroll areas the layout's minimum size no longer forces the dialog open, so size it explicitly: grow it to fit the largest tab, capped at the available screen, when there is no saved geometry and whenever the global settings add the game folders panel. * launcher: simplify display options and preserve saved settings --------- Co-authored-by: nmzik --- src/common/emulatorConfig.cpp | 4 ++ src/common/emulatorConfig.h | 2 + .../presentation/window/swapchain.cpp | 63 ++++++++++++---- .../forms/configuration_edit_dialog.ui | 18 ++++- src/launcher/include/configuration.h | 28 +++----- .../include/configurationEditDialog.h | 4 ++ src/launcher/src/configuration.cpp | 27 ++++++- src/launcher/src/configurationEditDialog.cpp | 72 ++++++++++++++++--- src/launcher/src/mainDialog.cpp | 6 +- src/main.cpp | 3 + 10 files changed, 181 insertions(+), 46 deletions(-) diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index 8c16a4bf2..09c8711dd 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -105,6 +105,10 @@ bool HideCursorEnabled() { return g_config->hide_cursor_enabled; } +bool StretchEnabled() { + return g_config->stretch_enabled; +} + bool VrEnabled() { return g_config->vr_enabled; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 56c026d84..47a6b9ab6 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -64,6 +64,7 @@ struct ConfigOptions { bool fullscreen_enabled = false; bool hdr_enabled = false; bool hide_cursor_enabled = false; + bool stretch_enabled = false; bool splash_enabled = true; bool vr_enabled = false; bool amd_cpu_enabled = false; @@ -125,6 +126,7 @@ int32_t GetGpuIndex(); bool FullscreenEnabled(); bool HdrEnabled(); bool HideCursorEnabled(); +bool StretchEnabled(); bool VrEnabled(); bool AmdCpuEnabled(); bool IsNetworkConnected(); diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index 22b56b261..46eb79c88 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -290,7 +290,8 @@ public: private: vk::SurfaceFormatKHR SelectFormat(); void Destroy(); - void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer); + void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer, + const vk::Rect2D& rect); WindowContext& m_window; vk::SwapchainKHR m_handle = nullptr; @@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() { return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount()); } -void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) { +static vk::Rect2D FitAspect(const vk::Extent3D& source, const vk::Extent2D& target) { + const uint64_t src_w = std::max(source.width, 1u); + const uint64_t src_h = std::max(source.height, 1u); + uint32_t w = target.width; + uint32_t h = target.height; + if (src_w * h > src_h * w) { + h = std::max(1, static_cast(src_h * w / src_w)); + } else { + w = std::max(1, static_cast(src_w * h / src_h)); + } + return {{static_cast((target.width - w) / 2), + static_cast((target.height - h) / 2)}, + {w, h}}; +} + +void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer, + const vk::Rect2D& rect) { auto device = m_window.graphic_ctx.device; if (m_overlay_pipeline == nullptr) { vk::SamplerCreateInfo sampler {}; @@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l rendering.colorAttachmentCount = 1; rendering.pColorAttachments = &attachment; command.beginRendering(&rendering); - const vk::Viewport viewport { - 0.0f, 0.0f, static_cast(m_extent.width), static_cast(m_extent.height), - 0.0f, 1.0f}; - const vk::Rect2D scissor {{0, 0}, m_extent}; + const vk::Viewport viewport {static_cast(rect.offset.x), + static_cast(rect.offset.y), + static_cast(rect.extent.width), + static_cast(rect.extent.height), + 0.0f, + 1.0f}; command.setViewport(0, 1, &viewport); - command.setScissor(0, 1, &scissor); + command.setScissor(0, 1, &rect); command.draw(3, 1, 0, 0); command.endRendering(); } @@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame* vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &to_transfer); + const auto* aspect_source = source != nullptr ? source : overlay.frame; + const auto game_rect = !Config::StretchEnabled() && aspect_source != nullptr + ? FitAspect(aspect_source->image.extent, m_extent) + : vk::Rect2D {{0, 0}, m_extent}; + if (source == nullptr || game_rect.extent != m_extent) { + const vk::ClearColorValue black {}; + vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, + &black, 1, &to_transfer.subresourceRange); + if (source != nullptr) { + vk::ImageMemoryBarrier after_clear = to_transfer; + after_clear.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + after_clear.oldLayout = vk::ImageLayout::eTransferDstOptimal; + vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, + &after_clear); + } + } if (source != nullptr) { vk::ImageBlit region {}; region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; @@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame* region.dstSubresource.mipLevel = 0; region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.layerCount = 1; - region.dstOffsets[1].x = static_cast(m_extent.width); - region.dstOffsets[1].y = static_cast(m_extent.height); + region.dstOffsets[0].x = game_rect.offset.x; + region.dstOffsets[0].y = game_rect.offset.y; + region.dstOffsets[1].x = game_rect.offset.x + static_cast(game_rect.extent.width); + region.dstOffsets[1].y = game_rect.offset.y + static_cast(game_rect.extent.height); region.dstOffsets[1].z = 1; vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal, m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1, ®ion, vk::Filter::eLinear); - } else { - const vk::ClearColorValue black {}; - vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, - &black, 1, &to_transfer.subresourceRange); } vk::ImageMemoryBarrier to_present {}; @@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame* &to_present); if (draw_attachment) { if (draw_overlay) { - DrawOverlay(vk_command, overlay); + DrawOverlay(vk_command, overlay, game_rect); if (draw_system_overlay) { to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal; diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui index efc2a531a..d10bc44dc 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -115,7 +115,17 @@ - Window resolution + Window resolution as WIDTHxHEIGHT; pick a preset or type a custom size. Non-16:9 sizes such as 21:9 show the game with black bars unless Stretch to window is on. + true + QComboBox::NoInsert + 1280x720 + 1920x1080 + 2560x1440 + 3840x2160 + 2560x1080 + 3440x1440 + 3840x1600 + 5120x2160 @@ -256,6 +266,12 @@ Auto-hide cursor + + + Stretch the game image to fill the window instead of keeping its aspect ratio with black bars + Stretch to window + + Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view. diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 74329e16f..3ab86bc10 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -21,10 +21,7 @@ inline QStringList EnumToList() { auto me = QMetaEnum::fromType(); int count = me.keyCount(); for (int i = 0; i < count; i++) { - auto key = QString(me.key(i)); - ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() - ? key.remove('R').toLower() - : key); + ret << QString(me.key(i)); } return ret; } @@ -32,16 +29,12 @@ inline QStringList EnumToList() { template T TextToEnum(const QString& text) { auto me = QMetaEnum::fromType(); - return static_cast(me.keyToValue( - ((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data())); + return static_cast(me.keyToValue(text.toUtf8().constData())); } template QString EnumToText(T value) { - auto me = QMetaEnum::fromType(); - auto key = QString(me.valueToKey(static_cast(value))); - return (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() ? key.remove('R').toLower() - : key); + return QString(QMetaEnum::fromType().valueToKey(static_cast(value))); } struct ControllerSettings { @@ -80,13 +73,10 @@ public: static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1; static constexpr int MAX_CONSOLE_LANGUAGE = 29; - enum class Resolution { - R1280X720, - R1920X1080, - R2560X1440, - R3840X2160, - }; - Q_ENUM(Resolution) + static constexpr int MIN_SCREEN_SIZE = 320; + static constexpr int MAX_SCREEN_SIZE = 16384; + + static QString NormalizeResolution(const QString& text); enum class ShaderOptimizationType { None, Size, Performance }; Q_ENUM(ShaderOptimizationType) @@ -117,7 +107,7 @@ public: // Controller preferences always come from the global configuration. ControllerSettings controller; - Resolution screen_resolution = Resolution::R1280X720; + QString screen_resolution = QStringLiteral("1280x720"); QString user_name = "Kyty"; int user_id = Config::DEFAULT_USER_ID; QString audio_input_device; @@ -126,6 +116,7 @@ public: bool fullscreen_enabled = false; bool hdr_enabled = false; bool hide_cursor_enabled = false; + bool stretch_enabled = false; bool readback_linear_images = false; bool readback_tiled_images = false; bool sync_raw_image_buffers = false; @@ -168,6 +159,7 @@ public: fullscreen_enabled = other.fullscreen_enabled; hdr_enabled = other.hdr_enabled; hide_cursor_enabled = other.hide_cursor_enabled; + stretch_enabled = other.stretch_enabled; readback_linear_images = other.readback_linear_images; readback_tiled_images = other.readback_tiled_images; sync_raw_image_buffers = other.sync_raw_image_buffers; diff --git a/src/launcher/include/configurationEditDialog.h b/src/launcher/include/configurationEditDialog.h index 2408e9f21..cdee8ca03 100644 --- a/src/launcher/include/configurationEditDialog.h +++ b/src/launcher/include/configurationEditDialog.h @@ -46,11 +46,15 @@ private: QLineEdit* m_addons_dir_edit = nullptr; QToolButton* m_remove_game_dir_button = nullptr; bool m_global_settings = false; + bool m_geometry_restored = false; protected: void Init(const Configuration& info); void InitGameDirectories(); void AddGameDirectoryItem(const QString& dir); + void MakeTabsScrollable(); + void GrowToFitContents(); + void ShowTab(QWidget* page); void moveEvent(QMoveEvent* event) override; void resizeEvent(QResizeEvent* event) override; diff --git a/src/launcher/src/configuration.cpp b/src/launcher/src/configuration.cpp index 0b3b7aa85..ad07c12d6 100644 --- a/src/launcher/src/configuration.cpp +++ b/src/launcher/src/configuration.cpp @@ -1,5 +1,23 @@ #include "configuration.h" +#include + +QString Configuration::NormalizeResolution(const QString& text) { + // Older settings stored the enum key, e.g. "R1920X1080". + static const QRegularExpression re(QStringLiteral("^\\s*R?(\\d+)\\s*[xX]\\s*(\\d+)\\s*$")); + const auto match = re.match(text); + if (!match.hasMatch()) { + return {}; + } + const int width = match.captured(1).toInt(); + const int height = match.captured(2).toInt(); + if (width < MIN_SCREEN_SIZE || height < MIN_SCREEN_SIZE || width > MAX_SCREEN_SIZE || + height > MAX_SCREEN_SIZE) { + return {}; + } + return QStringLiteral("%1x%2").arg(width).arg(height); +} + QVariantMap Configuration::GameSettings() const { QVariantMap values; #define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString()) @@ -12,6 +30,7 @@ QVariantMap Configuration::GameSettings() const { KYTY_CFG_SET(fullscreen_enabled); KYTY_CFG_SET(hdr_enabled); KYTY_CFG_SET(hide_cursor_enabled); + KYTY_CFG_SET(stretch_enabled); KYTY_CFG_SET(readback_linear_images); KYTY_CFG_SET(readback_tiled_images); KYTY_CFG_SET(sync_raw_image_buffers); @@ -48,7 +67,8 @@ QVariantMap Configuration::GameSettings() const { template void Configuration::ReadGameSettingsValues(const Settings& s) { #define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value() - KYTY_CFG_GET(screen_resolution); + const auto resolution = NormalizeResolution(s.value("screen_resolution").toString()); + screen_resolution = resolution.isEmpty() ? QStringLiteral("1280x720") : resolution; user_name = s.value("user_name", user_name).toString(); bool user_id_ok = false; auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok); @@ -64,6 +84,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) { KYTY_CFG_GET(fullscreen_enabled); KYTY_CFG_GET(hdr_enabled); KYTY_CFG_GET(hide_cursor_enabled); + KYTY_CFG_GET(stretch_enabled); KYTY_CFG_GET(readback_linear_images); KYTY_CFG_GET(readback_tiled_images); KYTY_CFG_GET(sync_raw_image_buffers); @@ -134,7 +155,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error) if (!it.value().isString()) { return fail(tr("Game setting %1 must be a string.").arg(it.key())); } - values.insert(it.key(), it.value().toString()); + const auto value = it.value().toString(); + values.insert(it.key(), it.key() == "screen_resolution" ? NormalizeResolution(value) + : value); } ReadGameSettingsValues(values); diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index cb5811b17..c9de0ee79 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) { } ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent) - : QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog), - m_info(info) { + : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint), + m_ui(new Ui::ConfigurationEditDialog), m_info(info) { m_ui->setupUi(this); - setMinimumWidth(width()); + MakeTabsScrollable(); InitGameDirectories(); m_ui->controller_group->setVisible(false); m_ui->settings_button->setVisible(false); @@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p setSizeGripEnabled(true); - restoreGeometry(g_last_geometry); - Init(info); + m_geometry_restored = restoreGeometry(g_last_geometry); + if (!m_geometry_restored) { + GrowToFitContents(); + } } QByteArray ConfigurationEditDialog::g_last_geometry; @@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) { info.audio_input_device); } microphone->setCurrentIndex(microphone->findData(info.audio_input_device)); - ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution); + m_ui->comboBox_screen_resolution->setCurrentText(info.screen_resolution); ListInit(m_ui->comboBox_present_mode, info.present_mode); m_ui->comboBox_gpu->clear(); m_ui->comboBox_gpu->addItem(tr("Auto")); @@ -285,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) { m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled); m_ui->checkBox_hdr->setChecked(info.hdr_enabled); m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled); + m_ui->checkBox_stretch->setChecked(info.stretch_enabled); m_ui->checkBox_readback->setChecked(info.readback_linear_images); m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images); m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers); @@ -413,8 +418,46 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const Q m_game_dirs_group->setVisible(true); m_ui->controller_group->setVisible(true); update_game_directory_buttons(); + if (!m_geometry_restored) { + GrowToFitContents(); + } +} + +void ConfigurationEditDialog::MakeTabsScrollable() { + auto* tabs = m_ui->settings_tabs; + for (int i = 0; i < tabs->count(); i++) { + auto* page = tabs->widget(i); + auto title = tabs->tabText(i); + auto* scroll = new QScrollArea; + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + tabs->removeTab(i); + scroll->setWidget(page); + tabs->insertTab(i, scroll, title); + } + tabs->setCurrentIndex(0); +} + +void ConfigurationEditDialog::GrowToFitContents() { layout()->activate(); - resize(size().expandedTo(minimumSizeHint())); + auto* tabs = m_ui->settings_tabs; + QSize content; + for (int i = 0; i < tabs->count(); i++) { + content = content.expandedTo( + static_cast(tabs->widget(i))->widget()->minimumSizeHint()); + } + const QSize chrome = size() - tabs->currentWidget()->size(); + resize(size().expandedTo(content + chrome).boundedTo(screen()->availableGeometry().size())); +} + +void ConfigurationEditDialog::ShowTab(QWidget* page) { + auto* tabs = m_ui->settings_tabs; + for (int i = 0; i < tabs->count(); i++) { + if (tabs->widget(i)->isAncestorOf(page)) { + tabs->setCurrentIndex(i); + return; + } + } } QString ConfigurationEditDialog::GetAddonsDirectory() const { @@ -474,13 +517,14 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt(); } info.screen_resolution = - TextToEnum(ui.comboBox_screen_resolution->currentText()); + Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText()); info.present_mode = TextToEnum(ui.comboBox_present_mode->currentText()); info.gpu_index = ui.comboBox_gpu->currentIndex() - 1; info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked(); info.hdr_enabled = ui.checkBox_hdr->isChecked(); info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked(); + info.stretch_enabled = ui.checkBox_stretch->isChecked(); info.readback_linear_images = ui.checkBox_readback->isChecked(); info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked(); info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked(); @@ -518,7 +562,7 @@ void ConfigurationEditDialog::save() { for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file, m_ui->lineEdit_cmd_dump_folder}) { if (field->isEnabled() && field->text().isEmpty()) { - m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab); + ShowTab(m_ui->debug_tab); field->setFocus(); break; } @@ -540,6 +584,16 @@ void ConfigurationEditDialog::save() { tr("User ID cannot be 254 (everyone) or 255 (system)")); return; } + if (Configuration::NormalizeResolution(m_ui->comboBox_screen_resolution->currentText()) + .isEmpty()) { + ShowTab(m_ui->settings_tab); + m_ui->comboBox_screen_resolution->setFocus(); + QMessageBox::critical(this, tr("Save failed"), + tr("Screen resolution must be WIDTHxHEIGHT, each between %1 and %2") + .arg(Configuration::MIN_SCREEN_SIZE) + .arg(Configuration::MAX_SCREEN_SIZE)); + return; + } UpdateInfo(m_info, *m_ui, m_global_settings); diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index cd6e100a8..21d0af992 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -134,7 +134,8 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) { m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal. #endif - main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/); + main_dialog->setWindowFlags(Qt::Dialog | Qt::WindowMinMaxButtonsHint | + Qt::WindowCloseButtonHint); connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter, Qt::QueuedConnection); @@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) { static QStringList CreateEmulatorArgs(const Configuration& info) { QStringList args; - auto r = EnumToText(info.screen_resolution).split('x'); + auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x'); if (r.size() != 2) { return {}; @@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) { AddFlag(args, info.fullscreen_enabled, "--fullscreen"); AddFlag(args, info.hdr_enabled, "--hdr"); AddFlag(args, info.hide_cursor_enabled, "--hide-cursor"); + AddFlag(args, info.stretch_enabled, "--stretch"); AddFlag(args, info.readback_linear_images, "--readback-linear-images"); AddFlag(args, info.readback_tiled_images, "--readback-tiled-images"); AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers"); diff --git a/src/main.cpp b/src/main.cpp index 13c759301..87cab9934 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -70,6 +70,8 @@ static void PrintUsage() { ::printf(" --hdr Enable HDR10 output when supported.\n"); ::printf( " --hide-cursor Hide the cursor after 2 s idle. Default: off.\n"); + ::printf( + " --stretch Stretch the image to the window. Default: off.\n"); ::printf(" --no-splash Disable the startup splash screen.\n"); ::printf(" --vr Enable the virtual VR headset.\n"); ::printf(" --amd-cpu Apply AMD CPU instruction patches.\n"); @@ -199,6 +201,7 @@ static constexpr Flag FLAGS[] = { {"--fullscreen", &ConfigOptions::fullscreen_enabled}, {"--hdr", &ConfigOptions::hdr_enabled}, {"--hide-cursor", &ConfigOptions::hide_cursor_enabled}, + {"--stretch", &ConfigOptions::stretch_enabled}, {"--no-splash", &ConfigOptions::splash_enabled, false}, {"--vr", &ConfigOptions::vr_enabled}, {"--amd-cpu", &ConfigOptions::amd_cpu_enabled},