From 0f9a2550933e2f970ad3ef59353e81f3af013983 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 24 May 2024 22:48:06 +1000 Subject: [PATCH] System: Fallback to FIFO on AMD (no mailbox support) --- src/core/host.cpp | 13 ++--- src/core/system.cpp | 95 ++++++++++++++++------------------ src/core/system.h | 2 + src/util/d3d11_device.cpp | 20 ++++--- src/util/d3d11_device.h | 2 +- src/util/d3d12_device.cpp | 15 +++--- src/util/d3d12_device.h | 2 +- src/util/gpu_device.cpp | 18 +++---- src/util/gpu_device.h | 21 ++++---- src/util/metal_device.h | 2 +- src/util/metal_device.mm | 12 +++-- src/util/opengl_device.cpp | 9 +++- src/util/opengl_device.h | 2 +- src/util/vulkan_device.cpp | 45 ++++++++-------- src/util/vulkan_device.h | 3 +- src/util/vulkan_swap_chain.cpp | 92 ++++++++++++++++++++------------ src/util/vulkan_swap_chain.h | 22 ++++---- 17 files changed, 201 insertions(+), 174 deletions(-) diff --git a/src/core/host.cpp b/src/core/host.cpp index be3e62393..2ab5cd932 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -285,12 +285,13 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error) disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF; Error create_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::GetEffectiveVSyncMode(), - g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, - static_cast(disabled_features), &create_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::GetEffectiveVSyncMode(), System::ShouldAllowPresentThrottle(), + g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, + static_cast(disabled_features), &create_error)) { ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription()); if (g_gpu_device) diff --git a/src/core/system.cpp b/src/core/system.cpp index 94c600e83..fefec9355 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -123,7 +123,6 @@ static void UpdatePerformanceCounters(); static void AccumulatePreFrameSleepTime(); static void UpdatePreFrameSleepTime(); static void UpdateDisplayVSync(); -static void UpdateDisplayMaxFPS(); static void SetRewinding(bool enabled); static bool SaveRewindState(); @@ -186,6 +185,8 @@ static bool s_turbo_enabled = false; static bool s_throttler_enabled = false; static bool s_optimal_frame_pacing = false; static bool s_pre_frame_sleep = false; +static bool s_can_sync_to_host = false; +static bool s_syncing_to_host = false; static bool s_syncing_to_host_with_vsync = false; static bool s_skip_presenting_duplicate_frames = false; static u32 s_skipped_frame_count = 0; @@ -1264,7 +1265,7 @@ void System::PauseSystem(bool paused) Host::OnSystemPaused(); Host::OnIdleStateChanged(); - UpdateDisplayMaxFPS(); + UpdateDisplayVSync(); InvalidateDisplay(); } else @@ -1279,7 +1280,7 @@ void System::PauseSystem(bool paused) Host::OnSystemResumed(); Host::OnIdleStateChanged(); - UpdateDisplayMaxFPS(); + UpdateDisplayVSync(); ResetPerformanceCounters(); ResetThrottler(); } @@ -1823,7 +1824,6 @@ void System::DestroySystem() if (s_keep_gpu_device_on_shutdown && g_gpu_device) { UpdateDisplayVSync(); - UpdateDisplayMaxFPS(); } else { @@ -1982,7 +1982,7 @@ void System::FrameDone() s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) || (!s_optimal_frame_pacing && current_time > s_next_frame_time && s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) || - g_gpu_device->ShouldSkipDisplayingFrame()) && + g_gpu_device->ShouldSkipPresentingFrame()) && !s_syncing_to_host_with_vsync && !IsExecutionInterrupted()); if (!skip_this_frame) { @@ -2851,25 +2851,32 @@ void System::UpdateSpeedLimiterState() g_gpu_device->GetWindowInfo().IsSurfaceless(); // surfaceless check for regtest s_skip_presenting_duplicate_frames = s_throttler_enabled && g_settings.display_skip_presenting_duplicate_frames; s_pre_frame_sleep = s_optimal_frame_pacing && g_settings.display_pre_frame_sleep; + s_can_sync_to_host = false; + s_syncing_to_host = false; s_syncing_to_host_with_vsync = false; - if (const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate; host_refresh_rate > 0.0f) + if (g_settings.sync_to_host_refresh_rate) { - const float ratio = host_refresh_rate / System::GetThrottleFrequency(); - const bool can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f); - INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, System::GetThrottleFrequency(), - ratio, can_sync_to_host ? "can sync" : "can't sync"); - - if (can_sync_to_host && g_settings.sync_to_host_refresh_rate && s_target_speed == 1.0f) + const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate; + if (host_refresh_rate > 0.0f) { - s_target_speed = ratio; + const float ratio = host_refresh_rate / System::GetThrottleFrequency(); + s_can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f); + INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, System::GetThrottleFrequency(), + ratio, s_can_sync_to_host ? "can sync" : "can't sync"); - // When syncing to host and using vsync, we don't need to sleep. - s_syncing_to_host_with_vsync = g_settings.display_vsync; - if (s_syncing_to_host_with_vsync) + s_syncing_to_host = (s_can_sync_to_host && g_settings.sync_to_host_refresh_rate && s_target_speed == 1.0f); + if (s_syncing_to_host) { - INFO_LOG("Using host vsync for throttling."); - s_throttler_enabled = false; + s_target_speed = ratio; + + // When syncing to host and using vsync, we don't need to sleep. + s_syncing_to_host_with_vsync = g_settings.display_vsync; + if (s_syncing_to_host_with_vsync) + { + INFO_LOG("Using host vsync for throttling."); + s_throttler_enabled = false; + } } } } @@ -2885,7 +2892,6 @@ void System::UpdateSpeedLimiterState() UpdateThrottlePeriod(); ResetThrottler(); UpdateDisplayVSync(); - UpdateDisplayMaxFPS(); if (g_settings.increase_timer_resolution) SetTimerResolutionIncreased(s_throttler_enabled); @@ -2895,30 +2901,18 @@ void System::UpdateDisplayVSync() { static constexpr std::array(GPUVSyncMode::Count)> vsync_modes = {{ "Disabled", - "DoubleBuffered", - "TripleBuffered", + "FIFO", + "Mailbox", }}; // Avoid flipping vsync on and off by manually throttling when vsync is on. const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode(); - VERBOSE_LOG("VSync: {}{}", vsync_modes[static_cast(vsync_mode)], - s_syncing_to_host_with_vsync ? " (for throttling)" : ""); + const bool allow_present_throttle = ShouldAllowPresentThrottle(); + VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast(vsync_mode)], + s_syncing_to_host_with_vsync ? " (for throttling)" : "", + allow_present_throttle ? " (present throttle allowed)" : ""); - g_gpu_device->SetVSyncMode(vsync_mode); -} - -void System::UpdateDisplayMaxFPS() -{ - const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode(); - const float max_display_fps = - (!IsPaused() && IsValid() && - (s_target_speed == 0.0f || - (vsync_mode != GPUVSyncMode::Disabled && s_target_speed != 1.0f && !s_syncing_to_host_with_vsync))) ? - g_gpu_device->GetWindowInfo().surface_refresh_rate : - 0.0f; - - VERBOSE_LOG("Max display fps: {}", max_display_fps); - g_gpu_device->SetDisplayMaxFPS(max_display_fps); + g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle); } GPUVSyncMode System::GetEffectiveVSyncMode() @@ -2928,16 +2922,21 @@ GPUVSyncMode System::GetEffectiveVSyncMode() return GPUVSyncMode::Disabled; // If there's no VM, or we're using vsync for timing, then we always use double-buffered (blocking). - if (s_state == State::Shutdown || s_state == State::Stopping || s_syncing_to_host_with_vsync) - return GPUVSyncMode::DoubleBuffered; + // Try to keep the same present mode whether we're running or not, since it'll avoid flicker. + const bool valid_vm = (s_state != State::Shutdown && s_state != State::Stopping); + if (s_can_sync_to_host || (!valid_vm && g_settings.sync_to_host_refresh_rate)) + return GPUVSyncMode::FIFO; // For PAL games, we always want to triple buffer, because otherwise we'll be tearing. // Or for when we aren't using sync-to-host-refresh, to avoid dropping frames. - // Force vsync off when not running at 100% speed. - // We can avoid this by manually throttling when vsync is on (see above). - return (s_throttler_enabled && g_gpu_device->GetWindowInfo().surface_refresh_rate == 0.0f) ? - GPUVSyncMode::Disabled : - GPUVSyncMode::TripleBuffered; + // Allow present skipping when running outside of normal speed, if mailbox isn't supported. + return GPUVSyncMode::Mailbox; +} + +bool System::ShouldAllowPresentThrottle() +{ + const bool valid_vm = (s_state != State::Shutdown && s_state != State::Stopping); + return !valid_vm || !IsRunningAtNonStandardSpeed(); } bool System::IsFastForwardEnabled() @@ -4037,10 +4036,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) if (g_gpu_device) { if (g_settings.display_vsync != old_settings.display_vsync) - { UpdateDisplayVSync(); - UpdateDisplayMaxFPS(); - } } } @@ -4553,8 +4549,7 @@ bool System::IsRunningAtNonStandardSpeed() if (!IsValid()) return false; - const float target_speed = GetTargetSpeed(); - return (target_speed <= 0.95f || target_speed >= 1.05f); + return (s_target_speed == 1.0f || s_syncing_to_host); } s32 System::GetAudioOutputVolume() diff --git a/src/core/system.h b/src/core/system.h index d087630bd..5915d51af 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -12,6 +12,7 @@ #include #include #include +#include class ByteStream; class CDImage; @@ -463,6 +464,7 @@ bool IsRunningAtNonStandardSpeed(); /// Returns true if vsync should be used. GPUVSyncMode GetEffectiveVSyncMode(); +bool ShouldAllowPresentThrottle(); /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 09ec7a4a5..73573ccec 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -201,7 +201,7 @@ u32 D3D11Device::GetSwapChainBufferCount() const { // With vsync off, we only need two buffers. Same for blocking vsync. // With triple buffering, we need three. - return (m_vsync_mode == GPUVSyncMode::TripleBuffered) ? 3 : 2; + return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; } bool D3D11Device::CreateSwapChain() @@ -588,8 +588,10 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t) static_cast(t)->CommitClear(m_context.Get()); } -void D3D11Device::SetVSyncMode(GPUVSyncMode mode) +void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) { + m_allow_present_throttle = allow_present_throttle; + if (m_vsync_mode == mode) return; @@ -634,7 +636,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 (IsVSyncModeBlocking() && m_gpu_timing_enabled) + if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled) PopTimestampQuery(); static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; @@ -652,16 +654,12 @@ void D3D11Device::EndPresent(bool explicit_present) DebugAssert(!explicit_present); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); - if (!IsVSyncModeBlocking() && m_gpu_timing_enabled) + if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled) PopTimestampQuery(); - // DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. - if (IsVSyncModeBlocking()) - m_swap_chain->Present(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(0, 0); + const UINT sync_interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); + const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; + m_swap_chain->Present(sync_interval, flags); if (m_gpu_timing_enabled) KickTimestampQuery(); diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index 88f275654..9ee426a58 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -96,7 +96,7 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode) override; + void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index 9e3d60fbc..a9bae63d7 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -801,7 +801,7 @@ u32 D3D12Device::GetSwapChainBufferCount() const { // With vsync off, we only need two buffers. Same for blocking vsync. // With triple buffering, we need three. - return (m_vsync_mode == GPUVSyncMode::TripleBuffered) ? 3 : 2; + return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; } bool D3D12Device::CreateSwapChain() @@ -1084,8 +1084,9 @@ std::string D3D12Device::GetDriverInfo() const return ret; } -void D3D12Device::SetVSyncMode(GPUVSyncMode mode) +void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) { + m_allow_present_throttle = allow_present_throttle; if (m_vsync_mode == mode) return; @@ -1158,13 +1159,9 @@ void D3D12Device::SubmitPresent() { DebugAssert(m_swap_chain); - // DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. - if (IsVSyncModeBlocking()) - m_swap_chain->Present(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(0, 0); + const UINT sync_interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); + const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; + m_swap_chain->Present(sync_interval, flags); } #ifdef _DEBUG diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index ac36c31bd..44251f8ac 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -118,7 +118,7 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode) override; + void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index c128aa4e2..90e29842f 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -276,10 +276,11 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs) } bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, - bool debug_device, GPUVSyncMode vsync, bool threaded_presentation, + bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle, bool threaded_presentation, std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) { m_vsync_mode = vsync; + m_allow_present_throttle = allow_present_throttle; m_debug_device = debug_device; if (!AcquireWindow(true)) @@ -970,11 +971,6 @@ void GPUDevice::TrimTexturePool() } } -void GPUDevice::SetDisplayMaxFPS(float max_fps) -{ - m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f; -} - bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, GPUTexture::Format format, bool preserve /* = true */) { @@ -1015,14 +1011,18 @@ bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u return true; } -bool GPUDevice::ShouldSkipDisplayingFrame() +bool GPUDevice::ShouldSkipPresentingFrame() { - if (m_display_frame_interval == 0.0f) + // Only needed with FIFO. But since we're so fast, we allow it always. + if (!m_allow_present_throttle) return false; + const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; + const float throttle_period = 1.0f / throttle_rate; + const u64 now = Common::Timer::GetCurrentValue(); const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); - if (diff < m_display_frame_interval) + if (diff < throttle_period) return true; m_last_frame_displayed_time = now; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index c6456e0d7..66f35cfa4 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -39,8 +39,8 @@ enum class RenderAPI : u32 enum class GPUVSyncMode : u8 { Disabled, - DoubleBuffered, - TripleBuffered, + FIFO, + Mailbox, Count }; @@ -581,8 +581,8 @@ public: virtual RenderAPI GetRenderAPI() const = 0; bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, - GPUVSyncMode vsync, bool threaded_presentation, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error); + GPUVSyncMode vsync, bool allow_present_throttle, bool threaded_presentation, + std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error); void Destroy(); virtual bool HasSurface() const = 0; @@ -681,8 +681,8 @@ public: void RenderImGui(); ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; } - ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode >= GPUVSyncMode::DoubleBuffered); } - virtual void SetVSyncMode(GPUVSyncMode mode) = 0; + ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); } + virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0; ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } @@ -690,10 +690,9 @@ public: bool UpdateImGuiFontTexture(); bool UsesLowerLeftOrigin() const; static Common::Rectangle FlipToLowerLeft(const Common::Rectangle& rc, s32 target_height); - void SetDisplayMaxFPS(float max_fps); bool ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, GPUTexture::Format format, bool preserve = true); - bool ShouldSkipDisplayingFrame(); + bool ShouldSkipPresentingFrame(); void ThrottlePresentation(); virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0; @@ -736,6 +735,7 @@ protected: u32 m_max_multisamples = 0; WindowInfo m_window_info; + u64 m_last_frame_displayed_time = 0; GPUShaderCache m_shader_cache; @@ -793,14 +793,11 @@ private: size_t m_pool_vram_usage = 0; u32 m_texture_pool_counter = 0; - // TODO: Move out. - u64 m_last_frame_displayed_time = 0; - float m_display_frame_interval = 0.0f; - protected: static Statistics s_stats; GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled; + bool m_allow_present_throttle = false; bool m_gpu_timing_enabled = false; bool m_debug_device = false; }; diff --git a/src/util/metal_device.h b/src/util/metal_device.h index 9c1ea221d..7afd5b944 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 SetVSyncMode(GPUVSyncMode mode) override; + void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; bool BeginPresent(bool skip_present) override; void EndPresent(bool explicit_submit) override; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 4ee0fe279..3c27b2807 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -115,14 +115,18 @@ bool MetalDevice::HasSurface() const return (m_layer != nil); } -void MetalDevice::SetVSyncMode(GPUVSyncMode mode) +void MetalDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) { + // Metal does not support mailbox mode. + mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; + m_allow_present_throttle = allow_present_throttle; + if (m_vsync_mode == mode) return; m_vsync_mode = mode; if (m_layer != nil) - [m_layer setDisplaySyncEnabled:m_vsync_mode >= GPUVSyncMode::DoubleBuffered]; + [m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO]; } bool MetalDevice::CreateDevice(std::string_view adapter, bool threaded_presentation, @@ -396,7 +400,9 @@ bool MetalDevice::CreateLayer() } }); - [m_layer setDisplaySyncEnabled:m_vsync_mode >= GPUVSyncMode::DoubleBuffered]; + // Metal does not support mailbox mode. + m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode; + [m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO]; 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 8ad1d5659..60bf7ec88 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -238,8 +238,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg) #endif } -void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode) +void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) { + // OpenGL does not support Mailbox. + mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; + m_allow_present_throttle = allow_present_throttle; + if (m_vsync_mode == mode) return; @@ -286,6 +290,7 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, bool threaded_presenta // Is this needed? m_window_info = m_gl_context->GetWindowInfo(); + m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode; const bool opengl_is_available = ((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || @@ -583,7 +588,7 @@ void OpenGLDevice::SetSwapInterval() return; // Window framebuffer has to be bound to call SetSwapInterval. - const s32 interval = (m_vsync_mode >= GPUVSyncMode::DoubleBuffered) ? 1 : 0; + const s32 interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); 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 3a760a86a..01da0bbf1 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -100,7 +100,7 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode) override; + void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; bool BeginPresent(bool skip_present) override; void EndPresent(bool explicit_present) override; diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 0de5d26be..3ceffb2e0 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -436,19 +436,6 @@ bool VulkanDevice::SelectDeviceFeatures() return true; } -VkPresentModeKHR VulkanDevice::SelectPresentMode() const -{ - // Use mailbox/triple buffering for "normal" vsync, due to PAL refresh rate mismatch. - // Otherwise, use FIFO when syncing to host, because we don't want to return early. - static constexpr std::array(GPUVSyncMode::Count)> modes = {{ - VK_PRESENT_MODE_IMMEDIATE_KHR, // Disabled - VK_PRESENT_MODE_FIFO_KHR, // DoubleBuffered - VK_PRESENT_MODE_MAILBOX_KHR, // TripleBuffered - }}; - - return modes[static_cast(m_vsync_mode)]; -} - bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer) { u32 queue_family_count; @@ -2052,8 +2039,9 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta if (surface != VK_NULL_HANDLE) { - m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control); - if (!m_swap_chain) + VkPresentModeKHR present_mode; + if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) || + !(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control))) { Error::SetStringView(error, "Failed to create swap chain"); return false; @@ -2273,8 +2261,9 @@ bool VulkanDevice::UpdateWindow() return false; } - m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control); - if (!m_swap_chain) + VkPresentModeKHR present_mode; + if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) || + !(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control))) { ERROR_LOG("Failed to create swap chain"); VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface); @@ -2349,18 +2338,32 @@ std::string VulkanDevice::GetDriverInfo() const return ret; } -void VulkanDevice::SetVSyncMode(GPUVSyncMode mode) +void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) { + m_allow_present_throttle = allow_present_throttle; + if (!m_swap_chain) + { + // For when it is re-created. + m_vsync_mode = mode; + return; + } + + VkPresentModeKHR present_mode; + if (!VulkanSwapChain::SelectPresentMode(m_swap_chain->GetSurface(), &mode, &present_mode)) + { + ERROR_LOG("Ignoring vsync mode change."); + return; + } + + // Actually changed? If using a fallback, it might not have. if (m_vsync_mode == mode) return; m_vsync_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->SetRequestedPresentMode(SelectPresentMode())) + if (!m_swap_chain->SetPresentMode(present_mode)) { Panic("Failed to update swap chain present mode."); m_swap_chain.reset(); diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 339d55aec..14e821ab8 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -129,7 +129,7 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSyncMode(GPUVSyncMode mode) override; + void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; bool BeginPresent(bool skip_present) override; void EndPresent(bool explicit_present) override; @@ -324,7 +324,6 @@ private: bool enable_debug_utils); bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface); bool SelectDeviceFeatures(); - VkPresentModeKHR SelectPresentMode() const; bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer); void ProcessDeviceExtensions(); diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index bd26cc9b1..264b2570d 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -72,7 +72,7 @@ static const char* PresentModeToString(VkPresentModeKHR mode) VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control) - : m_window_info(wi), m_surface(surface), m_requested_present_mode(present_mode), + : m_window_info(wi), m_surface(surface), m_present_mode(present_mode), m_exclusive_fullscreen_control(exclusive_fullscreen_control) { } @@ -260,8 +260,7 @@ std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurface return std::nullopt; } -std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, - VkPresentModeKHR requested_mode) +bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode) { VulkanDevice& dev = VulkanDevice::GetInstance(); VkResult res; @@ -270,7 +269,7 @@ std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR if (res != VK_SUCCESS || mode_count == 0) { LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); - return std::nullopt; + return false; } std::vector present_modes(mode_count); @@ -279,43 +278,71 @@ std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR Assert(res == VK_SUCCESS); // Checks if a particular mode is supported, if it is, returns that mode. - auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { + const auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { auto it = std::find_if(present_modes.begin(), present_modes.end(), [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); return it != present_modes.end(); }; - // Use preferred mode if available. - VkPresentModeKHR selected_mode; - if (CheckForMode(requested_mode)) + switch (*vsync_mode) { - selected_mode = requested_mode; - } - else if (requested_mode == VK_PRESENT_MODE_IMMEDIATE_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) - { - // Prefer mailbox over FIFO for vsync-off, since we don't want to block. - selected_mode = VK_PRESENT_MODE_MAILBOX_KHR; - } - else - { - // Fallback to FIFO if we we can't use mailbox. This should never fail, FIFO is mandated. - selected_mode = VK_PRESENT_MODE_FIFO_KHR; + case GPUVSyncMode::Disabled: + { + // Prefer immediate > mailbox > fifo. + if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) + { + *present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + } + else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + { + WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox."); + *present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + } + else + { + WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO."); + *present_mode = VK_PRESENT_MODE_FIFO_KHR; + *vsync_mode = GPUVSyncMode::FIFO; + } + } + break; + + case GPUVSyncMode::FIFO: + { + // FIFO is always available. + *present_mode = VK_PRESENT_MODE_FIFO_KHR; + } + break; + + case GPUVSyncMode::Mailbox: + { + // Mailbox > fifo. + if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + { + *present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + } + else + { + WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO."); + *present_mode = VK_PRESENT_MODE_FIFO_KHR; + *vsync_mode = GPUVSyncMode::FIFO; + } + } + break; + + DefaultCaseIsUnreachable() } - DEV_LOG("Preferred present mode: {}, selected: {}", PresentModeToString(requested_mode), - PresentModeToString(selected_mode)); - - return selected_mode; + return true; } bool VulkanSwapChain::CreateSwapChain() { VulkanDevice& dev = VulkanDevice::GetInstance(); - // Select swap chain format and present mode + // Select swap chain format std::optional surface_format = SelectSurfaceFormat(m_surface); - std::optional present_mode = SelectPresentMode(m_surface, m_requested_present_mode); - if (!surface_format.has_value() || !present_mode.has_value()) + if (!surface_format.has_value()) return false; // Look up surface properties to determine image count and dimensions @@ -331,9 +358,9 @@ bool VulkanSwapChain::CreateSwapChain() // Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode. // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. u32 image_count = std::clamp( - (m_requested_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount, + (m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount, (surface_capabilities.maxImageCount == 0) ? std::numeric_limits::max() : surface_capabilities.maxImageCount); - DEV_LOG("Creating a swap chain with {} images", image_count); + DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode)); // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here // determines window size? Android sometimes lags updating currentExtent, so don't use it. @@ -392,7 +419,7 @@ bool VulkanSwapChain::CreateSwapChain() nullptr, transform, alpha, - present_mode.value(), + m_present_mode, VK_TRUE, old_swap_chain}; std::array indices = {{ @@ -453,7 +480,6 @@ 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) { ERROR_LOG("Unknown Vulkan surface format {}", static_cast(surface_format->format)); @@ -624,12 +650,12 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s return true; } -bool VulkanSwapChain::SetRequestedPresentMode(VkPresentModeKHR mode) +bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode) { - if (m_requested_present_mode == mode) + if (m_present_mode == present_mode) return true; - m_requested_present_mode = mode; + m_present_mode = present_mode; // Recreate the swap chain with the new present mode. VERBOSE_LOG("Recreating swap chain to change present mode."); diff --git a/src/util/vulkan_swap_chain.h b/src/util/vulkan_swap_chain.h index f85aeddea..a7c15be1a 100644 --- a/src/util/vulkan_swap_chain.h +++ b/src/util/vulkan_swap_chain.h @@ -29,6 +29,9 @@ public: VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control); + // Determines present mode to use. + static bool SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode); + ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() const { return &m_swap_chain; } @@ -61,11 +64,8 @@ public: } // Returns true if the current present mode is synchronizing (adaptive or hard). - 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); - } + ALWAYS_INLINE bool IsPresentModeSynchronizing() const { return (m_present_mode == VK_PRESENT_MODE_FIFO_KHR); } + ALWAYS_INLINE VkPresentModeKHR GetPresentMode() const { return m_present_mode; } VkResult AcquireNextImage(); void ReleaseCurrentImage(); @@ -74,14 +74,13 @@ 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 SetRequestedPresentMode(VkPresentModeKHR mode); + bool SetPresentMode(VkPresentModeKHR present_mode); private: VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control); static std::optional SelectSurfaceFormat(VkSurfaceKHR surface); - static std::optional SelectPresentMode(VkSurfaceKHR surface, VkPresentModeKHR requested_mode); bool CreateSwapChain(); void DestroySwapChain(); @@ -108,15 +107,14 @@ private: VkSurfaceKHR m_surface = VK_NULL_HANDLE; VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; - u32 m_current_image = 0; - u32 m_current_semaphore = 0; - std::vector m_images; std::vector m_semaphores; + u32 m_current_image = 0; + u32 m_current_semaphore = 0; + VkFormat m_format = VK_FORMAT_UNDEFINED; - VkPresentModeKHR m_requested_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; - VkPresentModeKHR m_actual_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; std::optional m_image_acquire_result; std::optional m_exclusive_fullscreen_control;