Vulkan: Implement adaptive vsync (FIFO-relaxed)

This commit is contained in:
Connor McLaughlin 2022-02-06 20:41:12 +10:00 committed by refractionpcsx2
parent c74cc9bc12
commit 0ca3167595
7 changed files with 89 additions and 33 deletions

View File

@ -291,7 +291,8 @@ namespace Vulkan
}
bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
bool vsync, bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer)
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
bool enable_validation_layer)
{
pxAssertMsg(!g_vulkan_context, "Has no current context");
@ -370,7 +371,7 @@ namespace Vulkan
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) ||
!g_vulkan_context->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() ||
!g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() ||
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, vsync)) == nullptr))
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, preferred_present_mode)) == nullptr))
{
// Since we are destroying the instance, we're also responsible for destroying the surface.
if (surface != VK_NULL_HANDLE)

View File

@ -70,7 +70,8 @@ namespace Vulkan
// Creates a new context and sets it up as global.
static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
bool vsync, bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer);
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
bool enable_validation_layer);
// Destroys context.
static void Destroy();

View File

@ -85,10 +85,10 @@ static void DestroyMetalLayer(WindowInfo* wi)
namespace Vulkan
{
SwapChain::SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync)
SwapChain::SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR preferred_present_mode)
: m_window_info(wi)
, m_surface(surface)
, m_vsync_enabled(vsync)
, m_preferred_present_mode(preferred_present_mode)
{
}
@ -465,9 +465,10 @@ namespace Vulkan
return {};
}
std::unique_ptr<SwapChain> SwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync)
std::unique_ptr<SwapChain> SwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
VkPresentModeKHR preferred_present_mode)
{
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(wi, surface, vsync);
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(wi, surface, preferred_present_mode);
if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages() || !swap_chain->CreateSemaphores())
return nullptr;
@ -537,27 +538,31 @@ namespace Vulkan
return it != present_modes.end();
};
// If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR.
// This check should not fail with conforming drivers, as the FIFO present mode is mandated by
// the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode.
if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
// Use preferred mode if available.
if (CheckForMode(m_preferred_present_mode))
{
m_present_mode = m_preferred_present_mode;
return true;
}
// Prefer mailbox over fifo for adaptive vsync/no-vsync.
if ((m_preferred_present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR ||
m_preferred_present_mode == VK_PRESENT_MODE_IMMEDIATE_KHR) &&
CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
return true;
}
// Fallback to FIFO if we're using any kind of vsync.
if (m_preferred_present_mode == VK_PRESENT_MODE_FIFO_KHR || m_preferred_present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR)
{
// This should never fail, FIFO is mandated.
if (CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
{
m_present_mode = VK_PRESENT_MODE_FIFO_KHR;
return true;
}
// Prefer screen-tearing, if possible, for lowest latency.
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
{
m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
return true;
}
// Use optimized-vsync above vsync.
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
return true;
}
// Fall back to whatever is available.
@ -581,6 +586,9 @@ namespace Vulkan
if (!SelectSurfaceFormat() || !SelectPresentMode())
return false;
DevCon.WriteLn("(SwapChain) Preferred present mode: %s, selected: %s",
Util::PresentModeToString(m_preferred_present_mode), Util::PresentModeToString(m_present_mode));
// 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);
@ -771,13 +779,13 @@ namespace Vulkan
return true;
}
bool SwapChain::SetVSync(bool enabled)
bool SwapChain::SetVSync(VkPresentModeKHR preferred_mode)
{
if (m_vsync_enabled == enabled)
if (m_preferred_present_mode == preferred_mode)
return true;
// Recreate the swap chain with the new present mode.
m_vsync_enabled = enabled;
m_preferred_present_mode = preferred_mode;
return RecreateSwapChain();
}

View File

@ -27,7 +27,7 @@ namespace Vulkan
class SwapChain
{
public:
SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync);
SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR preferred_present_mode);
~SwapChain();
// Creates a vulkan-renderable surface for the specified window handle.
@ -47,12 +47,13 @@ namespace Vulkan
VkInstance instance, VkPhysicalDevice physical_device, const WindowInfo& wi);
// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<SwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync);
static std::unique_ptr<SwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface,
VkPresentModeKHR preferred_present_mode);
__fi VkSurfaceKHR GetSurface() const { return m_surface; }
__fi VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; }
__fi VkFormat GetTextureFormat() const { return m_surface_format.format; }
__fi bool IsVSyncEnabled() const { return m_vsync_enabled; }
__fi VkPresentModeKHR GetPreferredPresentMode() const { return m_preferred_present_mode; }
__fi VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
__fi const WindowInfo& GetWindowInfo() const { return m_window_info; }
__fi u32 GetWidth() const { return m_window_info.surface_width; }
@ -74,7 +75,13 @@ namespace Vulkan
bool RecreateSwapChain();
// Change vsync enabled state. This may fail as it causes a swapchain recreation.
bool SetVSync(bool enabled);
bool SetVSync(VkPresentModeKHR preferred_mode);
// Returns true if the current present mode is synchronizing (adaptive or hard).
bool IsPresentModeSynchronizing() const
{
return (m_present_mode == VK_PRESENT_MODE_FIFO_KHR || m_present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR);
}
private:
bool SelectSurfaceFormat();
@ -102,6 +109,7 @@ namespace Vulkan
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
VkSurfaceFormatKHR m_surface_format = {};
VkPresentModeKHR m_preferred_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkRenderPass m_load_render_pass = VK_NULL_HANDLE;
@ -113,6 +121,5 @@ namespace Vulkan
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
std::vector<SwapChainImage> m_images;
u32 m_current_image = 0;
bool m_vsync_enabled = false;
};
} // namespace Vulkan

View File

@ -352,6 +352,33 @@ namespace Vulkan
}
}
const char* PresentModeToString(VkPresentModeKHR mode)
{
switch (mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "VK_PRESENT_MODE_MAILBOX_KHR";
case VK_PRESENT_MODE_FIFO_KHR:
return "VK_PRESENT_MODE_FIFO_KHR";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR";
case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR";
default:
return "UNKNOWN_VK_PRESENT_MODE";
}
}
void LogVulkanResult(const char* func_name, VkResult res, const char* msg, ...)
{
std::va_list ap;

View File

@ -53,6 +53,7 @@ namespace Vulkan
void AddPointerToChain(void* head, const void* ptr);
const char* VkResultToString(VkResult res);
const char* PresentModeToString(VkPresentModeKHR mode);
void LogVulkanResult(const char* func_name, VkResult res, const char* msg, ...) /*printflike(4, 5)*/;
#define LOG_VULKAN_ERROR(res, ...) ::Vulkan::Util::LogVulkanResult(__func__, res, __VA_ARGS__)

View File

@ -36,6 +36,16 @@ private:
Vulkan::Texture m_texture;
};
static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(VsyncMode mode)
{
if (mode == VsyncMode::On)
return VK_PRESENT_MODE_FIFO_KHR;
else if (mode == VsyncMode::Adaptive)
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
else
return VK_PRESENT_MODE_IMMEDIATE_KHR;
}
VulkanHostDisplay::VulkanHostDisplay() = default;
VulkanHostDisplay::~VulkanHostDisplay()
@ -85,7 +95,7 @@ bool VulkanHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi)
return false;
}
m_swap_chain = Vulkan::SwapChain::Create(wi_copy, surface, m_vsync_mode != VsyncMode::Off);
m_swap_chain = Vulkan::SwapChain::Create(wi_copy, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode));
if (!m_swap_chain)
{
Console.Error("Failed to create swap chain");
@ -221,7 +231,7 @@ void VulkanHostDisplay::SetVSync(VsyncMode mode)
// This swap chain should not be used by the current buffer, thus safe to destroy.
g_vulkan_context->WaitForGPUIdle();
m_swap_chain->SetVSync(mode != VsyncMode::Off);
m_swap_chain->SetVSync(GetPreferredPresentModeForVsyncMode(mode));
m_vsync_mode = mode;
}
@ -232,7 +242,8 @@ bool VulkanHostDisplay::CreateRenderDevice(
WindowInfo local_wi(wi);
if (!Vulkan::Context::Create(
adapter_name, &local_wi, &m_swap_chain, vsync != VsyncMode::Off, threaded_presentation, debug_device, debug_device))
adapter_name, &local_wi, &m_swap_chain, GetPreferredPresentModeForVsyncMode(vsync),
threaded_presentation, debug_device, debug_device))
{
Console.Error("Failed to create Vulkan context");
m_window_info = {};
@ -378,7 +389,7 @@ void VulkanHostDisplay::EndPresent()
g_vulkan_context->SubmitCommandBuffer(m_swap_chain->GetImageAvailableSemaphore(),
m_swap_chain->GetRenderingFinishedSemaphore(), m_swap_chain->GetSwapChain(),
m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsVSyncEnabled());
m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsPresentModeSynchronizing());
g_vulkan_context->MoveToNextCommandBuffer();
}