From 11327b59eabf5c4c457c9d6004ea66dc9701755e Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Tue, 13 Jul 2021 23:54:06 -0400 Subject: [PATCH] Expose vsync interval with new cvar rpi_vsync_interval and support Vulkan vsync (#2061) * Expose vsync interval with new cvar rpi_vsync_interval On change, the rpi_vsync_interval is broadcasted to a new event on the WindowNotificationBus and all swapchains are recreated with the new vsync value. Signed-off-by: Jeremy Ong * Add vsync interval support to the Vulkan RHI Vsync intervals are not intrinsically supported using the Vulkan swapchain extension. Instead, extra presents are enqueued for each extra vblank requested past 1. Swapchain recreation is triggered when transitioning to and from the FIFO presentation mode (when rpi_vsync_interval transitions from and to 0 respectively). Signed-off-by: Jeremy Ong * Rollback vsync > 1 implementation on Vulkan and leverage *Internal pattern Signed-off-by: Jeremy Ong --- .../AzFramework/Windowing/WindowBus.h | 3 ++ .../RHI/Code/Include/Atom/RHI/SwapChain.h | 5 +++ Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp | 4 ++ .../RHI/Vulkan/Code/Source/RHI/SwapChain.cpp | 40 ++++++++++++++----- .../RHI/Vulkan/Code/Source/RHI/SwapChain.h | 7 ++-- .../Include/Atom/RPI.Public/WindowContext.h | 1 + .../Code/Source/RPI.Public/WindowContext.cpp | 26 +++++++++++- 7 files changed, 73 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h index f21993b80a..0369e703b5 100644 --- a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h +++ b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h @@ -97,6 +97,9 @@ namespace AzFramework //! This is called when the window is deactivated from code or if the user closes the window. virtual void OnWindowClosed() {}; + + //! This is called when vsync interval is changed. + virtual void OnVsyncIntervalChanged(uint32_t interval) { AZ_UNUSED(interval); }; }; using WindowNotificationBus = AZ::EBus; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h index ae4c0dce57..03b7b8831f 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h @@ -123,6 +123,11 @@ namespace AZ /// Returns the index of the current image after the swap. virtual uint32_t PresentInternal() = 0; + virtual void SetVerticalSyncIntervalInternal(uint32_t previousVerticalSyncInterval) + { + AZ_UNUSED(previousVerticalSyncInterval); + } + ////////////////////////////////////////////////////////////////////////// SwapChainDescriptor m_descriptor; diff --git a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp index 5d1d938fc8..d0db51ece5 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp @@ -168,7 +168,11 @@ namespace AZ void SwapChain::SetVerticalSyncInterval(uint32_t verticalSyncInterval) { + uint32_t previousVsyncInterval = m_descriptor.m_verticalSyncInterval; + m_descriptor.m_verticalSyncInterval = verticalSyncInterval; + + SetVerticalSyncIntervalInternal(previousVsyncInterval); } const AttachmentId& SwapChain::GetAttachmentId() const diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp index d356d61cef..0935e1acd8 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp @@ -56,6 +56,18 @@ namespace AZ m_swapChainBarrier.m_isValid = true; } + void SwapChain::SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) + { + uint32_t verticalSyncInterval = GetDescriptor().m_verticalSyncInterval; + if (verticalSyncInterval == 0 || previousVsyncInterval == 0) + { + // The presentation mode may change when transitioning to or from a vsynced presentation mode + // In this case, the swapchain must be recreated. + InvalidateNativeSwapChain(); + BuildNativeSwapChain(GetDescriptor().m_dimensions, verticalSyncInterval); + } + } + void SwapChain::SetNameInternal(const AZStd::string_view& name) { if (IsInitialized() && !name.empty()) @@ -84,7 +96,7 @@ namespace AZ auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - result = BuildNativeSwapChain(swapchainDimensions); + result = BuildNativeSwapChain(swapchainDimensions, descriptor.m_verticalSyncInterval); RETURN_RESULT_IF_UNSUCCESSFUL(result); uint32_t imageCount = 0; VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &imageCount, nullptr); @@ -166,7 +178,7 @@ namespace AZ auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - BuildNativeSwapChain(resizeDimensions); + BuildNativeSwapChain(resizeDimensions, GetDescriptor().m_verticalSyncInterval); resizeDimensions.m_imageCount = 0; VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &resizeDimensions.m_imageCount, nullptr); @@ -256,7 +268,8 @@ namespace AZ info.pImageIndices = &imageIndex; info.pResults = nullptr; - const VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); + VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); + // Resizing window cause recreation of SwapChain after calling this method, // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen at this point. AZ_Assert(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Failed to present swapchain %s", GetName().GetCStr()); @@ -321,9 +334,17 @@ namespace AZ return surfaceFormats[0]; } - VkPresentModeKHR SwapChain::GetSupportedPresentMode() const + VkPresentModeKHR SwapChain::GetSupportedPresentMode(uint32_t verticalSyncInterval) const { AZ_Assert(m_surface, "Surface has not been initialized."); + + if (verticalSyncInterval > 0) + { + // When a non-zero vsync interval is requested, the FIFO presentation mode (always available) + // is usable without needing to query available presentation modes. + return VK_PRESENT_MODE_FIFO_KHR; + } + auto& device = static_cast(GetDevice()); const auto& physicalDevice = static_cast(device.GetPhysicalDevice()); @@ -335,12 +356,12 @@ namespace AZ AZStd::vector supportedModes(modeCount); AssertSuccess(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice.GetNativePhysicalDevice(), m_surface->GetNativeSurface(), &modeCount, supportedModes.data())); - VkPresentModeKHR preferedModes[] = {VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR}; - for (VkPresentModeKHR preferedMode : preferedModes) + VkPresentModeKHR preferredModes[] = {VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR}; + for (VkPresentModeKHR preferredMode : preferredModes) { for (VkPresentModeKHR supportedMode : supportedModes) { - if (supportedMode == preferedMode) + if (supportedMode == preferredMode) { return supportedMode; } @@ -370,7 +391,7 @@ namespace AZ return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; } - RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions) + RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval) { AZ_Assert(m_nativeSwapChain == VK_NULL_HANDLE, "Vulkan's native SwapChain has been initialized already."); auto& device = static_cast(GetDevice()); @@ -421,7 +442,7 @@ namespace AZ createInfo.pQueueFamilyIndices = familyIndices.empty() ? nullptr : familyIndices.data(); createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; createInfo.compositeAlpha = GetSupportedCompositeAlpha(); - createInfo.presentMode = GetSupportedPresentMode(); + createInfo.presentMode = GetSupportedPresentMode(verticalSyncInterval); createInfo.clipped = VK_FALSE; createInfo.oldSwapchain = VK_NULL_HANDLE; @@ -442,6 +463,7 @@ namespace AZ imageAvailableSemaphore->GetNativeSemaphore(), VK_NULL_HANDLE, acquiredImageIndex); + // Resizing window cause recreation of SwapChain before calling this method, // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen. AssertSuccess(vkResult); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h index b77c305e46..475375a5b3 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h @@ -49,7 +49,7 @@ namespace AZ const CommandQueue& GetPresentationQueue() const; void QueueBarrier(const VkPipelineStageFlags src, const VkPipelineStageFlags dst, const VkImageMemoryBarrier& imageBarrier); - + private: SwapChain() = default; @@ -65,14 +65,15 @@ namespace AZ RHI::ResultCode InitImageInternal(const RHI::SwapChain::InitImageRequest& request) override; RHI::ResultCode ResizeInternal(const RHI::SwapChainDimensions& dimensions, RHI::SwapChainDimensions* nativeDimensions) override; uint32_t PresentInternal() override; + void SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) override; ////////////////////////////////////////////////////////////////////// RHI::ResultCode BuildSurface(const RHI::SwapChainDescriptor& descriptor); bool ValidateSurfaceDimensions(const RHI::SwapChainDimensions& dimensions); VkSurfaceFormatKHR GetSupportedSurfaceFormat(const RHI::Format format) const; - VkPresentModeKHR GetSupportedPresentMode() const; + VkPresentModeKHR GetSupportedPresentMode(uint32_t verticalSyncInterval) const; VkCompositeAlphaFlagBitsKHR GetSupportedCompositeAlpha() const; - RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions); + RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval); RHI::ResultCode AcquireNewImage(uint32_t* acquiredImageIndex); void InvalidateSurface(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h index fc809c7d39..9f2f40fbbe 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h @@ -71,6 +71,7 @@ namespace AZ // WindowNotificationBus::Handler overrides ... void OnWindowResized(uint32_t width, uint32_t height) override; void OnWindowClosed() override; + void OnVsyncIntervalChanged(uint32_t interval) override; // ExclusiveFullScreenRequestBus::Handler overrides ... bool IsExclusiveFullScreenPreferred() const override; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp index 7a5982c10d..16ad7a1155 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp @@ -13,6 +13,22 @@ #include +#include +#include + + +void OnVsyncIntervalChanged(uint32_t const& interval) +{ + AzFramework::WindowNotificationBus::Broadcast( + &AzFramework::WindowNotificationBus::Events::OnVsyncIntervalChanged, + AZ::GetClamp(interval, 0u, 4u)); +} + +// NOTE: On change, broadcasts the new requested vsync interval to all windows. +// The value of the vsync interval is constrained between 0 and 4 +// Vsync intervals greater than 1 are not currently supported on the Vulkan RHI (see #2061 for discussion) +AZ_CVAR(uint32_t, rpi_vsync_interval, 0, OnVsyncIntervalChanged, AZ::ConsoleFunctorFlags::Null, "Set swapchain vsync interval"); + namespace AZ { namespace RPI @@ -103,6 +119,14 @@ namespace AZ AzFramework::WindowNotificationBus::Handler::BusDisconnect(m_windowHandle); } + void WindowContext::OnVsyncIntervalChanged(uint32_t interval) + { + if (m_swapChain->GetDescriptor().m_verticalSyncInterval != interval) + { + m_swapChain->SetVerticalSyncInterval(interval); + } + } + bool WindowContext::IsExclusiveFullScreenPreferred() const { return m_swapChain->IsExclusiveFullScreenPreferred(); @@ -135,7 +159,7 @@ namespace AZ RHI::SwapChainDescriptor descriptor; descriptor.m_window = windowHandle; - descriptor.m_verticalSyncInterval = 0; + descriptor.m_verticalSyncInterval = rpi_vsync_interval; descriptor.m_dimensions.m_imageWidth = width; descriptor.m_dimensions.m_imageHeight = height; descriptor.m_dimensions.m_imageCount = 3;