diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 684e73f3a..c96dcc0a3 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -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++; } } diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index fbef26a6d..1f0a1007d 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -162,6 +162,7 @@ protected: PrivateData* _res; Array _resources; CriticalSection _resourcesLock; + RenderTask* _lastVSyncTask = nullptr; void OnRequestingExit(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index d270ea89e..fd46159bb 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -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> 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(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h index 638c16bea..34972edd4 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h @@ -65,6 +65,7 @@ private: int32 _currentImageIndex; int32 _semaphoreIndex; int32 _acquiredImageIndex; + bool _vsyncCurrent = false, _vsyncPending = false, _vsyncInit = true; Array> _backBuffers; SemaphoreVulkan* _acquiredSemaphore; @@ -75,7 +76,6 @@ public: /// /// Gets the Vulkan surface. /// - /// The surface object. FORCE_INLINE VkSurfaceKHR GetSurface() const { return _surface; @@ -84,7 +84,6 @@ public: /// /// Gets the Vulkan surface swap chain. /// - /// The swap chain object. FORCE_INLINE VkSwapchainKHR GetSwapChain() const { return _swapChain;