System: Combine VRR and Optimal Frame Pacing

GSync/FreeSync display users should:
 - DISABLE VSync.
 - ENABLE Optimal Frame Pacing.
This commit is contained in:
Stenzek
2024-04-11 21:26:50 +10:00
parent 2ad67ad3ee
commit 88270771da
26 changed files with 224 additions and 289 deletions

View File

@ -631,7 +631,7 @@ bool D3D11Device::BeginPresent(bool skip_present)
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
// the time, when it's more like a couple of percent.
if ((m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) && m_gpu_timing_enabled)
if (m_vsync_enabled && m_gpu_timing_enabled)
PopTimestampQuery();
static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
@ -648,11 +648,11 @@ void D3D11Device::EndPresent()
{
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
if (m_sync_mode != DisplaySyncMode::VSync && m_sync_mode != DisplaySyncMode::VSyncRelaxed && m_gpu_timing_enabled)
if (m_vsync_enabled && 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 (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed)
if (m_vsync_enabled)
m_swap_chain->Present(BoolToUInt32(1), 0);
else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/
m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);

View File

@ -1108,7 +1108,7 @@ void D3D12Device::EndPresent()
SubmitCommandList(false);
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it.
if (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed)
if (m_vsync_enabled)
m_swap_chain->Present(BoolToUInt32(1), 0);
else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/
m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);

View File

@ -271,11 +271,10 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
}
bool GPUDevice::Create(const std::string_view& adapter, const std::string_view& shader_cache_path,
u32 shader_cache_version, bool debug_device, DisplaySyncMode sync_mode,
bool threaded_presentation, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
u32 shader_cache_version, bool debug_device, bool vsync, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
{
m_sync_mode = sync_mode;
m_vsync_enabled = vsync;
m_debug_device = debug_device;
if (!AcquireWindow(true))
@ -586,9 +585,9 @@ void GPUDevice::RenderImGui()
}
}
void GPUDevice::SetSyncMode(DisplaySyncMode mode)
void GPUDevice::SetVSyncEnabled(bool enabled)
{
m_sync_mode = mode;
m_vsync_enabled = enabled;
}
void GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex)

View File

@ -562,7 +562,7 @@ public:
virtual RenderAPI GetRenderAPI() const = 0;
bool Create(const std::string_view& adapter, const std::string_view& shader_cache_path, u32 shader_cache_version,
bool debug_device, DisplaySyncMode sync_mode, bool threaded_presentation,
bool debug_device, bool vsync, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error);
void Destroy();
@ -660,12 +660,8 @@ public:
/// Renders ImGui screen elements. Call before EndPresent().
void RenderImGui();
ALWAYS_INLINE DisplaySyncMode GetSyncMode() const { return m_sync_mode; }
ALWAYS_INLINE bool IsVSyncActive() const
{
return (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed);
}
virtual void SetSyncMode(DisplaySyncMode mode);
ALWAYS_INLINE bool IsVSyncEnabled() const { return m_vsync_enabled; }
virtual void SetVSyncEnabled(bool enabled);
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
@ -780,7 +776,7 @@ private:
protected:
static Statistics s_stats;
DisplaySyncMode m_sync_mode = DisplaySyncMode::Disabled;
bool m_vsync_enabled = false;
bool m_gpu_timing_enabled = false;
bool m_debug_device = false;
};

View File

@ -13,12 +13,3 @@ enum class RenderAPI : u32
OpenGLES,
Metal
};
enum class DisplaySyncMode : u8
{
Disabled,
VSync,
VSyncRelaxed,
VRR,
Count
};

View File

@ -265,7 +265,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetSyncMode(DisplaySyncMode mode) override;
void SetVSyncEnabled(bool enabled) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -127,15 +127,14 @@ bool MetalDevice::GetHostRefreshRate(float* refresh_rate)
return GPUDevice::GetHostRefreshRate(refresh_rate);
}
void MetalDevice::SetSyncMode(DisplaySyncMode mode)
void MetalDevice::SetVSyncEnabled(bool enabled)
{
m_sync_mode = mode;
if (m_vsync_enabled == enabled)
return;
m_vsync_enabled = enabled;
if (m_layer != nil)
{
const bool enabled = (mode == DisplaySyncMode::VSync || mode == DisplaySyncMode::VSyncRelaxed);
[m_layer setDisplaySyncEnabled:enabled];
}
}
bool MetalDevice::CreateDevice(const std::string_view& adapter, bool threaded_presentation,
@ -389,8 +388,7 @@ bool MetalDevice::CreateLayer()
}
});
const bool sync_enabled = (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed);
[m_layer setDisplaySyncEnabled:sync_enabled];
[m_layer setDisplaySyncEnabled:m_vsync_enabled];
DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];

View File

@ -139,7 +139,7 @@ bool OpenGLContextWGL::SwapBuffers()
return ::SwapBuffers(m_dc);
}
bool OpenGLContextWGL::IsCurrent()
bool OpenGLContextWGL::IsCurrent() const
{
return (m_rc && wglGetCurrentContext() == m_rc);
}

View File

@ -238,12 +238,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif
}
void OpenGLDevice::SetSyncMode(DisplaySyncMode mode)
void OpenGLDevice::SetVSyncEnabled(bool enabled)
{
if (m_sync_mode == mode)
if (m_vsync_enabled == enabled)
return;
m_sync_mode = mode;
m_vsync_enabled = enabled;
SetSwapInterval();
}
@ -582,14 +582,13 @@ void OpenGLDevice::SetSwapInterval()
return;
// Window framebuffer has to be bound to call SetSwapInterval.
const s32 interval =
(m_sync_mode == DisplaySyncMode::VSync) ? 1 : ((m_sync_mode == DisplaySyncMode::VSyncRelaxed) ? -1 : 0);
const s32 interval = m_vsync_enabled ? (m_gl_context->SupportsNegativeSwapInterval() ? -1 : 1) : 0;
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (!m_gl_context->SetSwapInterval(interval))
Log_WarningPrintf("Failed to set swap interval to %d", interval);
Log_WarningFmt("Failed to set swap interval to {}", interval);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
}
@ -1096,9 +1095,10 @@ void OpenGLDevice::UnmapUniformBuffer(u32 size)
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_uniform_buffer->GetGLBufferId(), pos, size);
}
void OpenGLDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, GPUPipeline::RenderPassFlag feedback_loop)
void OpenGLDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds,
GPUPipeline::RenderPassFlag feedback_loop)
{
//DebugAssert(!feedback_loop); TODO
// DebugAssert(!feedback_loop); TODO
bool changed = (m_num_current_render_targets != num_rts || m_current_depth_target != ds);
bool needs_ds_clear = (ds && ds->IsClearedOrInvalidated());
bool needs_rt_clear = false;

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 SetSyncMode(DisplaySyncMode mode) override;
void SetVSyncEnabled(bool enabled) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -2023,7 +2023,7 @@ bool VulkanDevice::CreateDevice(const std::string_view& adapter, bool threaded_p
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control);
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control);
if (!m_swap_chain)
{
Error::SetStringView(error, "Failed to create swap chain");
@ -2244,7 +2244,7 @@ bool VulkanDevice::UpdateWindow()
return false;
}
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control);
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control);
if (!m_swap_chain)
{
Log_ErrorPrintf("Failed to create swap chain");
@ -2320,22 +2320,21 @@ std::string VulkanDevice::GetDriverInfo() const
return ret;
}
void VulkanDevice::SetSyncMode(DisplaySyncMode mode)
void VulkanDevice::SetVSyncEnabled(bool enabled)
{
if (m_sync_mode == mode)
if (m_vsync_enabled == enabled)
return;
const DisplaySyncMode prev_mode = m_sync_mode;
m_sync_mode = mode;
m_vsync_enabled = enabled;
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->SetSyncMode(mode))
if (!m_swap_chain->SetVSyncEnabled(enabled))
{
// Try switching back to the old mode..
if (!m_swap_chain->SetSyncMode(prev_mode))
if (!m_swap_chain->SetVSyncEnabled(!enabled))
{
Panic("Failed to reset old vsync mode after failure");
m_swap_chain.reset();

View File

@ -128,7 +128,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetSyncMode(DisplaySyncMode mode) override;
void SetVSyncEnabled(bool enabled) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "vulkan_swap_chain.h"
@ -70,22 +70,10 @@ static const char* PresentModeToString(VkPresentModeKHR mode)
}
}
static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(DisplaySyncMode mode)
{
static constexpr std::array<VkPresentModeKHR, static_cast<size_t>(DisplaySyncMode::Count)> modes = {{
VK_PRESENT_MODE_IMMEDIATE_KHR, // Disabled
VK_PRESENT_MODE_FIFO_KHR, // VSync
VK_PRESENT_MODE_FIFO_RELAXED_KHR, // VSyncRelaxed
VK_PRESENT_MODE_IMMEDIATE_KHR, // VRR ??
}};
return modes[static_cast<size_t>(mode)];
}
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode,
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_requested_present_mode(requested_present_mode),
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_exclusive_fullscreen_control(exclusive_fullscreen_control),
m_vsync_enabled(vsync)
{
}
@ -220,13 +208,11 @@ void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi,
#endif
}
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
DisplaySyncMode sync_mode,
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
std::optional<bool> exclusive_fullscreen_control)
{
const VkPresentModeKHR requested_mode = GetPreferredPresentModeForVsyncMode(sync_mode);
std::unique_ptr<VulkanSwapChain> swap_chain =
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, requested_mode, exclusive_fullscreen_control));
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control));
if (!swap_chain->CreateSwapChain())
return nullptr;
@ -306,7 +292,7 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
}
else if (requested_mode != VK_PRESENT_MODE_FIFO_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
// Prefer mailbox over fifo for adaptive vsync/no-vsync.
// Prefer mailbox over fifo for adaptive vsync/no-vsync. This way it'll only delay one frame.
selected_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else if (requested_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR && CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
@ -333,7 +319,11 @@ bool VulkanSwapChain::CreateSwapChain()
// Select swap chain format and present mode
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, m_requested_present_mode);
// 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);
if (!surface_format.has_value() || !present_mode.has_value())
return false;
@ -643,13 +633,12 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
return true;
}
bool VulkanSwapChain::SetSyncMode(DisplaySyncMode mode)
bool VulkanSwapChain::SetVSyncEnabled(bool enabled)
{
const VkPresentModeKHR present_mode = GetPreferredPresentModeForVsyncMode(mode);
if (m_requested_present_mode == present_mode)
if (m_vsync_enabled == enabled)
return true;
m_requested_present_mode = present_mode;
m_vsync_enabled = enabled;
// Recreate the swap chain with the new present mode.
Log_VerbosePrintf("Recreating swap chain to change present mode.");

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
@ -25,7 +25,7 @@ public:
static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, DisplaySyncMode sync_mode,
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
std::optional<bool> exclusive_fullscreen_control);
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
@ -73,10 +73,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 SetSyncMode(DisplaySyncMode mode);
bool SetVSyncEnabled(bool enabled);
private:
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode,
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
std::optional<bool> exclusive_fullscreen_control);
static std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkSurfaceKHR surface);
@ -111,11 +111,11 @@ private:
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;
};