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 <jeremycong@gmail.com>

* 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 <jeremycong@gmail.com>

* Rollback vsync > 1 implementation on Vulkan and leverage *Internal pattern

Signed-off-by: Jeremy Ong <jeremycong@gmail.com>
This commit is contained in:
Jeremy Ong
2021-07-13 23:54:06 -04:00
committed by GitHub
parent abb70bcfa4
commit 11327b59ea
7 changed files with 73 additions and 13 deletions
@@ -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<WindowNotifications>;
@@ -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;
@@ -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
@@ -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<Device&>(GetDevice());
const auto& physicalDevice = static_cast<const PhysicalDevice&>(device.GetPhysicalDevice());
@@ -335,12 +356,12 @@ namespace AZ
AZStd::vector<VkPresentModeKHR> 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<Device&>(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);
@@ -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();
@@ -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;
@@ -13,6 +13,22 @@
#include <Atom/RHI/Factory.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Math/MathUtils.h>
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;