diff --git a/src/core/host.cpp b/src/core/host.cpp index cf676c856..1b94ea5bb 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -288,7 +288,8 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* 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::IsVSyncEffectivelyEnabled(), + SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::IsHostVSyncEffectivelyEnabled(), + System::IsHostVSyncEffectivelyEnabled() && !System::IsHostVSyncUsedForTiming(), g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, static_cast(disabled_features), &create_error)) { @@ -331,9 +332,15 @@ void Host::UpdateDisplayWindow() ImGuiManager::WindowResized(); - // If we're paused, re-present the current frame at the new window size. - if (System::IsValid() && System::IsPaused()) - System::InvalidateDisplay(); + if (System::IsValid()) + { + // Fix up vsync etc. + System::UpdateSpeedLimiterState(); + + // If we're paused, re-present the current frame at the new window size. + if (System::IsPaused()) + System::InvalidateDisplay(); + } } void Host::ResizeDisplayWindow(s32 width, s32 height, float scale) diff --git a/src/core/system.cpp b/src/core/system.cpp index 986fbf00f..9c4ccf9da 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2835,7 +2835,6 @@ void System::UpdateSpeedLimiterState() { DebugAssert(IsValid()); - const float old_target_speed = s_target_speed; s_target_speed = s_turbo_enabled ? g_settings.turbo_speed : (s_fast_forward_enabled ? g_settings.fast_forward_speed : g_settings.emulation_speed); @@ -2862,7 +2861,7 @@ void System::UpdateSpeedLimiterState() } // When syncing to host and using vsync, we don't need to sleep. - s_syncing_to_host_with_vsync = (s_syncing_to_host && IsVSyncEffectivelyEnabled()); + s_syncing_to_host_with_vsync = (s_syncing_to_host && IsHostVSyncEffectivelyEnabled()); if (s_syncing_to_host_with_vsync) { Log_InfoPrintf("Using host vsync for throttling."); @@ -2886,22 +2885,27 @@ void System::UpdateSpeedLimiterState() void System::UpdateDisplaySync() { - const bool vsync_enabled = IsVSyncEffectivelyEnabled(); - const bool syncing_to_host_vsync = (s_syncing_to_host && vsync_enabled); + const bool vsync_enabled = IsHostVSyncEffectivelyEnabled(); const float max_display_fps = (s_throttler_enabled || s_syncing_to_host) ? 0.0f : g_settings.display_max_fps; Log_VerboseFmt("VSync: {}{}", vsync_enabled ? "Enabled" : "Disabled", - syncing_to_host_vsync ? " (for throttling)" : ""); + s_syncing_to_host_with_vsync ? " (for throttling)" : ""); Log_VerboseFmt("Max display fps: {}", max_display_fps); Log_VerboseFmt("Preset timing: {}", s_optimal_frame_pacing ? "consistent" : "immediate"); g_gpu_device->SetDisplayMaxFPS(max_display_fps); - g_gpu_device->SetVSyncEnabled(vsync_enabled); + g_gpu_device->SetVSyncEnabled(vsync_enabled, vsync_enabled && !IsHostVSyncUsedForTiming()); } -bool System::IsVSyncEffectivelyEnabled() +bool System::IsHostVSyncEffectivelyEnabled() { // Disable vsync if running outside 100%. - return (g_settings.display_vsync && IsValid() && !IsRunningAtNonStandardSpeed()); + return (g_settings.display_vsync && (s_state != State::Shutdown && s_state != State::Stopping) && + !IsRunningAtNonStandardSpeed()); +} + +bool System::IsHostVSyncUsedForTiming() +{ + return (IsHostVSyncEffectivelyEnabled() && s_syncing_to_host_with_vsync); } bool System::IsFastForwardEnabled() @@ -4505,7 +4509,7 @@ bool System::IsRunningAtNonStandardSpeed() if (!IsValid()) return false; - const float target_speed = System::GetTargetSpeed(); + const float target_speed = GetTargetSpeed(); return (target_speed <= 0.95f || target_speed >= 1.05f); } diff --git a/src/core/system.h b/src/core/system.h index ab1400702..e0e5cdce7 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -466,7 +466,10 @@ void ToggleWidescreen(); bool IsRunningAtNonStandardSpeed(); /// Returns true if vsync should be used. -bool IsVSyncEffectivelyEnabled(); +bool IsHostVSyncEffectivelyEnabled(); + +/// Returns true if vsync is being used for frame pacing. +bool IsHostVSyncUsedForTiming(); /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 6d16ddc67..f64121869 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -197,6 +197,13 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features) m_features.prefer_unused_textures = false; } +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_enabled && m_vsync_prefer_triple_buffer) ? 3 : 2; +} + bool D3D11Device::CreateSwapChain() { if (m_window_info.type != WindowInfo::Type::Win32) @@ -233,7 +240,7 @@ bool D3D11Device::CreateSwapChain() swap_chain_desc.Height = static_cast(client_rc.bottom - client_rc.top); swap_chain_desc.Format = dxgi_format; swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = 3; + swap_chain_desc.BufferCount = GetSwapChainBufferCount(); 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; @@ -593,8 +600,7 @@ std::optional D3D11Device::GetHostRefreshRate() if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && desc.BufferDesc.RefreshRate.Denominator > 0) { - Log_InfoPrintf("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, - desc.BufferDesc.RefreshRate.Denominator); + Log_DevFmt("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); return static_cast(desc.BufferDesc.RefreshRate.Numerator) / static_cast(desc.BufferDesc.RefreshRate.Denominator); } @@ -603,6 +609,25 @@ std::optional D3D11Device::GetHostRefreshRate() return GPUDevice::GetHostRefreshRate(); } +void D3D11Device::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) +{ + if (m_vsync_enabled == enabled && m_vsync_prefer_triple_buffer == prefer_triple_buffer) + return; + + const u32 old_buffer_count = GetSwapChainBufferCount(); + m_vsync_enabled = enabled; + m_vsync_prefer_triple_buffer = prefer_triple_buffer; + if (!m_swap_chain) + return; + + if (GetSwapChainBufferCount() != old_buffer_count) + { + DestroySwapChain(); + if (!CreateSwapChain()) + Panic("Failed to recreate swap chain after vsync change."); + } +} + bool D3D11Device::BeginPresent(bool skip_present) { if (skip_present) diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index e8099b8a3..735de4354 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -97,6 +97,7 @@ public: void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; std::optional GetHostRefreshRate() override; + void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; @@ -135,6 +136,7 @@ private: void SetFeatures(FeatureMask disabled_features); + u32 GetSwapChainBufferCount() const; bool CreateSwapChain(); bool CreateSwapChainRTV(); void DestroySwapChain(); diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index e10b2de04..5ae31f9b6 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -797,6 +797,13 @@ bool D3D12Device::HasSurface() const return static_cast(m_swap_chain); } +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_enabled && m_vsync_prefer_triple_buffer) ? 3 : 2; +} + bool D3D12Device::CreateSwapChain() { if (m_window_info.type != WindowInfo::Type::Win32) @@ -830,7 +837,7 @@ bool D3D12Device::CreateSwapChain() swap_chain_desc.Height = static_cast(client_rc.bottom - client_rc.top); swap_chain_desc.Format = fm.resource_format; swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = 3; + swap_chain_desc.BufferCount = GetSwapChainBufferCount(); swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; @@ -1081,6 +1088,42 @@ std::string D3D12Device::GetDriverInfo() const return ret; } +std::optional D3D12Device::GetHostRefreshRate() +{ + if (m_swap_chain && m_is_exclusive_fullscreen) + { + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && + desc.BufferDesc.RefreshRate.Denominator > 0) + { + Log_DevFmt("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); + return static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + } + } + + return GPUDevice::GetHostRefreshRate(); +} + +void D3D12Device::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) +{ + if (m_vsync_enabled == enabled && m_vsync_prefer_triple_buffer == prefer_triple_buffer) + return; + + const u32 old_buffer_count = GetSwapChainBufferCount(); + m_vsync_enabled = enabled; + m_vsync_prefer_triple_buffer = prefer_triple_buffer; + if (!m_swap_chain) + return; + + if (GetSwapChainBufferCount() != old_buffer_count) + { + DestroySwapChain(); + if (!CreateSwapChain()) + Panic("Failed to recreate swap chain after vsync change."); + } +} + bool D3D12Device::BeginPresent(bool frame_skip) { if (InRenderPass()) diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 59e193698..57c68c7ff 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -118,6 +118,9 @@ 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; + std::optional GetHostRefreshRate() override; + void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) override; + bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; @@ -220,6 +223,7 @@ private: void SetFeatures(FeatureMask disabled_features); + u32 GetSwapChainBufferCount() const; bool CreateSwapChain(); bool CreateSwapChainRTV(); void DestroySwapChainRTVs(); diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 46eba60cf..b0e968bf7 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, bool vsync, bool threaded_presentation, + bool debug_device, bool vsync, bool vsync_prefer_triple_buffer, bool threaded_presentation, std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) { m_vsync_enabled = vsync; + m_vsync_prefer_triple_buffer = vsync_prefer_triple_buffer; m_debug_device = debug_device; if (!AcquireWindow(true)) @@ -590,7 +591,7 @@ void GPUDevice::RenderImGui() } } -void GPUDevice::SetVSyncEnabled(bool enabled) +void GPUDevice::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) { m_vsync_enabled = enabled; } diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 021c5af15..22f5b5413 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -573,8 +573,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, - bool vsync, bool threaded_presentation, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error); + bool vsync, bool vsync_prefer_triple_buffer, bool threaded_presentation, + std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error); void Destroy(); virtual bool HasSurface() const = 0; @@ -673,7 +673,7 @@ public: void RenderImGui(); ALWAYS_INLINE bool IsVSyncEnabled() const { return m_vsync_enabled; } - virtual void SetVSyncEnabled(bool enabled); + virtual void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer); ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } @@ -794,6 +794,7 @@ protected: static Statistics s_stats; bool m_vsync_enabled = false; + bool m_vsync_prefer_triple_buffer = 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 6e8bcc7bd..8333919ac 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -265,7 +265,7 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSyncEnabled(bool enabled) override; + void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) 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 4635984e8..5c4b5c638 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -124,12 +124,13 @@ std::optional MetalDevice::GetHostRefreshRate() return GPUDevice::GetHostRefreshRate(); } -void MetalDevice::SetVSyncEnabled(bool enabled) +void MetalDevice::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) { - if (m_vsync_enabled == enabled) + if (m_vsync_enabled == enabled && m_vsync_prefer_triple_buffer == prefer_triple_buffer) return; m_vsync_enabled = enabled; + m_vsync_prefer_triple_buffer = prefer_triple_buffer; if (m_layer != nil) [m_layer setDisplaySyncEnabled:enabled]; } diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index cd89145b3..6b6dceefd 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -238,12 +238,13 @@ void OpenGLDevice::InsertDebugMessage(const char* msg) #endif } -void OpenGLDevice::SetVSyncEnabled(bool enabled) +void OpenGLDevice::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) { - if (m_vsync_enabled == enabled) + if (m_vsync_enabled == enabled && m_vsync_prefer_triple_buffer == prefer_triple_buffer) return; m_vsync_enabled = enabled; + m_vsync_prefer_triple_buffer = prefer_triple_buffer; SetSwapInterval(); } @@ -583,7 +584,7 @@ void OpenGLDevice::SetSwapInterval() return; // Window framebuffer has to be bound to call SetSwapInterval. - const s32 interval = m_vsync_enabled ? (m_gl_context->SupportsNegativeSwapInterval() ? -1 : 1) : 0; + const s32 interval = m_vsync_enabled ? 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 2e5e1cae9..6aaf4c75e 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 SetVSyncEnabled(bool enabled) override; + void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) 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 b967925c4..e324e736d 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -436,6 +436,14 @@ 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. + return m_vsync_enabled ? (m_vsync_prefer_triple_buffer ? VK_PRESENT_MODE_MAILBOX_KHR : VK_PRESENT_MODE_FIFO_KHR) : + VK_PRESENT_MODE_IMMEDIATE_KHR; +} + bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer) { u32 queue_family_count; @@ -2041,7 +2049,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta 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, SelectPresentMode(), m_exclusive_fullscreen_control); if (!m_swap_chain) { Error::SetStringView(error, "Failed to create swap chain"); @@ -2262,7 +2270,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, SelectPresentMode(), m_exclusive_fullscreen_control); if (!m_swap_chain) { Log_ErrorPrintf("Failed to create swap chain"); @@ -2338,25 +2346,22 @@ std::string VulkanDevice::GetDriverInfo() const return ret; } -void VulkanDevice::SetVSyncEnabled(bool enabled) +void VulkanDevice::SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) { - if (m_vsync_enabled == enabled) + if (m_vsync_enabled == enabled && m_vsync_prefer_triple_buffer == prefer_triple_buffer) return; m_vsync_enabled = enabled; + m_vsync_prefer_triple_buffer = prefer_triple_buffer; 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->SetVSyncEnabled(enabled)) + if (!m_swap_chain->SetRequestedPresentMode(SelectPresentMode())) { - // Try switching back to the old mode.. - if (!m_swap_chain->SetVSyncEnabled(!enabled)) - { - Panic("Failed to reset old vsync mode after failure"); - m_swap_chain.reset(); - } + Panic("Failed to update swap chain present mode."); + m_swap_chain.reset(); } } @@ -2390,6 +2395,7 @@ bool VulkanDevice::BeginPresent(bool frame_skip) VkResult res = m_swap_chain->AcquireNextImage(); if (res != VK_SUCCESS) { + LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); m_swap_chain->ReleaseCurrentImage(); if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) @@ -2416,7 +2422,6 @@ bool VulkanDevice::BeginPresent(bool frame_skip) if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) { // Still submit the command buffer, otherwise we'll end up with several frames waiting. - LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); SubmitCommandBuffer(false); TrimTexturePool(); return false; diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 374f691cd..f6265f593 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 SetVSyncEnabled(bool enabled) override; + void SetVSyncEnabled(bool enabled, bool prefer_triple_buffer) override; bool BeginPresent(bool skip_present) override; void EndPresent(bool explicit_present) override; @@ -324,6 +324,7 @@ 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 daf9a46fe..527a142e8 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -70,10 +70,10 @@ static const char* PresentModeToString(VkPresentModeKHR mode) } } -VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, +VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control) - : m_window_info(wi), m_surface(surface), m_exclusive_fullscreen_control(exclusive_fullscreen_control), - m_vsync_enabled(vsync) + : m_window_info(wi), m_surface(surface), m_requested_present_mode(present_mode), + m_exclusive_fullscreen_control(exclusive_fullscreen_control) { } @@ -208,11 +208,12 @@ 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, + VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control) { std::unique_ptr swap_chain = - std::unique_ptr(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control)); + std::unique_ptr(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control)); if (!swap_chain->CreateSwapChain()) return nullptr; @@ -252,9 +253,9 @@ std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurface return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; } - Log_ErrorPrintf("Failed to find a suitable format for swap chain buffers. Available formats were:"); + Log_ErrorPrint("Failed to find a suitable format for swap chain buffers. Available formats were:"); for (const VkSurfaceFormatKHR& sf : surface_formats) - Log_ErrorPrintf(" %u", static_cast(sf.format)); + Log_ErrorFmt(" {}", static_cast(sf.format)); return std::nullopt; } @@ -290,25 +291,19 @@ std::optional VulkanSwapChain::SelectPresentMode(VkSurfaceKHR { selected_mode = requested_mode; } - else if (requested_mode != VK_PRESENT_MODE_FIFO_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + else if (requested_mode == VK_PRESENT_MODE_IMMEDIATE_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { - // Prefer mailbox over fifo for adaptive vsync/no-vsync. This way it'll only delay one frame. + // Prefer mailbox over FIFO for vsync-off, since we don't want to block. selected_mode = VK_PRESENT_MODE_MAILBOX_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. - selected_mode = VK_PRESENT_MODE_FIFO_KHR; - } else { - // Fall back to whatever is available. - selected_mode = present_modes[0]; + // Fallback to FIFO if we we can't use mailbox. This should never fail, FIFO is mandated. + selected_mode = VK_PRESENT_MODE_FIFO_KHR; } - Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(requested_mode), - PresentModeToString(selected_mode)); + Log_DevFmt("Preferred present mode: {}, selected: {}", PresentModeToString(requested_mode), + PresentModeToString(selected_mode)); return selected_mode; } @@ -319,11 +314,7 @@ bool VulkanSwapChain::CreateSwapChain() // Select swap chain format and present mode std::optional surface_format = SelectSurfaceFormat(m_surface); - - // Prefer relaxed vsync if available, stalling is bad. - const VkPresentModeKHR requested_mode = - m_vsync_enabled ? VK_PRESENT_MODE_FIFO_RELAXED_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; - std::optional present_mode = SelectPresentMode(m_surface, requested_mode); + std::optional present_mode = SelectPresentMode(m_surface, m_requested_present_mode); if (!surface_format.has_value() || !present_mode.has_value()) return false; @@ -337,12 +328,12 @@ bool VulkanSwapChain::CreateSwapChain() return false; } - // Select number of images in swap chain, we prefer one buffer in the background to work on - u32 image_count = std::max(surface_capabilities.minImageCount + 1u, 2u); - + // 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. - if (surface_capabilities.maxImageCount > 0) - image_count = std::min(image_count, surface_capabilities.maxImageCount); + u32 image_count = std::clamp( + (m_requested_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount, + (surface_capabilities.maxImageCount == 0) ? std::numeric_limits::max() : surface_capabilities.maxImageCount); + Log_DevFmt("Creating a swap chain with {} images", image_count); // 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. @@ -376,7 +367,7 @@ bool VulkanSwapChain::CreateSwapChain() VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage) { - Log_ErrorPrintf("Vulkan: Swap chain does not support usage as color attachment"); + Log_ErrorPrint("Vulkan: Swap chain does not support usage as color attachment"); return false; } @@ -431,19 +422,19 @@ bool VulkanSwapChain::CreateSwapChain() exclusive_win32_info.hmonitor = MonitorFromWindow(reinterpret_cast(m_window_info.window_handle), MONITOR_DEFAULTTONEAREST); if (!exclusive_win32_info.hmonitor) - Log_ErrorPrintf("MonitorFromWindow() for exclusive fullscreen exclusive override failed."); + Log_ErrorPrint("MonitorFromWindow() for exclusive fullscreen exclusive override failed."); Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_info); Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_win32_info); } else { - Log_ErrorPrintf("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported."); + Log_ErrorPrint("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported."); } } #else if (m_exclusive_fullscreen_control.has_value()) - Log_ErrorPrintf("Exclusive fullscreen control requested, but is not supported on this platform."); + Log_ErrorPrint("Exclusive fullscreen control requested, but is not supported on this platform."); #endif res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain); @@ -465,7 +456,7 @@ bool VulkanSwapChain::CreateSwapChain() 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)); + Log_ErrorFmt("Unknown Vulkan surface format {}", static_cast(surface_format->format)); return false; } @@ -633,15 +624,15 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s return true; } -bool VulkanSwapChain::SetVSyncEnabled(bool enabled) +bool VulkanSwapChain::SetRequestedPresentMode(VkPresentModeKHR mode) { - if (m_vsync_enabled == enabled) + if (m_requested_present_mode == mode) return true; - m_vsync_enabled = enabled; + m_requested_present_mode = mode; // Recreate the swap chain with the new present mode. - Log_VerbosePrintf("Recreating swap chain to change present mode."); + Log_VerbosePrint("Recreating swap chain to change present mode."); DestroySwapChainImages(); if (!CreateSwapChain()) { diff --git a/src/util/vulkan_swap_chain.h b/src/util/vulkan_swap_chain.h index 3f126eb29..f85aeddea 100644 --- a/src/util/vulkan_swap_chain.h +++ b/src/util/vulkan_swap_chain.h @@ -25,7 +25,8 @@ 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, + VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control); ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } @@ -73,10 +74,10 @@ 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 SetVSyncEnabled(bool enabled); + bool SetRequestedPresentMode(VkPresentModeKHR mode); private: - VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync, + VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, std::optional exclusive_fullscreen_control); static std::optional SelectSurfaceFormat(VkSurfaceKHR surface); @@ -107,15 +108,16 @@ 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; 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; - u32 m_current_image = 0; - u32 m_current_semaphore = 0; std::optional m_image_acquire_result; std::optional m_exclusive_fullscreen_control; - bool m_vsync_enabled = false; };