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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user