Add VSync support to Vulkan

This commit is contained in:
2026-04-23 20:25:36 +02:00
parent 0cacc58b53
commit 804315bb3e
4 changed files with 52 additions and 18 deletions
+23 -11
View File
@@ -622,6 +622,16 @@ void GPUDevice::DumpResources()
extern void ClearVertexLayoutCache();
bool UseVSync()
{
bool vsync = Graphics::UseVSync;
if (CommandLine::Options.NoVSync.HasValue())
vsync = !CommandLine::Options.NoVSync.GetValue();
else if (CommandLine::Options.VSync.HasValue())
vsync = CommandLine::Options.VSync.GetValue();
return vsync;
}
void GPUDevice::preDispose()
{
Locker.Lock();
@@ -665,26 +675,26 @@ void GPUDevice::DrawEnd()
{
PROFILE_CPU_NAMED("Present");
// Check if use VSync
bool useVSync = Graphics::UseVSync;
if (CommandLine::Options.NoVSync.HasValue())
useVSync = !CommandLine::Options.NoVSync.GetValue();
else if (CommandLine::Options.VSync.HasValue())
useVSync = CommandLine::Options.VSync.GetValue();
// Find index of the last rendered window task (use vsync only on the last window)
int32 lastWindowIndex = -1;
// Check if use VSync (prioritize the last window, in case of multi-window in Editor)
bool useVSync = UseVSync();
int32 vsyncTask = -1;
for (int32 i = RenderTask::Tasks.Count() - 1; i >= 0; i--)
{
const auto task = RenderTask::Tasks[i];
if (task && task->LastUsedFrame == Engine::FrameCount && task->SwapChain && task->SwapChain->IsReady())
{
lastWindowIndex = i;
vsyncTask = i;
break;
}
}
if (_rendererType == RendererType::Vulkan && RenderTask::Tasks.Contains(_lastVSyncTask))
{
// On Vulkan, maintain the last window that was VSynced to avoid recreating swapchain too often (eg. when using context menus or tooltips)
vsyncTask = RenderTask::Tasks.Find(_lastVSyncTask);
}
// Call present on all used tasks
_lastVSyncTask = nullptr;
int32 presentCount = 0;
bool anyVSync = false;
#if COMPILE_WITH_PROFILER
@@ -696,7 +706,7 @@ void GPUDevice::DrawEnd()
if (task && task->LastUsedFrame == Engine::FrameCount && task->SwapChain && task->SwapChain->IsReady())
{
bool vsync = useVSync;
if (lastWindowIndex != i)
if (vsyncTask != i)
{
// Perform VSync only on the last window
vsync = false;
@@ -711,6 +721,8 @@ void GPUDevice::DrawEnd()
anyVSync |= vsync;
task->OnPresent(vsync);
if (vsync)
_lastVSyncTask = task;
presentCount++;
}
}
+1
View File
@@ -162,6 +162,7 @@ protected:
PrivateData* _res;
Array<GPUResource*> _resources;
CriticalSection _resourcesLock;
RenderTask* _lastVSyncTask = nullptr;
void OnRequestingExit();
@@ -9,7 +9,9 @@
#include "GPUContextVulkan.h"
#include "CmdBufferVulkan.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
@@ -153,6 +155,14 @@ void GPUSwapChainVulkan::Begin(RenderTask* task)
backBuffer.SubmitCmdBuffer = nullptr;
}
}
// Rebuild swapchain if need to
if (_vsyncPending != _vsyncCurrent)
{
LOG(Info, "Changing VSync mode to {}", _vsyncPending ? TEXT("on") : TEXT("off"));
_device->WaitForGPU();
CreateSwapChain(_width, _height);
}
}
bool GPUSwapChainVulkan::Resize(int32 width, int32 height)
@@ -213,6 +223,14 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
// Flush removed resources
_device->DeferredDeletionQueue.ReleaseResources(true);
}
if (_vsyncInit)
{
// Guess the VSync value for the 1st time (Present has not been called yet)
extern bool UseVSync();
_vsyncPending = UseVSync();
_vsyncPending &= _window == Engine::MainWindow; // Don't use VSync on new context menus or tooltips in Editor
_vsyncInit = false;
}
ASSERT(_surface == VK_NULL_HANDLE);
ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
@@ -329,14 +347,14 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
Array<VkPresentModeKHR, InlinedAllocation<8>> presentModes;
presentModes.Resize(presentModesCount);
VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, _surface, &presentModesCount, presentModes.Get()));
if (presentModes.Contains(VK_PRESENT_MODE_MAILBOX_KHR))
{
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else if (presentModes.Contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
if (!_vsyncPending && presentModes.Contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
{
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
else if (!_vsyncPending && presentModes.Contains(VK_PRESENT_MODE_MAILBOX_KHR))
{
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else if (presentModes.Contains(VK_PRESENT_MODE_FIFO_KHR))
{
presentMode = VK_PRESENT_MODE_FIFO_KHR;
@@ -406,6 +424,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
// Cache data
_width = width;
_height = height;
_vsyncCurrent = _vsyncPending;
// Setup back buffers
{
@@ -567,6 +586,9 @@ void GPUSwapChainVulkan::Present(bool vsync)
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
// Update pending VSync value based on the present mode
_vsyncPending = vsync;
// Ensure that backbuffer has been acquired before presenting it to the window
const auto backBuffer = (GPUTextureViewVulkan*)GetBackBufferView();
@@ -65,6 +65,7 @@ private:
int32 _currentImageIndex;
int32 _semaphoreIndex;
int32 _acquiredImageIndex;
bool _vsyncCurrent = false, _vsyncPending = false, _vsyncInit = true;
Array<BackBufferVulkan, FixedAllocation<VULKAN_BACK_BUFFERS_COUNT_MAX>> _backBuffers;
SemaphoreVulkan* _acquiredSemaphore;
@@ -75,7 +76,6 @@ public:
/// <summary>
/// Gets the Vulkan surface.
/// </summary>
/// <returns>The surface object.</returns>
FORCE_INLINE VkSurfaceKHR GetSurface() const
{
return _surface;
@@ -84,7 +84,6 @@ public:
/// <summary>
/// Gets the Vulkan surface swap chain.
/// </summary>
/// <returns>The swap chain object.</returns>
FORCE_INLINE VkSwapchainKHR GetSwapChain() const
{
return _swapChain;