From 9246b4277bc462a0dd1d09ceaa27b5f6a1511cd4 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:42:31 -0700 Subject: [PATCH] config: Add present mode option. (#3502) * config: Add present mode option. * settings_dialog: Add details for present modes. --- src/common/config.cpp | 12 +++++ src/common/config.h | 2 + src/qt_gui/settings_dialog.cpp | 15 ++++++ src/qt_gui/settings_dialog.ui | 34 +++++++++++++ .../renderer_vulkan/vk_swapchain.cpp | 49 +++++++++++++------ src/video_core/renderer_vulkan/vk_swapchain.h | 4 ++ 6 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 016ba45d0..12ad47d93 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -79,6 +79,7 @@ static bool shouldPatchShaders = false; static u32 vblankDivider = 1; static bool isFullscreen = false; static std::string fullscreenMode = "Windowed"; +static std::string presentMode = "Mailbox"; static bool isHDRAllowed = false; // Vulkan @@ -194,6 +195,10 @@ std::string getFullscreenMode() { return fullscreenMode; } +std::string getPresentMode() { + return presentMode; +} + bool getisTrophyPopupDisabled() { return isTrophyPopupDisabled; } @@ -466,6 +471,10 @@ void setFullscreenMode(std::string mode) { fullscreenMode = mode; } +void setPresentMode(std::string mode) { + presentMode = mode; +} + void setisTrophyPopupDisabled(bool disable) { isTrophyPopupDisabled = disable; } @@ -726,6 +735,7 @@ void load(const std::filesystem::path& path) { vblankDivider = toml::find_or(gpu, "vblankDivider", vblankDivider); isFullscreen = toml::find_or(gpu, "Fullscreen", isFullscreen); fullscreenMode = toml::find_or(gpu, "FullscreenMode", fullscreenMode); + presentMode = toml::find_or(gpu, "presentMode", presentMode); isHDRAllowed = toml::find_or(gpu, "allowHDR", isHDRAllowed); } @@ -894,6 +904,7 @@ void save(const std::filesystem::path& path) { data["GPU"]["vblankDivider"] = vblankDivider; data["GPU"]["Fullscreen"] = isFullscreen; data["GPU"]["FullscreenMode"] = fullscreenMode; + data["GPU"]["presentMode"] = presentMode; data["GPU"]["allowHDR"] = isHDRAllowed; data["Vulkan"]["gpuId"] = gpuId; data["Vulkan"]["validation"] = vkValidation; @@ -1003,6 +1014,7 @@ void setDefaultValues() { vblankDivider = 1; isFullscreen = false; fullscreenMode = "Windowed"; + presentMode = "Mailbox"; isHDRAllowed = false; // Vulkan diff --git a/src/common/config.h b/src/common/config.h index 8a5db1dc9..2862ea12a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -27,6 +27,8 @@ bool getIsFullscreen(); void setIsFullscreen(bool enable); std::string getFullscreenMode(); void setFullscreenMode(std::string mode); +std::string getPresentMode(); +void setPresentMode(std::string mode); u32 getWindowWidth(); u32 getWindowHeight(); void setWindowWidth(u32 width); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index fbd04174f..9b04e9e95 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -67,6 +67,7 @@ const QVector languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 2 QMap channelMap; QMap logTypeMap; QMap screenModeMap; +QMap presentModeMap; QMap chooseHomeTabMap; QMap micMap; @@ -93,6 +94,9 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, screenModeMap = {{tr("Fullscreen (Borderless)"), "Fullscreen (Borderless)"}, {tr("Windowed"), "Windowed"}, {tr("Fullscreen"), "Fullscreen"}}; + presentModeMap = {{tr("Mailbox (Vsync)"), "Mailbox"}, + {tr("Fifo (Vsync)"), "Fifo"}, + {tr("Immediate (No Vsync)"), "Immediate"}}; chooseHomeTabMap = {{tr("General"), "General"}, {tr("GUI"), "GUI"}, {tr("Graphics"), "Graphics"}, {tr("User"), "User"}, {tr("Input"), "Input"}, {tr("Paths"), "Paths"}, @@ -414,6 +418,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, // Graphics ui->graphicsAdapterGroupBox->installEventFilter(this); ui->windowSizeGroupBox->installEventFilter(this); + ui->presentModeGroupBox->installEventFilter(this); ui->heightDivider->installEventFilter(this); ui->dumpShadersCheckBox->installEventFilter(this); ui->nullGpuCheckBox->installEventFilter(this); @@ -540,6 +545,9 @@ void SettingsDialog::LoadValuesFromConfig() { QString translatedText_FullscreenMode = screenModeMap.key(QString::fromStdString(Config::getFullscreenMode())); ui->displayModeComboBox->setCurrentText(translatedText_FullscreenMode); + QString translatedText_PresentMode = + presentModeMap.key(QString::fromStdString(Config::getPresentMode())); + ui->presentModeComboBox->setCurrentText(translatedText_PresentMode); ui->gameSizeCheckBox->setChecked(toml::find_or(data, "GUI", "loadGameSizeEnabled", true)); ui->showSplashCheckBox->setChecked(toml::find_or(data, "General", "showSplash", false)); QString translatedText_logType = logTypeMap.key(QString::fromStdString(Config::getLogType())); @@ -746,6 +754,11 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { // Graphics if (elementName == "graphicsAdapterGroupBox") { text = tr("Graphics Device:\\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\\nor select \"Auto Select\" to automatically determine it."); + } else if (elementName == "presentModeGroupBox") { + text = tr("Present Mode:\\nConfigures how video output will be presented to your screen.\\n\\n" + "Mailbox: Frames synchronize with your screen's refresh rate. New frames will replace any pending frames. Reduces latency but may skip frames if running behind.\\n" + "Fifo: Frames synchronize with your screen's refresh rate. New frames will be queued behind pending frames. Ensures all frames are presented but may increase latency.\\n" + "Immediate: Frames immediately present to your screen when ready. May result in tearing."); } else if (elementName == "windowSizeGroupBox") { text = tr("Width/Height:\\nSets the size of the emulator window at launch, which can be resized during gameplay.\\nThis is different from the in-game resolution."); } else if (elementName == "heightDivider") { @@ -834,6 +847,8 @@ void SettingsDialog::UpdateSettings() { "Windowed"); Config::setFullscreenMode( screenModeMap.value(ui->displayModeComboBox->currentText()).toStdString()); + Config::setPresentMode( + presentModeMap.value(ui->presentModeComboBox->currentText()).toStdString()); Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked()); Config::setBackgroundControllerInput(ui->backgroundControllerCheckBox->isChecked()); Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 8b52ce55d..36cf12be7 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1096,6 +1096,40 @@ + + + + Present Mode + + + + + + + 0 + 0 + + + + + Mailbox (Vsync) + + + + + Fifo (Vsync) + + + + + Immediate (No Vsync) + + + + + + + diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index de7bec894..4dd3bd502 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -21,6 +21,7 @@ static constexpr vk::SurfaceFormatKHR SURFACE_FORMAT_HDR = { Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window_) : instance{instance_}, window{window_}, surface{CreateSurface(instance.GetInstance(), window)} { FindPresentFormat(); + FindPresentMode(); Create(window.GetWidth(), window.GetHeight()); ImGui::Core::Initialize(instance, window, image_count, surface_format.format); @@ -45,20 +46,6 @@ void Swapchain::Create(u32 width_, u32 height_) { instance.GetPresentQueueFamilyIndex(), }; - const auto [modes_result, modes] = - instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface); - const auto find_mode = [&modes_result, &modes](vk::PresentModeKHR requested) { - if (modes_result != vk::Result::eSuccess) { - return false; - } - const auto it = - std::find_if(modes.begin(), modes.end(), - [&requested](vk::PresentModeKHR mode) { return mode == requested; }); - - return it != modes.cend(); - }; - const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox); - const bool exclusive = queue_family_indices[0] == queue_family_indices[1]; const u32 queue_family_indices_count = exclusive ? 1u : 2u; const vk::SharingMode sharing_mode = @@ -78,7 +65,7 @@ void Swapchain::Create(u32 width_, u32 height_) { .pQueueFamilyIndices = queue_family_indices.data(), .preTransform = transform, .compositeAlpha = composite_alpha, - .presentMode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate, + .presentMode = present_mode, .clipped = true, .oldSwapchain = nullptr, }; @@ -202,6 +189,38 @@ void Swapchain::FindPresentFormat() { UNREACHABLE_MSG("Unable to find required swapchain format!"); } +void Swapchain::FindPresentMode() { + const auto [modes_result, modes] = + instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface); + if (modes_result != vk::Result::eSuccess) { + LOG_ERROR(Render, "Failed to query available present modes, falling back to Fifo as " + "guaranteed supported option."); + present_mode = vk::PresentModeKHR::eFifo; + return; + } + + const auto requested_mode = Config::getPresentMode(); + if (requested_mode == "Mailbox") { + present_mode = vk::PresentModeKHR::eMailbox; + } else if (requested_mode == "Fifo") { + present_mode = vk::PresentModeKHR::eFifo; + } else if (requested_mode == "Immediate") { + present_mode = vk::PresentModeKHR::eImmediate; + } else { + LOG_ERROR(Render_Vulkan, "Unknown present mode {}, defaulting to Mailbox.", + Config::getPresentMode()); + present_mode = vk::PresentModeKHR::eMailbox; + } + + if (std::ranges::find(modes, present_mode) == modes.cend()) { + // FIFO is guaranteed to be supported by the Vulkan spec. + constexpr auto fallback = vk::PresentModeKHR::eFifo; + LOG_WARNING(Render, "Requested present mode {} is not supported, falling back to {}.", + vk::to_string(present_mode), vk::to_string(fallback)); + present_mode = fallback; + } +} + void Swapchain::SetSurfaceProperties() { const auto [capabilities_result, capabilities] = instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 7944566fa..826705d40 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -96,6 +96,9 @@ private: /// Selects the best available swapchain image format void FindPresentFormat(); + /// Selects the best available present mode + void FindPresentMode(); + /// Sets the surface properties according to device capabilities void SetSurfaceProperties(); @@ -115,6 +118,7 @@ private: vk::SurfaceKHR surface{}; vk::SurfaceFormatKHR surface_format; vk::Format view_format; + vk::PresentModeKHR present_mode; vk::Extent2D extent; vk::SurfaceTransformFlagBitsKHR transform; vk::CompositeAlphaFlagBitsKHR composite_alpha;