GPUDevice: Make vsync actually tear-free

Apparently users prefer stutter over tearing...
This commit is contained in:
Stenzek 2024-05-23 13:52:24 +10:00
parent dd98b630ea
commit 6cad97b404
No known key found for this signature in database
17 changed files with 178 additions and 87 deletions

View File

@ -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)

View File

@ -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);
}

View File

@ -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();

View File

@ -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)

View File

@ -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();

View File

@ -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())

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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];
}

View File

@ -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, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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())
{

View File

@ -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;
};