GPUDevice: Make vsync actually tear-free
Apparently users prefer stutter over tearing...
This commit is contained in:
parent
dd98b630ea
commit
6cad97b404
|
@ -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<GPUDevice::FeatureMask>(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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<u32>(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<float> 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<float>(desc.BufferDesc.RefreshRate.Numerator) /
|
||||
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
|
||||
}
|
||||
|
@ -603,6 +609,25 @@ std::optional<float> 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)
|
||||
|
|
|
@ -97,6 +97,7 @@ public:
|
|||
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
|
||||
|
||||
std::optional<float> 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();
|
||||
|
|
|
@ -797,6 +797,13 @@ bool D3D12Device::HasSurface() const
|
|||
return static_cast<bool>(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<u32>(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<float> 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<float>(desc.BufferDesc.RefreshRate.Numerator) /
|
||||
static_cast<float>(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())
|
||||
|
|
|
@ -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<float> 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();
|
||||
|
|
|
@ -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<bool> 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;
|
||||
}
|
||||
|
|
|
@ -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<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error);
|
||||
bool vsync, bool vsync_prefer_triple_buffer, bool threaded_presentation,
|
||||
std::optional<bool> 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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -124,12 +124,13 @@ std::optional<float> 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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<bool> 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> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
|
||||
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
|
||||
VkPresentModeKHR present_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control)
|
||||
{
|
||||
std::unique_ptr<VulkanSwapChain> swap_chain =
|
||||
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control));
|
||||
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control));
|
||||
if (!swap_chain->CreateSwapChain())
|
||||
return nullptr;
|
||||
|
||||
|
@ -252,9 +253,9 @@ std::optional<VkSurfaceFormatKHR> 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<unsigned>(sf.format));
|
||||
Log_ErrorFmt(" {}", static_cast<unsigned>(sf.format));
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -290,25 +291,19 @@ std::optional<VkPresentModeKHR> 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<VkSurfaceFormatKHR> 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<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, requested_mode);
|
||||
std::optional<VkPresentModeKHR> 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<u32>(
|
||||
(m_requested_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
|
||||
(surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::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<HWND>(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<u32>(surface_format->format));
|
||||
Log_ErrorFmt("Unknown Vulkan surface format {}", static_cast<u32>(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())
|
||||
{
|
||||
|
|
|
@ -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<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
|
||||
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface,
|
||||
VkPresentModeKHR present_mode,
|
||||
std::optional<bool> 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<bool> exclusive_fullscreen_control);
|
||||
|
||||
static std::optional<VkSurfaceFormatKHR> 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<Image> m_images;
|
||||
std::vector<ImageSemaphores> 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<VkResult> m_image_acquire_result;
|
||||
std::optional<bool> m_exclusive_fullscreen_control;
|
||||
bool m_vsync_enabled = false;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue