11327b59ea
* 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>
226 lines
8.8 KiB
C++
226 lines
8.8 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <Atom/RPI.Public/RPISystemInterface.h>
|
|
#include <Atom/RPI.Public/WindowContext.h>
|
|
#include <Atom/RPI.Public/WindowContextBus.h>
|
|
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
|
|
#include <Atom/RPI.Public/Pass/Specific/SwapChainPass.h>
|
|
|
|
#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
|
|
{
|
|
void WindowContext::Initialize(RHI::Device& device, AzFramework::NativeWindowHandle windowHandle)
|
|
{
|
|
m_windowHandle = windowHandle;
|
|
|
|
CreateSwapChain(device);
|
|
|
|
FillWindowState(m_swapChain->GetDescriptor().m_dimensions.m_imageWidth, m_swapChain->GetDescriptor().m_dimensions.m_imageHeight);
|
|
|
|
AzFramework::WindowNotificationBus::Handler::BusConnect(m_windowHandle);
|
|
AzFramework::ExclusiveFullScreenRequestBus::Handler::BusConnect(m_windowHandle);
|
|
}
|
|
|
|
AZStd::vector<ViewportContextPtr> WindowContext::GetAssociatedViewportContexts()
|
|
{
|
|
AZStd::vector<ViewportContextPtr> associatedContexts;
|
|
for (auto viewportContextWeakRef : m_viewportContexts)
|
|
{
|
|
if (auto viewportContextRef = viewportContextWeakRef.lock())
|
|
{
|
|
associatedContexts.push_back(viewportContextRef);
|
|
}
|
|
}
|
|
return associatedContexts;
|
|
}
|
|
|
|
void WindowContext::RegisterAssociatedViewportContext(ViewportContextPtr viewportContext)
|
|
{
|
|
m_viewportContexts.push_back(viewportContext);
|
|
}
|
|
|
|
void WindowContext::Shutdown()
|
|
{
|
|
AzFramework::ExclusiveFullScreenRequestBus::Handler::BusDisconnect(m_windowHandle);
|
|
AzFramework::WindowNotificationBus::Handler::BusDisconnect(m_windowHandle);
|
|
|
|
DestroySwapChain();
|
|
}
|
|
|
|
const RHI::AttachmentId& WindowContext::GetSwapChainAttachmentId() const
|
|
{
|
|
return m_swapChain->GetAttachmentId();
|
|
}
|
|
|
|
const RHI::Ptr<RHI::SwapChain>& WindowContext::GetSwapChain() const
|
|
{
|
|
return m_swapChain;
|
|
}
|
|
|
|
const RHI::Viewport& WindowContext::GetViewport() const
|
|
{
|
|
return m_viewportDefault;
|
|
}
|
|
|
|
const RHI::Scissor& WindowContext::GetScissor() const
|
|
{
|
|
return m_scissorDefault;
|
|
}
|
|
|
|
void WindowContext::OnWindowResized(uint32_t width, uint32_t height)
|
|
{
|
|
const AZ::RHI::SwapChainDimensions& currentDimensions = m_swapChain->GetDescriptor().m_dimensions;
|
|
if (width != currentDimensions.m_imageWidth || height != currentDimensions.m_imageHeight)
|
|
{
|
|
// Get current dimension and only overwrite the sizes.
|
|
RHI::SwapChainDimensions dimensions = m_swapChain->GetDescriptor().m_dimensions;
|
|
dimensions.m_imageWidth = AZStd::max(width, 1u);
|
|
dimensions.m_imageHeight = AZStd::max(height, 1u);
|
|
dimensions.m_imageFormat = GetSwapChainFormat(m_swapChain->GetDevice());
|
|
|
|
FillWindowState(dimensions.m_imageWidth, dimensions.m_imageHeight);
|
|
|
|
m_swapChain->Resize(dimensions);
|
|
|
|
WindowContextNotificationBus::Event(m_windowHandle, &WindowContextNotifications::OnViewportResized, width, height);
|
|
}
|
|
}
|
|
|
|
void WindowContext::OnWindowClosed()
|
|
{
|
|
DestroySwapChain();
|
|
|
|
// We don't want to listen to events anymore if the window has closed
|
|
AzFramework::ExclusiveFullScreenRequestBus::Handler::BusDisconnect(m_windowHandle);
|
|
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();
|
|
}
|
|
|
|
bool WindowContext::GetExclusiveFullScreenState() const
|
|
{
|
|
return m_swapChain->GetExclusiveFullScreenState();
|
|
}
|
|
|
|
bool WindowContext::SetExclusiveFullScreenState(bool fullScreenState)
|
|
{
|
|
return m_swapChain->SetExclusiveFullScreenState(fullScreenState);
|
|
}
|
|
|
|
void WindowContext::CreateSwapChain(RHI::Device& device)
|
|
{
|
|
m_swapChain = RHI::Factory::Get().CreateSwapChain();
|
|
|
|
AzFramework::WindowSize windowSize;
|
|
AzFramework::WindowRequestBus::EventResult(
|
|
windowSize,
|
|
m_windowHandle,
|
|
&AzFramework::WindowRequestBus::Events::GetClientAreaSize);
|
|
|
|
uint32_t width = AZStd::max(windowSize.m_width, 1u);
|
|
uint32_t height = AZStd::max(windowSize.m_height, 1u);
|
|
|
|
const RHI::WindowHandle windowHandle = RHI::WindowHandle(reinterpret_cast<uintptr_t>(m_windowHandle));
|
|
|
|
RHI::SwapChainDescriptor descriptor;
|
|
descriptor.m_window = windowHandle;
|
|
descriptor.m_verticalSyncInterval = rpi_vsync_interval;
|
|
descriptor.m_dimensions.m_imageWidth = width;
|
|
descriptor.m_dimensions.m_imageHeight = height;
|
|
descriptor.m_dimensions.m_imageCount = 3;
|
|
descriptor.m_dimensions.m_imageFormat = GetSwapChainFormat(device);
|
|
|
|
AZStd::string attachmentName = AZStd::string::format("WindowContextAttachment_%p", m_windowHandle);
|
|
descriptor.m_attachmentId = RHI::AttachmentId{ attachmentName.c_str() };
|
|
|
|
m_swapChain->Init(device, descriptor);
|
|
}
|
|
|
|
void WindowContext::DestroySwapChain()
|
|
{
|
|
m_swapChain = nullptr;
|
|
}
|
|
|
|
void WindowContext::FillWindowState(const uint32_t width, const uint32_t height)
|
|
{
|
|
m_viewportDefault.m_minX = 0;
|
|
m_viewportDefault.m_minY = 0;
|
|
m_viewportDefault.m_maxX = static_cast<float>(width);
|
|
m_viewportDefault.m_maxY = static_cast<float>(height);
|
|
|
|
m_scissorDefault.m_minX = 0;
|
|
m_scissorDefault.m_minY = 0;
|
|
m_scissorDefault.m_maxX = static_cast<int16_t>(width);
|
|
m_scissorDefault.m_maxY = static_cast<int16_t>(height);
|
|
}
|
|
|
|
RHI::Format WindowContext::GetSwapChainFormat(RHI::Device& device) const
|
|
{
|
|
// Array of preferred format in decreasing order of preference.
|
|
const AZStd::array<RHI::Format, 3> preferredFormats =
|
|
{{
|
|
RHI::Format::R10G10B10A2_UNORM,
|
|
RHI::Format::R8G8B8A8_UNORM,
|
|
RHI::Format::B8G8R8A8_UNORM
|
|
}};
|
|
|
|
auto GetPreferredFormat = [](const AZStd::array<RHI::Format, 3>& preferredFormats, const AZStd::vector<RHI::Format>& supportedFormats) -> RHI::Format
|
|
{
|
|
for (int preferredIndex = 0; preferredIndex < preferredFormats.size(); ++preferredIndex)
|
|
{
|
|
for (int supportedIndex = 0; supportedIndex < supportedFormats.size(); ++supportedIndex)
|
|
{
|
|
if (supportedFormats[supportedIndex] == preferredFormats[preferredIndex])
|
|
{
|
|
return supportedFormats[supportedIndex];
|
|
}
|
|
}
|
|
}
|
|
// If no match found, just return the first supported format
|
|
return supportedFormats[0];
|
|
};
|
|
|
|
const RHI::WindowHandle windowHandle = RHI::WindowHandle(reinterpret_cast<uintptr_t>(m_windowHandle));
|
|
const AZStd::vector<RHI::Format> supportedFormats = device.GetValidSwapChainImageFormats(windowHandle);
|
|
AZ_Assert(!supportedFormats.empty(), "There is no supported format for SwapChain images.");
|
|
|
|
return GetPreferredFormat(preferredFormats, supportedFormats);
|
|
}
|
|
} // namespace RPI
|
|
} // namespace AZ
|