From a1d7d214cfb7491b8ba54081b5c40825055f4bc7 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 3 Mar 2024 12:25:37 +1000 Subject: [PATCH] GPUDevice: Add support for VRR and relaxed vsync --- src/core/fullscreen_ui.cpp | 7 +- src/core/host.cpp | 14 +- src/core/settings.cpp | 44 ++- src/core/settings.h | 12 +- src/core/system.cpp | 36 ++- src/core/system.h | 2 +- src/duckstation-qt/graphicssettingswidget.cpp | 132 +++++---- src/duckstation-qt/graphicssettingswidget.ui | 276 +++++++++--------- src/duckstation-qt/qthost.cpp | 2 +- src/util/CMakeLists.txt | 1 + src/util/d3d11_device.cpp | 19 +- src/util/d3d11_device.h | 2 - src/util/d3d12_device.cpp | 12 +- src/util/d3d12_device.h | 2 - src/util/gpu_device.cpp | 12 +- src/util/gpu_device.h | 24 +- src/util/gpu_types.h | 24 ++ src/util/metal_device.h | 2 +- src/util/metal_device.mm | 10 +- src/util/opengl_device.cpp | 9 +- src/util/opengl_device.h | 2 +- src/util/util.vcxproj | 1 + src/util/util.vcxproj.filters | 1 + src/util/vulkan_device.cpp | 19 +- src/util/vulkan_device.h | 2 +- src/util/vulkan_swap_chain.cpp | 152 +++++----- src/util/vulkan_swap_chain.h | 19 +- 27 files changed, 474 insertions(+), 364 deletions(-) create mode 100644 src/util/gpu_types.h diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 72b5541ae..c756d9236 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -3879,10 +3879,11 @@ void FullscreenUI::DrawDisplaySettingsPage() "GPU", "UseSoftwareRendererForReadbacks", false); } - DrawToggleSetting( - bsi, FSUI_CSTR("Enable VSync"), + DrawEnumSetting( + bsi, FSUI_CSTR("VSync"), FSUI_CSTR("Synchronizes presentation of the console's frames to the host. Enable for smoother animations."), - "Display", "VSync", Settings::DEFAULT_VSYNC_VALUE); + "Display", "SyncMode", Settings::DEFAULT_DISPLAY_SYNC_MODE, &Settings::ParseDisplaySyncMode, + &Settings::GetDisplaySyncModeName, &Settings::GetDisplaySyncModeDisplayName, DisplaySyncMode::Count); DrawToggleSetting( bsi, FSUI_CSTR("Sync To Host Refresh Rate"), diff --git a/src/core/host.cpp b/src/core/host.cpp index 73d618eb7..87a555299 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -265,15 +265,13 @@ bool Host::CreateGPUDevice(RenderAPI api) if (g_settings.gpu_disable_texture_copy_to_self) disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF; - // TODO: FSUI should always use vsync.. Error error; - const bool vsync = System::IsValid() ? System::ShouldUseVSync() : g_settings.video_sync_enabled; - if (!g_gpu_device || !g_gpu_device->Create(g_settings.gpu_adapter, - g_settings.gpu_disable_shader_cache ? std::string_view() : - std::string_view(EmuFolders::Cache), - SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, vsync, - g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, - static_cast(disabled_features), &error)) + if (!g_gpu_device || !g_gpu_device->Create( + g_settings.gpu_adapter, + g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache), + SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveDisplaySyncMode(), + g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, + static_cast(disabled_features), &error)) { Log_ErrorPrintf("Failed to create GPU device."); if (g_gpu_device) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 448858ca3..6b722cd23 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -243,6 +243,10 @@ void Settings::Load(SettingsInterface& si) display_scaling = ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str()) .value_or(DEFAULT_DISPLAY_SCALING); + display_sync_mode = + ParseDisplaySyncMode( + si.GetStringValue("Display", "SyncMode", GetDisplaySyncModeName(DEFAULT_DISPLAY_SYNC_MODE)).c_str()) + .value_or(DEFAULT_DISPLAY_SYNC_MODE); display_exclusive_fullscreen_control = ParseDisplayExclusiveFullscreenControl( si.GetStringValue("Display", "ExclusiveFullscreenControl", @@ -279,7 +283,6 @@ void Settings::Load(SettingsInterface& si) display_show_enhancements = si.GetBoolValue("Display", "ShowEnhancements", false); display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false); display_stretch_vertically = si.GetBoolValue("Display", "StretchVertically", false); - video_sync_enabled = si.GetBoolValue("Display", "VSync", DEFAULT_VSYNC_VALUE); display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS); display_osd_scale = si.GetFloatValue("Display", "OSDScale", DEFAULT_OSD_SCALE); @@ -504,6 +507,7 @@ void Settings::Save(SettingsInterface& si) const si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio)); si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment)); si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling)); + si.SetStringValue("Display", "SyncMode", GetDisplaySyncModeName(display_sync_mode)); si.SetStringValue("Display", "ExclusiveFullscreenControl", GetDisplayExclusiveFullscreenControlName(display_exclusive_fullscreen_control)); si.SetStringValue("Display", "ScreenshotMode", GetDisplayScreenshotModeName(display_screenshot_mode)); @@ -524,7 +528,6 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Display", "ShowEnhancements", display_show_enhancements); si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames); si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically); - si.SetBoolValue("Display", "VSync", video_sync_enabled); si.SetFloatValue("Display", "MaxFPS", display_max_fps); si.SetFloatValue("Display", "OSDScale", display_osd_scale); @@ -1357,6 +1360,43 @@ const char* Settings::GetDisplayScalingDisplayName(DisplayScalingMode mode) return Host::TranslateToCString("DisplayScalingMode", s_display_scaling_display_names[static_cast(mode)]); } +static constexpr const std::array s_display_sync_mode_names = { + "Disabled", + "VSync", + "VSyncRelaxed", + "VRR", +}; +static constexpr const std::array s_display_sync_mode_display_names = { + TRANSLATE_NOOP("Settings", "Disabled"), + TRANSLATE_NOOP("Settings", "VSync"), + TRANSLATE_NOOP("Settings", "Relaxed VSync"), + TRANSLATE_NOOP("Settings", "VRR/FreeSync/GSync"), +}; + +std::optional Settings::ParseDisplaySyncMode(const char* str) +{ + int index = 0; + for (const char* name : s_display_sync_mode_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetDisplaySyncModeName(DisplaySyncMode mode) +{ + return s_display_sync_mode_names[static_cast(mode)]; +} + +const char* Settings::GetDisplaySyncModeDisplayName(DisplaySyncMode mode) +{ + return Host::TranslateToCString("Settings", s_display_sync_mode_display_names[static_cast(mode)]); +} + static constexpr const std::array s_display_exclusive_fullscreen_mode_names = { "Automatic", "Disallowed", diff --git a/src/core/settings.h b/src/core/settings.h index 73febe371..34cca1e18 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,6 +6,7 @@ #include "types.h" #include "util/audio_stream.h" +#include "util/gpu_types.h" #include "common/log.h" #include "common/settings_interface.h" @@ -18,8 +19,6 @@ #include #include -enum class RenderAPI : u32; - struct SettingInfo { enum class Type @@ -134,6 +133,7 @@ struct Settings DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO; DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT; DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING; + DisplaySyncMode display_sync_mode = DEFAULT_DISPLAY_SYNC_MODE; DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL; DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE; DisplayScreenshotFormat display_screenshot_format = DEFAULT_DISPLAY_SCREENSHOT_FORMAT; @@ -159,7 +159,6 @@ struct Settings bool display_show_enhancements : 1 = false; bool display_all_frames : 1 = false; bool display_stretch_vertically : 1 = false; - bool video_sync_enabled = DEFAULT_VSYNC_VALUE; float display_osd_scale = 100.0f; float display_max_fps = DEFAULT_DISPLAY_MAX_FPS; float gpu_pgxp_tolerance = -1.0f; @@ -411,6 +410,10 @@ struct Settings static const char* GetDisplayScalingName(DisplayScalingMode mode); static const char* GetDisplayScalingDisplayName(DisplayScalingMode mode); + static std::optional ParseDisplaySyncMode(const char* str); + static const char* GetDisplaySyncModeName(DisplaySyncMode mode); + static const char* GetDisplaySyncModeDisplayName(DisplaySyncMode mode); + static std::optional ParseDisplayExclusiveFullscreenControl(const char* str); static const char* GetDisplayExclusiveFullscreenControlName(DisplayExclusiveFullscreenControl mode); static const char* GetDisplayExclusiveFullscreenControlDisplayName(DisplayExclusiveFullscreenControl mode); @@ -484,6 +487,7 @@ struct Settings static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto; static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center; static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth; + static constexpr DisplaySyncMode DEFAULT_DISPLAY_SYNC_MODE = DisplaySyncMode::Disabled; static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL = DisplayExclusiveFullscreenControl::Automatic; static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution; @@ -525,13 +529,11 @@ struct Settings #ifndef __ANDROID__ static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true; static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = true; - static constexpr bool DEFAULT_VSYNC_VALUE = false; static constexpr bool DEFAULT_FAST_BOOT_VALUE = false; static constexpr float DEFAULT_DISPLAY_MAX_FPS = 0.0f; #else static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true; static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false; - static constexpr bool DEFAULT_VSYNC_VALUE = false; static constexpr bool DEFAULT_FAST_BOOT_VALUE = true; static constexpr float DEFAULT_DISPLAY_MAX_FPS = 60.0f; #endif diff --git a/src/core/system.cpp b/src/core/system.cpp index 42da3513e..6bd992795 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1849,6 +1849,12 @@ void System::FrameDone() SaveRunaheadState(); } + // TODO: Kick cmdbuffer early + const DisplaySyncMode sync_mode = g_gpu_device->GetSyncMode(); + const bool throttle_after_present = (sync_mode == DisplaySyncMode::Disabled); + if (!throttle_after_present && s_throttler_enabled && !IsExecutionInterrupted()) + Throttle(); + const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); if (current_time < s_next_frame_time || s_display_all_frames || s_last_frame_skipped) { @@ -1860,7 +1866,7 @@ void System::FrameDone() s_last_frame_skipped = true; } - if (s_throttler_enabled && !IsExecutionInterrupted()) + if (throttle_after_present && s_throttler_enabled && !IsExecutionInterrupted()) Throttle(); // Input poll already done above @@ -2638,10 +2644,14 @@ void System::UpdateSpeedLimiterState() } // When syncing to host and using vsync, we don't need to sleep. - if (s_syncing_to_host && ShouldUseVSync() && s_display_all_frames) + if (s_syncing_to_host && s_display_all_frames) { - Log_InfoPrintf("Using host vsync for throttling."); - s_throttler_enabled = false; + const DisplaySyncMode effective_sync_mode = GetEffectiveDisplaySyncMode(); + if (effective_sync_mode == DisplaySyncMode::VSync || effective_sync_mode == DisplaySyncMode::VSyncRelaxed) + { + Log_InfoPrintf("Using host vsync for throttling."); + s_throttler_enabled = false; + } } Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f); @@ -2674,21 +2684,25 @@ void System::UpdateSpeedLimiterState() void System::UpdateDisplaySync() { - const bool video_sync_enabled = ShouldUseVSync(); - const bool syncing_to_host_vsync = (s_syncing_to_host && video_sync_enabled && s_display_all_frames); + const DisplaySyncMode display_sync_mode = GetEffectiveDisplaySyncMode(); + const bool syncing_to_host_vsync = + (s_syncing_to_host && + (display_sync_mode == DisplaySyncMode::VSync || display_sync_mode == DisplaySyncMode::VSyncRelaxed) && + s_display_all_frames); const float max_display_fps = (s_throttler_enabled || s_syncing_to_host) ? 0.0f : g_settings.display_max_fps; - Log_VerbosePrintf("Using vsync: %s%s", video_sync_enabled ? "YES" : "NO", + Log_VerbosePrintf("Display sync: %s%s", Settings::GetDisplaySyncModeDisplayName(display_sync_mode), syncing_to_host_vsync ? " (for throttling)" : ""); Log_VerbosePrintf("Max display fps: %f (%s)", max_display_fps, s_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed"); g_gpu_device->SetDisplayMaxFPS(max_display_fps); - g_gpu_device->SetVSync(video_sync_enabled); + g_gpu_device->SetSyncMode(display_sync_mode); } -bool System::ShouldUseVSync() +DisplaySyncMode System::GetEffectiveDisplaySyncMode() { - return g_settings.video_sync_enabled && !IsRunningAtNonStandardSpeed(); + // Disable vsync if running outside 100%. + return (IsValid() && IsRunningAtNonStandardSpeed()) ? DisplaySyncMode::Disabled : g_settings.display_sync_mode; } bool System::IsFastForwardEnabled() @@ -3737,7 +3751,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) DMA::SetHaltTicks(g_settings.dma_halt_ticks); if (g_settings.audio_backend != old_settings.audio_backend || - g_settings.video_sync_enabled != old_settings.video_sync_enabled || + g_settings.display_sync_mode != old_settings.display_sync_mode || g_settings.increase_timer_resolution != old_settings.increase_timer_resolution || g_settings.emulation_speed != old_settings.emulation_speed || g_settings.fast_forward_speed != old_settings.fast_forward_speed || diff --git a/src/core/system.h b/src/core/system.h index 0aa7b6f66..a0495bc73 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -455,7 +455,7 @@ void ToggleWidescreen(); bool IsRunningAtNonStandardSpeed(); /// Returns true if vsync should be used. -bool ShouldUseVSync(); +DisplaySyncMode GetEffectiveDisplaySyncMode(); /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index 034502ea4..2706361d6 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -51,8 +51,13 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName, &Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false); - + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", + &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, + Settings::DEFAULT_GPU_TEXTURE_FILTER); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", + &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, + Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio", &Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName, Settings::DEFAULT_DISPLAY_ASPECT_RATIO); @@ -67,13 +72,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling", &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName, Settings::DEFAULT_DISPLAY_SCALING); - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1); - SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", - &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, - Settings::DEFAULT_GPU_TEXTURE_FILTER); - SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode", - &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName, - Settings::DEFAULT_GPU_DOWNSAMPLE_MODE); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displaySyncMode, "Display", "SyncMode", + &Settings::ParseDisplaySyncMode, &Settings::GetDisplaySyncModeName, + Settings::DEFAULT_DISPLAY_SYNC_MODE); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true); @@ -243,9 +244,23 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* "renderers.
This option is only supported in Direct3D and Vulkan. OpenGL will always use the default " "device.")); dialog->registerWidgetHelp( - m_ui.vsync, tr("VSync"), tr("Unchecked"), - tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. " - "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed).")); + m_ui.resolutionScale, tr("Internal Resolution"), "1x", + tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies " + "to the hardware backends.
This option is usually safe, with most games looking fine at " + "higher resolutions. Higher resolutions require a more powerful GPU.")); + dialog->registerWidgetHelp( + m_ui.gpuDownsampleMode, tr("Down-Sampling"), tr("Disabled"), + tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, " + "but should be disabled for pure 3D games. Only applies to the hardware renderers.")); + dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"), + tr("Selects the resolution scale that will be applied to the final image. 1x will " + "downsample to the original console resolution.")); + dialog->registerWidgetHelp( + m_ui.textureFiltering, tr("Texture Filtering"), + QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)), + tr("Smooths out the blockiness of magnified textures on 3D object by using filtering.
Will have a " + "greater effect on higher resolution scales. Only applies to the hardware renderers.
The JINC2 and " + "especially xBR filtering modes are very demanding, and may not be worth the speed penalty.")); dialog->registerWidgetHelp( m_ui.displayAspectRatio, tr("Aspect Ratio"), QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)), @@ -261,27 +276,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* m_ui.displayScaling, tr("Scaling"), tr("Bilinear (Smooth)"), tr("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution.")); dialog->registerWidgetHelp( - m_ui.resolutionScale, tr("Internal Resolution"), "1x", - tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies " - "to the hardware backends.
This option is usually safe, with most games looking fine at " - "higher resolutions. Higher resolutions require a more powerful GPU.")); - dialog->registerWidgetHelp( - m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"), - tr("Uses multi-sampled anti-aliasing when rendering 3D polygons. Can improve visuals with a lower performance " - "requirement compared to upscaling, but often introduces rendering errors.")); - dialog->registerWidgetHelp( - m_ui.gpuDownsampleMode, tr("Down-Sampling"), tr("Disabled"), - tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, " - "but should be disabled for pure 3D games. Only applies to the hardware renderers.")); - dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"), - tr("Selects the resolution scale that will be applied to the final image. 1x will " - "downsample to the original console resolution.")); - dialog->registerWidgetHelp( - m_ui.textureFiltering, tr("Texture Filtering"), - QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)), - tr("Smooths out the blockiness of magnified textures on 3D object by using filtering.
Will have a " - "greater effect on higher resolution scales. Only applies to the hardware renderers.
The JINC2 and " - "especially xBR filtering modes are very demanding, and may not be worth the speed penalty.")); + m_ui.displaySyncMode, tr("VSync"), tr("Unchecked"), + tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. " + "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed).")); dialog->registerWidgetHelp( m_ui.trueColor, tr("True Color Rendering"), tr("Checked"), tr("Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per " @@ -354,9 +351,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* QString::fromUtf8(Settings::GetLineDetectModeName(Settings::DEFAULT_GPU_LINE_DETECT_MODE)), tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization " "behavior, filling in gaps introduced by upscaling.")); - dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"), - tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a " - "replacement or an overlay.")); + dialog->registerWidgetHelp( + m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"), + tr("Uses multi-sampled anti-aliasing when rendering 3D polygons. Can improve visuals with a lower performance " + "requirement compared to upscaling, but often introduces rendering errors.")); dialog->registerWidgetHelp( m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"), tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. " @@ -473,6 +471,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* // Debugging Tab + dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"), + tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a " + "replacement or an overlay.")); + dialog->registerWidgetHelp( m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"), tr("Enable debugging when supported by the host's renderer API. Only for developer use.")); @@ -505,6 +507,18 @@ void GraphicsSettingsWidget::setupAdditionalUi() m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast(i)))); } + for (u32 i = 0; i < static_cast(GPUTextureFilter::Count); i++) + { + m_ui.textureFiltering->addItem( + QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast(i)))); + } + + for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) + { + m_ui.gpuDownsampleMode->addItem( + QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast(i)))); + } + for (u32 i = 0; i < static_cast(DisplayAspectRatio::Count); i++) { m_ui.displayAspectRatio->addItem( @@ -523,26 +537,10 @@ void GraphicsSettingsWidget::setupAdditionalUi() QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast(i)))); } + for (u32 i = 0; i < static_cast(DisplaySyncMode::Count); i++) { - if (m_dialog->isPerGameSettings()) - m_ui.msaaMode->addItem(tr("Use Global Setting")); - m_ui.msaaMode->addItem(tr("Disabled"), GetMSAAModeValue(1, false)); - for (uint i = 2; i <= 32; i *= 2) - m_ui.msaaMode->addItem(tr("%1x MSAA").arg(i), GetMSAAModeValue(i, false)); - for (uint i = 2; i <= 32; i *= 2) - m_ui.msaaMode->addItem(tr("%1x SSAA").arg(i), GetMSAAModeValue(i, true)); - } - - for (u32 i = 0; i < static_cast(GPUTextureFilter::Count); i++) - { - m_ui.textureFiltering->addItem( - QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast(i)))); - } - - for (u32 i = 0; i < static_cast(GPUDownsampleMode::Count); i++) - { - m_ui.gpuDownsampleMode->addItem( - QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast(i)))); + m_ui.displaySyncMode->addItem( + QString::fromUtf8(Settings::GetDisplaySyncModeDisplayName(static_cast(i)))); } // Advanced Tab @@ -559,18 +557,22 @@ void GraphicsSettingsWidget::setupAdditionalUi() QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast(i)))); } + { + if (m_dialog->isPerGameSettings()) + m_ui.msaaMode->addItem(tr("Use Global Setting")); + m_ui.msaaMode->addItem(tr("Disabled"), GetMSAAModeValue(1, false)); + for (uint i = 2; i <= 32; i *= 2) + m_ui.msaaMode->addItem(tr("%1x MSAA").arg(i), GetMSAAModeValue(i, false)); + for (uint i = 2; i <= 32; i *= 2) + m_ui.msaaMode->addItem(tr("%1x SSAA").arg(i), GetMSAAModeValue(i, true)); + } + for (u32 i = 0; i < static_cast(GPULineDetectMode::Count); i++) { m_ui.gpuLineDetectMode->addItem( QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast(i)))); } - for (u32 i = 0; i < static_cast(GPUWireframeMode::Count); i++) - { - m_ui.gpuWireframeMode->addItem( - QString::fromUtf8(Settings::GetGPUWireframeModeDisplayName(static_cast(i)))); - } - // Capture Tab for (u32 i = 0; i < static_cast(DisplayScreenshotMode::Count); i++) @@ -584,6 +586,14 @@ void GraphicsSettingsWidget::setupAdditionalUi() m_ui.screenshotFormat->addItem( QString::fromUtf8(Settings::GetDisplayScreenshotFormatDisplayName(static_cast(i)))); } + + // Debugging Tab + + for (u32 i = 0; i < static_cast(GPUWireframeMode::Count); i++) + { + m_ui.gpuWireframeMode->addItem( + QString::fromUtf8(Settings::GetGPUWireframeModeDisplayName(static_cast(i)))); + } } void GraphicsSettingsWidget::removePlatformSpecificUi() diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index e7c556b99..cef62b791 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -7,7 +7,7 @@ 0 0 584 - 446 + 434 @@ -50,18 +50,7 @@ - - - - - - - - VSync - - - - + @@ -98,74 +87,13 @@ - - - Aspect Ratio: - - - - - - - - - - - - 1 - - - 9999 - - - - - - - : - - - - - - - 1 - - - 9999 - - - - - - - - - Crop: - - - - - - - - - - Scaling: - - - - - - - Internal Resolution: - + @@ -254,27 +182,115 @@ - - + + - Multi-Sampling: + Down-Sampling: - - + + + + + + + + + x + + + 1 + + + 16 + + + + - + Texture Filtering: - + - + + + + Aspect Ratio: + + + + + + + + + + + + 1 + + + 9999 + + + + + + + : + + + + + + + 1 + + + 9999 + + + + + + + + + Crop: + + + + + + + + + + Scaling: + + + + + + + + + + VSync: + + + + + + + @@ -283,6 +299,13 @@ + + + + True Color Rendering + + + @@ -304,20 +327,6 @@ - - - - True Color Rendering - - - - - - - Force NTSC Timings - - - @@ -332,30 +341,10 @@ - - - - - - Down-Sampling: - - - - - - - - - - - - x - - - 1 - - - 16 + + + + Force NTSC Timings @@ -477,6 +466,16 @@ Rendering Options + + + + Line Detection: + + + + + + @@ -503,24 +502,14 @@ - + - Line Detection: + Multi-Sampling: - - - - - - Wireframe Mode: - - - - - + @@ -1035,6 +1024,25 @@ 0 + + + + Rendering Options + + + + + + Wireframe Mode: + + + + + + + + + diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 6a9cbadfb..b768d70dd 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1529,7 +1529,7 @@ void EmuThread::run() if (g_gpu_device) { System::PresentDisplay(false); - if (!g_gpu_device->IsVsyncEnabled()) + if (!g_gpu_device->IsVSyncActive()) g_gpu_device->ThrottlePresentation(); } } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index cf0c746bb..57e0c18d3 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(util gpu_shader_cache.h gpu_texture.cpp gpu_texture.h + gpu_types.h host.cpp host.h http_downloader.cpp diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 20c5efeab..9a98178f1 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -233,7 +233,8 @@ bool D3D11Device::CreateSwapChain() swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; - m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen); + m_using_allow_tearing = + (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen); if (m_using_allow_tearing) swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; @@ -600,11 +601,6 @@ bool D3D11Device::GetHostRefreshRate(float* refresh_rate) return GPUDevice::GetHostRefreshRate(refresh_rate); } -void D3D11Device::SetVSync(bool enabled) -{ - m_vsync_enabled = enabled; -} - bool D3D11Device::BeginPresent(bool skip_present) { if (skip_present) @@ -633,7 +629,7 @@ bool D3D11Device::BeginPresent(bool skip_present) // This blows our our GPU usage number considerably, so read the timestamp before the final blit // in this configuration. It does reduce accuracy a little, but better than seeing 100% all of // the time, when it's more like a couple of percent. - if (m_vsync_enabled && m_gpu_timing_enabled) + if ((m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) && m_gpu_timing_enabled) PopTimestampQuery(); static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; @@ -650,13 +646,16 @@ void D3D11Device::EndPresent() { DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); - if (!m_vsync_enabled && m_gpu_timing_enabled) + if (m_sync_mode != DisplaySyncMode::VSync && m_sync_mode != DisplaySyncMode::VSyncRelaxed && m_gpu_timing_enabled) PopTimestampQuery(); - if (!m_vsync_enabled && m_using_allow_tearing) + // DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. + if (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) + m_swap_chain->Present(BoolToUInt32(1), 0); + else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/ m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); else - m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0); + m_swap_chain->Present(0, 0); if (m_gpu_timing_enabled) KickTimestampQuery(); diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index 39d5ff459..466d957d0 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -98,8 +98,6 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSync(bool enabled) override; - bool BeginPresent(bool skip_present) override; void EndPresent() override; diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index 0b75a8117..76637f77c 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -1060,11 +1060,6 @@ std::string D3D12Device::GetDriverInfo() const return ret; } -void D3D12Device::SetVSync(bool enabled) -{ - m_vsync_enabled = enabled; -} - bool D3D12Device::BeginPresent(bool frame_skip) { if (InRenderPass()) @@ -1112,10 +1107,13 @@ void D3D12Device::EndPresent() SubmitCommandList(false); - if (!m_vsync_enabled && m_using_allow_tearing) + // DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. + if (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) + m_swap_chain->Present(BoolToUInt32(1), 0); + else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/ m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); else - m_swap_chain->Present(static_cast(m_vsync_enabled), 0); + m_swap_chain->Present(0, 0); TrimTexturePool(); } diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 92aa052c7..e3776fb64 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -119,8 +119,6 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSync(bool enabled) override; - bool BeginPresent(bool skip_present) override; void EndPresent() override; diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 3940d2a7e..3b9668c36 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -271,10 +271,11 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs) } bool GPUDevice::Create(const std::string_view& adapter, const std::string_view& shader_cache_path, - u32 shader_cache_version, bool debug_device, bool vsync, bool threaded_presentation, - std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) + u32 shader_cache_version, bool debug_device, DisplaySyncMode sync_mode, + bool threaded_presentation, std::optional exclusive_fullscreen_control, + FeatureMask disabled_features, Error* error) { - m_vsync_enabled = vsync; + m_sync_mode = sync_mode; m_debug_device = debug_device; if (!AcquireWindow(true)) @@ -584,6 +585,11 @@ void GPUDevice::RenderImGui() } } +void GPUDevice::SetSyncMode(DisplaySyncMode mode) +{ + m_sync_mode = mode; +} + void GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex) { void* map; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 4c74e5746..8074dea22 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -5,6 +5,7 @@ #include "gpu_shader_cache.h" #include "gpu_texture.h" +#include "gpu_types.h" #include "window_info.h" #include "common/bitfield.h" @@ -25,17 +26,6 @@ class Error; -enum class RenderAPI : u32 -{ - None, - D3D11, - D3D12, - Vulkan, - OpenGL, - OpenGLES, - Metal -}; - class GPUSampler { public: @@ -551,7 +541,7 @@ public: virtual RenderAPI GetRenderAPI() const = 0; bool Create(const std::string_view& adapter, const std::string_view& shader_cache_path, u32 shader_cache_version, - bool debug_device, bool vsync, bool threaded_presentation, + bool debug_device, DisplaySyncMode sync_mode, bool threaded_presentation, std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error); void Destroy(); @@ -646,8 +636,12 @@ public: /// Renders ImGui screen elements. Call before EndPresent(). void RenderImGui(); - ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; } - virtual void SetVSync(bool enabled) = 0; + ALWAYS_INLINE DisplaySyncMode GetSyncMode() const { return m_sync_mode; } + ALWAYS_INLINE bool IsVSyncActive() const + { + return (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed); + } + virtual void SetSyncMode(DisplaySyncMode mode); ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } @@ -760,8 +754,8 @@ private: protected: static Statistics s_stats; + DisplaySyncMode m_sync_mode = DisplaySyncMode::Disabled; bool m_gpu_timing_enabled = false; - bool m_vsync_enabled = false; bool m_debug_device = false; }; diff --git a/src/util/gpu_types.h b/src/util/gpu_types.h new file mode 100644 index 000000000..cf163eec3 --- /dev/null +++ b/src/util/gpu_types.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +enum class RenderAPI : u32 +{ + None, + D3D11, + D3D12, + Vulkan, + OpenGL, + OpenGLES, + Metal +}; + +enum class DisplaySyncMode : u8 +{ + Disabled, + VSync, + VSyncRelaxed, + VRR, + Count +}; diff --git a/src/util/metal_device.h b/src/util/metal_device.h index d4f35b182..92c330f12 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -263,7 +263,7 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSync(bool enabled) override; + void SetSyncMode(DisplaySyncMode mode) override; bool BeginPresent(bool skip_present) override; void EndPresent() override; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index e3bd58b13..2039dbe24 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -124,12 +124,15 @@ bool MetalDevice::GetHostRefreshRate(float* refresh_rate) return GPUDevice::GetHostRefreshRate(refresh_rate); } -void MetalDevice::SetVSync(bool enabled) +void MetalDevice::SetSyncMode(DisplaySyncMode mode) { - m_vsync_enabled = enabled; + m_sync_mode = mode; if (m_layer != nil) + { + const bool enabled = (mode == DisplaySyncMode::VSync || mode == DisplaySyncMode::VSyncRelaxed); [m_layer setDisplaySyncEnabled:enabled]; + } } bool MetalDevice::CreateDevice(const std::string_view& adapter, bool threaded_presentation, @@ -382,7 +385,8 @@ bool MetalDevice::CreateLayer() } }); - [m_layer setDisplaySyncEnabled:m_vsync_enabled]; + const bool sync_enabled = (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed); + [m_layer setDisplaySyncEnabled:sync_enabled]; DebugAssert(m_layer_pass_desc == nil); m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index cef210663..89263c8a4 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -236,12 +236,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg) #endif } -void OpenGLDevice::SetVSync(bool enabled) +void OpenGLDevice::SetSyncMode(DisplaySyncMode mode) { - if (m_vsync_enabled == enabled) + if (m_sync_mode == mode) return; - m_vsync_enabled = enabled; + m_sync_mode = mode; SetSwapInterval(); } @@ -577,7 +577,8 @@ void OpenGLDevice::SetSwapInterval() return; // Window framebuffer has to be bound to call SetSwapInterval. - const s32 interval = m_vsync_enabled ? 1 : 0; + const s32 interval = + (m_sync_mode == DisplaySyncMode::VSync) ? 1 : ((m_sync_mode == DisplaySyncMode::VSyncRelaxed) ? -1 : 0); GLint current_fbo = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index 6f4f102ba..df67e8c4c 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -98,7 +98,7 @@ public: void Draw(u32 vertex_count, u32 base_vertex) override; void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; - void SetVSync(bool enabled) override; + void SetSyncMode(DisplaySyncMode mode) override; bool BeginPresent(bool skip_present) override; void EndPresent() override; diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index a4d8b1f19..b06947c44 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -2,6 +2,7 @@ + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 29f0ea8aa..1c7cea475 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -72,6 +72,7 @@ + diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index ce0d6011e..db86e5e70 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -2022,7 +2022,7 @@ bool VulkanDevice::CreateDevice(const std::string_view& adapter, bool threaded_p if (surface != VK_NULL_HANDLE) { - m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control); + m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control); if (!m_swap_chain) { Error::SetStringView(error, "Failed to create swap chain"); @@ -2243,7 +2243,7 @@ bool VulkanDevice::UpdateWindow() return false; } - m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control); + m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control); if (!m_swap_chain) { Log_ErrorPrintf("Failed to create swap chain"); @@ -2319,24 +2319,27 @@ std::string VulkanDevice::GetDriverInfo() const return ret; } -void VulkanDevice::SetVSync(bool enabled) +void VulkanDevice::SetSyncMode(DisplaySyncMode mode) { - if (!m_swap_chain || m_vsync_enabled == enabled) + if (m_sync_mode == mode) + return; + + const DisplaySyncMode prev_mode = m_sync_mode; + m_sync_mode = mode; + if (!m_swap_chain) return; // This swap chain should not be used by the current buffer, thus safe to destroy. WaitForGPUIdle(); - if (!m_swap_chain->SetVSync(enabled)) + if (!m_swap_chain->SetSyncMode(mode)) { // Try switching back to the old mode.. - if (!m_swap_chain->SetVSync(m_vsync_enabled)) + if (!m_swap_chain->SetSyncMode(prev_mode)) { Panic("Failed to reset old vsync mode after failure"); m_swap_chain.reset(); } } - - m_vsync_enabled = enabled; } bool VulkanDevice::BeginPresent(bool frame_skip) diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index d0f8b8727..cef3c54fb 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -126,7 +126,7 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSync(bool enabled) override; + void SetSyncMode(DisplaySyncMode mode) override; bool BeginPresent(bool skip_present) override; void EndPresent() override; diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index e3b7835ca..81c302a07 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -22,9 +22,69 @@ Log_SetChannel(VulkanDevice); -VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, +static VkFormat GetLinearFormat(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_R8_SRGB: + return VK_FORMAT_R8_UNORM; + case VK_FORMAT_R8G8_SRGB: + return VK_FORMAT_R8G8_UNORM; + case VK_FORMAT_R8G8B8_SRGB: + return VK_FORMAT_R8G8B8_UNORM; + case VK_FORMAT_R8G8B8A8_SRGB: + return VK_FORMAT_R8G8B8A8_UNORM; + case VK_FORMAT_B8G8R8_SRGB: + return VK_FORMAT_B8G8R8_UNORM; + case VK_FORMAT_B8G8R8A8_SRGB: + return VK_FORMAT_B8G8R8A8_UNORM; + default: + return format; + } +} + +static const char* PresentModeToString(VkPresentModeKHR mode) +{ + switch (mode) + { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return "VK_PRESENT_MODE_IMMEDIATE_KHR"; + + case VK_PRESENT_MODE_MAILBOX_KHR: + return "VK_PRESENT_MODE_MAILBOX_KHR"; + + case VK_PRESENT_MODE_FIFO_KHR: + return "VK_PRESENT_MODE_FIFO_KHR"; + + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return "VK_PRESENT_MODE_FIFO_RELAXED_KHR"; + + case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: + return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR"; + + case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: + return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR"; + + default: + return "UNKNOWN_VK_PRESENT_MODE"; + } +} + +static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(DisplaySyncMode mode) +{ + static constexpr std::array(DisplaySyncMode::Count)> modes = {{ + VK_PRESENT_MODE_IMMEDIATE_KHR, // Disabled + VK_PRESENT_MODE_FIFO_KHR, // VSync + VK_PRESENT_MODE_FIFO_RELAXED_KHR, // VSyncRelaxed + VK_PRESENT_MODE_IMMEDIATE_KHR, // VRR ?? + }}; + + return modes[static_cast(mode)]; +} + +VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode, std::optional exclusive_fullscreen_control) - : m_window_info(wi), m_surface(surface), m_vsync_mode(vsync), + : m_window_info(wi), m_surface(surface), m_requested_present_mode(requested_present_mode), m_exclusive_fullscreen_control(exclusive_fullscreen_control) { } @@ -160,38 +220,19 @@ void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, #endif } -std::unique_ptr VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, +std::unique_ptr VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, + DisplaySyncMode sync_mode, std::optional exclusive_fullscreen_control) { + const VkPresentModeKHR requested_mode = GetPreferredPresentModeForVsyncMode(sync_mode); std::unique_ptr swap_chain = - std::unique_ptr(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control)); + std::unique_ptr(new VulkanSwapChain(wi, surface, requested_mode, exclusive_fullscreen_control)); if (!swap_chain->CreateSwapChain()) return nullptr; return swap_chain; } -static VkFormat GetLinearFormat(VkFormat format) -{ - switch (format) - { - case VK_FORMAT_R8_SRGB: - return VK_FORMAT_R8_UNORM; - case VK_FORMAT_R8G8_SRGB: - return VK_FORMAT_R8G8_UNORM; - case VK_FORMAT_R8G8B8_SRGB: - return VK_FORMAT_R8G8B8_UNORM; - case VK_FORMAT_R8G8B8A8_SRGB: - return VK_FORMAT_R8G8B8A8_UNORM; - case VK_FORMAT_B8G8R8_SRGB: - return VK_FORMAT_B8G8R8_UNORM; - case VK_FORMAT_B8G8R8A8_SRGB: - return VK_FORMAT_B8G8R8A8_UNORM; - default: - return format; - } -} - std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface) { VulkanDevice& dev = VulkanDevice::GetInstance(); @@ -232,44 +273,8 @@ std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurface return std::nullopt; } -static const char* PresentModeToString(VkPresentModeKHR mode) -{ - switch (mode) - { - case VK_PRESENT_MODE_IMMEDIATE_KHR: - return "VK_PRESENT_MODE_IMMEDIATE_KHR"; - - case VK_PRESENT_MODE_MAILBOX_KHR: - return "VK_PRESENT_MODE_MAILBOX_KHR"; - - case VK_PRESENT_MODE_FIFO_KHR: - return "VK_PRESENT_MODE_FIFO_KHR"; - - case VK_PRESENT_MODE_FIFO_RELAXED_KHR: - return "VK_PRESENT_MODE_FIFO_RELAXED_KHR"; - - case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: - return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR"; - - case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: - return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR"; - - default: - return "UNKNOWN_VK_PRESENT_MODE"; - } -} - -static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(bool mode) -{ - if (mode /*== VsyncMode::On*/) - return VK_PRESENT_MODE_FIFO_KHR; - /*else if (mode == VsyncMode::Adaptive) - return VK_PRESENT_MODE_FIFO_RELAXED_KHR;*/ - else - return VK_PRESENT_MODE_IMMEDIATE_KHR; -} - -std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, bool vsync) +std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, + VkPresentModeKHR requested_mode) { VulkanDevice& dev = VulkanDevice::GetInstance(); VkResult res; @@ -294,18 +299,17 @@ std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR }; // Use preferred mode if available. - const VkPresentModeKHR preferred_mode = GetPreferredPresentModeForVsyncMode(vsync); VkPresentModeKHR selected_mode; - if (CheckForMode(preferred_mode)) + if (CheckForMode(requested_mode)) { - selected_mode = preferred_mode; + selected_mode = requested_mode; } - else if (!vsync /*vsync != VsyncMode::On*/ && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + else if (requested_mode != VK_PRESENT_MODE_FIFO_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { // Prefer mailbox over fifo for adaptive vsync/no-vsync. selected_mode = VK_PRESENT_MODE_MAILBOX_KHR; } - else if (vsync /*vsync != VsyncMode::Off*/ && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) + else if (requested_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) { // Fallback to FIFO if we're using any kind of vsync. // This should never fail, FIFO is mandated. @@ -317,7 +321,7 @@ std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR selected_mode = present_modes[0]; } - Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(preferred_mode), + Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(requested_mode), PresentModeToString(selected_mode)); return selected_mode; @@ -329,7 +333,7 @@ bool VulkanSwapChain::CreateSwapChain() // Select swap chain format and present mode std::optional surface_format = SelectSurfaceFormat(m_surface); - std::optional present_mode = SelectPresentMode(m_surface, m_vsync_mode); + std::optional present_mode = SelectPresentMode(m_surface, m_requested_present_mode); if (!surface_format.has_value() || !present_mode.has_value()) return false; @@ -468,6 +472,7 @@ bool VulkanSwapChain::CreateSwapChain() m_window_info.surface_width = std::max(1u, size.width); m_window_info.surface_height = std::max(1u, size.height); m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format); + m_actual_present_mode = present_mode.value(); if (m_window_info.surface_format == GPUTexture::Format::Unknown) { Log_ErrorPrintf("Unknown Vulkan surface format %u", static_cast(surface_format->format)); @@ -634,12 +639,13 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s return true; } -bool VulkanSwapChain::SetVSync(bool mode) +bool VulkanSwapChain::SetSyncMode(DisplaySyncMode mode) { - if (m_vsync_mode == mode) + const VkPresentModeKHR present_mode = GetPreferredPresentModeForVsyncMode(mode); + if (m_requested_present_mode == present_mode) return true; - m_vsync_mode = mode; + m_requested_present_mode = present_mode; // Recreate the swap chain with the new present mode. Log_VerbosePrintf("Recreating swap chain to change present mode."); diff --git a/src/util/vulkan_swap_chain.h b/src/util/vulkan_swap_chain.h index 8f55a6856..7229e216c 100644 --- a/src/util/vulkan_swap_chain.h +++ b/src/util/vulkan_swap_chain.h @@ -25,7 +25,7 @@ public: static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface); // Create a new swap chain from a pre-existing surface. - static std::unique_ptr Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, + static std::unique_ptr Create(const WindowInfo& wi, VkSurfaceKHR surface, DisplaySyncMode sync_mode, std::optional exclusive_fullscreen_control); ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } @@ -60,9 +60,12 @@ public: } // Returns true if the current present mode is synchronizing (adaptive or hard). - ALWAYS_INLINE bool IsPresentModeSynchronizing() const { return (m_vsync_mode /*!= VsyncMode::Off*/); } + ALWAYS_INLINE bool IsPresentModeSynchronizing() const + { + return (m_actual_present_mode == VK_PRESENT_MODE_FIFO_KHR || + m_actual_present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR); + } - VkRenderPass GetRenderPass(VkAttachmentLoadOp load_op) const; VkResult AcquireNextImage(); void ReleaseCurrentImage(); @@ -70,19 +73,18 @@ public: bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f); // Change vsync enabled state. This may fail as it causes a swapchain recreation. - bool SetVSync(bool mode); + bool SetSyncMode(DisplaySyncMode mode); private: - VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, + VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode, std::optional exclusive_fullscreen_control); static std::optional SelectSurfaceFormat(VkSurfaceKHR surface); - static std::optional SelectPresentMode(VkSurfaceKHR surface, bool vsync); + static std::optional SelectPresentMode(VkSurfaceKHR surface, VkPresentModeKHR requested_mode); bool CreateSwapChain(); void DestroySwapChain(); - bool SetupSwapChainImages(); void DestroySwapChainImages(); void DestroySurface(); @@ -109,7 +111,8 @@ private: std::vector m_semaphores; VkFormat m_format = VK_FORMAT_UNDEFINED; - bool m_vsync_mode = false; + VkPresentModeKHR m_requested_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + VkPresentModeKHR m_actual_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; u32 m_current_image = 0; u32 m_current_semaphore = 0;