From 7e108bbc5844923b2c02afe2e556d27824bd2ef5 Mon Sep 17 00:00:00 2001
From: Carsten Rudolph <18394207+crud89@users.noreply.github.com>
Date: Fri, 1 Mar 2024 16:02:36 +0100
Subject: [PATCH 1/5] Implement vsync support on swap chains.
---
.../include/litefx/backends/dx12.hpp | 15 ++++--
src/Backends/DirectX12/src/device.cpp | 10 ++--
src/Backends/DirectX12/src/swapchain.cpp | 39 +++++++++-----
.../Vulkan/include/litefx/backends/vulkan.hpp | 15 ++++--
src/Backends/Vulkan/src/device.cpp | 10 ++--
src/Backends/Vulkan/src/swapchain.cpp | 51 ++++++++++++-------
.../include/litefx/rendering_api.hpp | 22 ++++++--
7 files changed, 108 insertions(+), 54 deletions(-)
diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp
index 0508e3fa3..d15a73d91 100644
--- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp
+++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp
@@ -1713,8 +1713,9 @@ namespace LiteFX::Rendering::Backends {
/// The device that owns the swap chain.
/// The initial surface format.
/// The initial size of the render area.
+ /// `true` if vertical synchronization should be used, otherwise `false`.
/// The initial number of buffers.
- explicit DirectX12SwapChain(const DirectX12Device& device, Format surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, UInt32 buffers = 3);
+ explicit DirectX12SwapChain(const DirectX12Device& device, Format surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, UInt32 buffers = 3, bool enableVsync = false);
DirectX12SwapChain(const DirectX12SwapChain&) = delete;
DirectX12SwapChain(DirectX12SwapChain&&) = delete;
virtual ~DirectX12SwapChain() noexcept;
@@ -1756,6 +1757,9 @@ namespace LiteFX::Rendering::Backends {
///
const Size2d& renderArea() const noexcept override;
+ ///
+ bool verticalSynchronization() const noexcept override;
+
///
IDirectX12Image* image(UInt32 backBuffer) const override;
@@ -1776,7 +1780,7 @@ namespace LiteFX::Rendering::Backends {
void addTimingEvent(SharedPtr timingEvent) override;
///
- void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) override;
+ void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync = false) override;
///
[[nodiscard]] UInt32 swapBackBuffer() const override;
@@ -1881,12 +1885,13 @@ namespace LiteFX::Rendering::Backends {
/// The adapter the device uses for drawing.
/// The surface, the device should draw to.
/// The initial surface format, device uses for drawing.
- /// The initial size of the frame buffers.
- /// The initial number of frame buffers.
+ /// The initial size of the render area.
+ /// The initial number of back buffers.
+ /// The initial setting for vertical synchronization.
/// The features that should be supported by this device.
/// The size of the global heap for constant buffers, shader resources and images.
/// The size of the global heap for samplers.
- explicit DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features = {}, UInt32 globalBufferHeapSize = 524287, UInt32 globalSamplerHeapSize = 2048);
+ explicit DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync = false, GraphicsDeviceFeatures features = {}, UInt32 globalBufferHeapSize = 524287, UInt32 globalSamplerHeapSize = 2048);
DirectX12Device(const DirectX12Device&) = delete;
DirectX12Device(DirectX12Device&&) = delete;
diff --git a/src/Backends/DirectX12/src/device.cpp b/src/Backends/DirectX12/src/device.cpp
index 3ca63bcd5..20d3abd3d 100644
--- a/src/Backends/DirectX12/src/device.cpp
+++ b/src/Backends/DirectX12/src/device.cpp
@@ -181,9 +181,9 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement {
m_factory = makeUnique(*m_parent);
}
- void createSwapChain(Format format, const Size2d& frameBufferSize, UInt32 frameBuffers)
+ void createSwapChain(Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync)
{
- m_swapChain = makeUnique(*m_parent, format, frameBufferSize, frameBuffers);
+ m_swapChain = makeUnique(*m_parent, format, renderArea, backBuffers, enableVsync);
}
void createQueues()
@@ -260,11 +260,11 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement {
// ------------------------------------------------------------------------------------------------
DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr&& surface, GraphicsDeviceFeatures features) :
- DirectX12Device(backend, adapter, std::move(surface), Format::B8G8R8A8_SRGB, { 800, 600 }, 3, features)
+ DirectX12Device(backend, adapter, std::move(surface), Format::B8G8R8A8_SRGB, { 800, 600 }, 3, false, features)
{
}
-DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features, UInt32 globalBufferHeapSize, UInt32 globalSamplerHeapSize) :
+DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync, GraphicsDeviceFeatures features, UInt32 globalBufferHeapSize, UInt32 globalSamplerHeapSize) :
ComResource(nullptr), m_impl(makePimpl(this, adapter, std::move(surface), backend, globalBufferHeapSize, globalSamplerHeapSize))
{
LITEFX_DEBUG(DIRECTX12_LOG, "Creating DirectX 12 device {{ Surface: {0}, Adapter: {1} }}...", fmt::ptr(&surface), adapter.deviceId());
@@ -281,7 +281,7 @@ DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX1
this->handle() = m_impl->initialize(features);
m_impl->createQueues();
m_impl->createFactory();
- m_impl->createSwapChain(format, frameBufferSize, frameBuffers);
+ m_impl->createSwapChain(format, renderArea, backBuffers, enableVsync);
m_impl->createBlitPipeline();
}
diff --git a/src/Backends/DirectX12/src/swapchain.cpp b/src/Backends/DirectX12/src/swapchain.cpp
index b4271883d..6a561d83f 100644
--- a/src/Backends/DirectX12/src/swapchain.cpp
+++ b/src/Backends/DirectX12/src/swapchain.cpp
@@ -19,6 +19,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement> m_presentImages{ };
Array m_presentFences{ };
bool m_supportsVariableRefreshRates{ false };
+ bool m_vsync{ false };
const DirectX12Device& m_device;
Array> m_timingEvents;
@@ -46,7 +47,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement initialize(Format format, const Size2d& frameBufferSize, UInt32 frameBuffers)
+ ComPtr initialize(Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync)
{
if (!std::ranges::any_of(m_parent->getSurfaceFormats(), [&format](Format surfaceFormat) { return surfaceFormat == format; }))
throw InvalidArgumentException("format", "The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats());
@@ -57,8 +58,8 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(1, frameBufferSize.width()), std::max(1, frameBufferSize.height()) };
- LITEFX_TRACE(DIRECTX12_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px }}...", fmt::ptr(m_device.handle().Get()), frameBuffers, size.width(), size.height());
+ auto size = Size2d{ std::max(1, renderArea.width()), std::max(1, renderArea.height()) };
+ LITEFX_TRACE(DIRECTX12_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(m_device.handle().Get()), backBuffers, size.width(), size.height(), format, enableVsync);
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = static_cast(size.width());
@@ -66,7 +67,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(2, frameBuffers);
+ swapChainDesc.BufferCount = std::max(2, backBuffers);
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
@@ -93,11 +94,12 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetSurfaceFormats(), [&format](Format surfaceFormat) { return surfaceFormat == format; }))
throw InvalidArgumentException("format", "The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats());
@@ -110,9 +112,10 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(2, frameBuffers);
- auto size = Size2d{ std::max(1, frameBufferSize.width()), std::max(1, frameBufferSize.height()) };
+ UInt32 buffers = std::max(2, backBuffers);
+ auto size = Size2d{ std::max(1, renderArea.width()), std::max(1, renderArea.height()) };
raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(size.width()), static_cast(size.height()), DX12::getFormat(format), m_supportsVariableRefreshRates ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers.");
+ LITEFX_TRACE(DIRECTX12_LOG, "Resetting swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(m_device.handle().Get()), backBuffers, size.width(), size.height(), format, enableVsync);
// Acquire the swap chain images.
m_presentImages.resize(buffers);
@@ -127,6 +130,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(this, device)), ComResource(nullptr)
{
- this->handle() = m_impl->initialize(format, frameBufferSize, frameBuffers);
+ this->handle() = m_impl->initialize(format, renderArea, backBuffers, enableVsync);
}
DirectX12SwapChain::~DirectX12SwapChain() noexcept = default;
@@ -264,6 +268,11 @@ const Size2d& DirectX12SwapChain::renderArea() const noexcept
return m_impl->m_renderArea;
}
+bool DirectX12SwapChain::verticalSynchronization() const noexcept
+{
+ return m_impl->m_vsync;
+}
+
IDirectX12Image* DirectX12SwapChain::image(UInt32 backBuffer) const
{
if (backBuffer >= m_impl->m_presentImages.size()) [[unlikely]]
@@ -287,7 +296,11 @@ void DirectX12SwapChain::present(UInt64 fence) const
// Store the last fence here that marks the end of the rendering to this frame buffer. Presenting is queued after rendering anyway, but when swapping the back buffers buffers,
// we need to wait for all commands to finish before being able to re-use the command buffers associated with queued commands.
m_impl->m_presentFences[m_impl->m_currentImage] = fence;
- raiseIfFailed(this->handle()->Present(0, this->supportsVariableRefreshRate() ? DXGI_PRESENT_ALLOW_TEARING : 0), "Unable to present swap chain");
+
+ if (m_impl->m_vsync)
+ raiseIfFailed(this->handle()->Present(1, 0), "Unable to present swap chain");
+ else
+ raiseIfFailed(this->handle()->Present(0, this->supportsVariableRefreshRate() ? DXGI_PRESENT_ALLOW_TEARING : 0), "Unable to present swap chain");
}
Enumerable DirectX12SwapChain::getSurfaceFormats() const noexcept
@@ -315,10 +328,10 @@ void DirectX12SwapChain::addTimingEvent(SharedPtr timingEvent)
m_impl->resetQueryHeaps(events);
}
-void DirectX12SwapChain::reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers)
+void DirectX12SwapChain::reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync)
{
- m_impl->reset(surfaceFormat, renderArea, buffers);
- this->reseted(this, { surfaceFormat, renderArea, buffers });
+ m_impl->reset(surfaceFormat, renderArea, buffers, enableVsync);
+ this->reseted(this, { surfaceFormat, renderArea, buffers, enableVsync });
}
UInt32 DirectX12SwapChain::swapBackBuffer() const
diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp
index 6da240753..e9e205f2d 100644
--- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp
+++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp
@@ -1723,7 +1723,8 @@ namespace LiteFX::Rendering::Backends {
/// The initial surface format.
/// The initial size of the render area.
/// The initial number of buffers.
- explicit VulkanSwapChain(const VulkanDevice& device, Format surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, UInt32 buffers = 3);
+ /// `true` if vertical synchronization should be used, otherwise `false`.
+ explicit VulkanSwapChain(const VulkanDevice& device, Format surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, UInt32 buffers = 3, bool enableVsync = false);
VulkanSwapChain(const VulkanSwapChain&) = delete;
VulkanSwapChain(VulkanSwapChain&&) = delete;
virtual ~VulkanSwapChain() noexcept;
@@ -1759,6 +1760,9 @@ namespace LiteFX::Rendering::Backends {
///
const Size2d& renderArea() const noexcept override;
+ ///
+ bool verticalSynchronization() const noexcept override;
+
///
IVulkanImage* image(UInt32 backBuffer) const override;
@@ -1779,7 +1783,7 @@ namespace LiteFX::Rendering::Backends {
void addTimingEvent(SharedPtr timingEvent) override;
///
- void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) override;
+ void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync = false) override;
///
[[nodiscard]] UInt32 swapBackBuffer() const override;
@@ -1882,11 +1886,12 @@ namespace LiteFX::Rendering::Backends {
/// The adapter the device uses for drawing.
/// The surface, the device should draw to.
/// The initial surface format, device uses for drawing.
- /// The initial size of the frame buffers.
- /// The initial number of frame buffers.
+ /// The initial size of the render area.
+ /// The initial number of back buffers.
+ /// The initial setting for vertical synchronization.
/// The features that should be supported by this device.
/// The required extensions the device gets initialized with.
- explicit VulkanDevice(const VulkanBackend& backend, const VulkanGraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features = { }, Span extensions = { });
+ explicit VulkanDevice(const VulkanBackend& backend, const VulkanGraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync = false, GraphicsDeviceFeatures features = { }, Span extensions = { });
VulkanDevice(const VulkanDevice&) = delete;
VulkanDevice(VulkanDevice&&) = delete;
diff --git a/src/Backends/Vulkan/src/device.cpp b/src/Backends/Vulkan/src/device.cpp
index aa687f12c..b6e998c36 100644
--- a/src/Backends/Vulkan/src/device.cpp
+++ b/src/Backends/Vulkan/src/device.cpp
@@ -469,9 +469,9 @@ class VulkanDevice::VulkanDeviceImpl : public Implement {
m_factory = makeUnique(*m_parent);
}
- void createSwapChain(Format format, const Size2d& frameBufferSize, UInt32 frameBuffers)
+ void createSwapChain(Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync)
{
- m_swapChain = makeUnique(*m_parent, format, frameBufferSize, frameBuffers);
+ m_swapChain = makeUnique(*m_parent, format, renderArea, backBuffers, enableVsync);
}
public:
@@ -503,11 +503,11 @@ class VulkanDevice::VulkanDeviceImpl : public Implement {
// ------------------------------------------------------------------------------------------------
VulkanDevice::VulkanDevice(const VulkanBackend& backend, const VulkanGraphicsAdapter& adapter, UniquePtr&& surface, GraphicsDeviceFeatures features, Span extensions) :
- VulkanDevice(backend, adapter, std::move(surface), Format::B8G8R8A8_SRGB, { 800, 600 }, 3, features, extensions)
+ VulkanDevice(backend, adapter, std::move(surface), Format::B8G8R8A8_SRGB, { 800, 600 }, 3, false, features, extensions)
{
}
-VulkanDevice::VulkanDevice(const VulkanBackend& /*backend*/, const VulkanGraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features, Span extensions) :
+VulkanDevice::VulkanDevice(const VulkanBackend& /*backend*/, const VulkanGraphicsAdapter& adapter, UniquePtr&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync, GraphicsDeviceFeatures features, Span extensions) :
Resource(nullptr), m_impl(makePimpl(this, adapter, std::move(surface), features, extensions))
{
LITEFX_DEBUG(VULKAN_LOG, "Creating Vulkan device {{ Surface: {0}, Adapter: {1}, Extensions: {2} }}...", fmt::ptr(reinterpret_cast(m_impl->m_surface.get())), adapter.deviceId(), Join(this->enabledExtensions(), ", "));
@@ -527,7 +527,7 @@ VulkanDevice::VulkanDevice(const VulkanBackend& /*backend*/, const VulkanGraphic
this->handle() = m_impl->initialize(features);
m_impl->initializeDefaultQueues();
m_impl->createFactory();
- m_impl->createSwapChain(format, frameBufferSize, frameBuffers);
+ m_impl->createSwapChain(format, renderArea, backBuffers, enableVsync);
}
VulkanDevice::~VulkanDevice() noexcept
diff --git a/src/Backends/Vulkan/src/swapchain.cpp b/src/Backends/Vulkan/src/swapchain.cpp
index 42bf463e0..2aa97835c 100644
--- a/src/Backends/Vulkan/src/swapchain.cpp
+++ b/src/Backends/Vulkan/src/swapchain.cpp
@@ -29,6 +29,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
Array m_timingQueryPools;
VkQueryPool m_currentQueryPool;
bool m_supportsTiming = false;
+ bool m_vsync = false;
public:
VulkanSwapChainImpl(VulkanSwapChain* parent, const VulkanDevice& device) :
@@ -51,7 +52,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
}
public:
- void initialize(Format format, const Size2d& renderArea, UInt32 buffers)
+ void initialize(Format format, const Size2d& renderArea, UInt32 buffers, bool vsync)
{
if (format == Format::Other || format == Format::None) [[unlikely]]
throw InvalidArgumentException("format", "The provided surface format it not a valid value.");
@@ -93,9 +94,10 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
createInfo.imageExtent.width = std::max(1u, std::clamp(static_cast(renderArea.width()), deviceCaps.minImageExtent.width, deviceCaps.maxImageExtent.width));
// Set the present mode to VK_PRESENT_MODE_MAILBOX_KHR, since it offers best performance without tearing. For VSync use VK_PRESENT_MODE_FIFO_KHR, which is also the only one guaranteed to be available.
- createInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+ createInfo.presentMode = vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_MAILBOX_KHR;
+ m_vsync = vsync;
- LITEFX_TRACE(VULKAN_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4} }}...", fmt::ptr(&m_device), images, createInfo.imageExtent.width, createInfo.imageExtent.height, selectedFormat);
+ LITEFX_TRACE(VULKAN_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(&m_device), images, createInfo.imageExtent.width, createInfo.imageExtent.height, selectedFormat, vsync);
// Log if something needed to be changed.
[[unlikely]] if (selectedFormat != format)
@@ -176,11 +178,11 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
m_timestamps.resize(timingEvents.size());
}
- void reset(Format format, const Size2d& renderArea, UInt32 buffers)
+ void reset(Format format, Size2d renderArea, UInt32 buffers, bool vsync)
{
// Cleanup and re-initialize.
this->cleanup();
- this->initialize(format, renderArea, buffers);
+ this->initialize(format, renderArea, buffers, vsync);
}
void cleanup()
@@ -372,6 +374,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
Array> m_presentCommandLists;
bool m_supportsTearing = false;
+ bool m_vsync = false;
HANDLE m_fenceHandle;
Array> m_timingEvents;
@@ -406,7 +409,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
}
public:
- void initialize(Format format, const Size2d& renderArea, UInt32 buffers)
+ void initialize(Format format, const Size2d& renderArea, UInt32 buffers, bool vsync)
{
if (format == Format::Other || format == Format::None) [[unlikely]]
throw InvalidArgumentException("format", "The provided surface format it not a valid value.");
@@ -445,6 +448,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
// Create a D3D12 factory.
ComPtr factory;
UInt32 tearingSupport = 0;
+ m_vsync = vsync;
#ifndef NDEBUG
D3D::raiseIfFailed(::CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&factory)), "Unable to crate D3D12 factory for interop.");
#else
@@ -499,7 +503,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
D3D::raiseIfFailed(m_d3dDevice->CreateCommandQueue(&presentQueueDesc, IID_PPV_ARGS(&m_presentQueue)), "Unable to create present queue.");
// Create the swap chain instance.
- LITEFX_TRACE(VULKAN_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4} }}...", fmt::ptr(&m_device), images, extent.width(), extent.height(), selectedFormat);
+ LITEFX_TRACE(VULKAN_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(&m_device), images, extent.width(), extent.height(), selectedFormat, vsync);
DXGI_SWAP_CHAIN_DESC1 swapChainDesc {
.Width = static_cast(extent.width()),
@@ -558,7 +562,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
}
}
- void reset(Format format, const Size2d& renderArea, UInt32 buffers)
+ void reset(Format format, const Size2d& renderArea, UInt32 buffers, bool vsync)
{
// Release the image memory of the previously allocated images.
std::ranges::for_each(m_presentImages, [this](const auto& image) { ::vkDestroyImage(m_device.handle(), std::as_const(*image).handle(), nullptr); });
@@ -592,7 +596,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
LITEFX_INFO(VULKAN_LOG, "The render area has been adjusted to {0}x{1} Px (was {2}x{3} Px).", extent.height(), extent.width(), static_cast(renderArea.height()), static_cast(renderArea.width()));
// Reset the swap chain instance.
- LITEFX_TRACE(VULKAN_LOG, "Resetting swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4} }}...", fmt::ptr(&m_device), images, extent.width(), extent.height(), selectedFormat);
+ LITEFX_TRACE(VULKAN_LOG, "Resetting swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(&m_device), images, extent.width(), extent.height(), selectedFormat, vsync);
// Wait for both devices to be idle.
this->waitForInteropDevice();
@@ -621,6 +625,9 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
D3D::raiseIfFailed(m_d3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_presentCommandAllocators[i])), "Unable to create command allocator for present queue commands.");
D3D::raiseIfFailed(m_d3dDevice->CreateCommandList1(0, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&m_presentCommandLists[i])), "Unable to create command list for present queue commands.");
}
+
+ // Store vsync flag.
+ m_vsync = vsync;
}
void createImages(Format format, const Size2d& renderArea, UInt32 buffers)
@@ -776,11 +783,10 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
// Release the image memory of the previously allocated images.
std::ranges::for_each(m_presentImages, [this](const auto& image) { ::vkDestroyImage(m_device.handle(), std::as_const(*image).handle(), nullptr); });
+ // Destroy the swap chain and interop device and resources.
+ this->waitForInteropDevice();
m_imageResources.clear();
m_presentImages.clear();
-
- // Destroy the swap chain and interop device.
- this->waitForInteropDevice();
m_swapChain.Reset();
m_d3dDevice.Reset();
@@ -887,7 +893,11 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
m_presentQueue->ExecuteCommandLists(commandBuffers.size(), commandBuffers.data());
// Do the presentation.
- D3D::raiseIfFailed(m_swapChain->Present(0, m_supportsTearing ? DXGI_PRESENT_ALLOW_TEARING : 0), "Unable to queue present event on swap chain.");
+ if (m_vsync)
+ D3D::raiseIfFailed(m_swapChain->Present(1, 0), "Unable to queue present event on swap chain.");
+ else
+ D3D::raiseIfFailed(m_swapChain->Present(0, m_supportsTearing ? DXGI_PRESENT_ALLOW_TEARING : 0), "Unable to queue present event on swap chain.");
+
D3D::raiseIfFailed(m_presentQueue->Signal(m_presentationFence.Get(), m_presentFences[m_currentImage]), "Unable to signal presentation fence.");
}
@@ -961,10 +971,10 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
// Shared interface.
// ------------------------------------------------------------------------------------------------
-VulkanSwapChain::VulkanSwapChain(const VulkanDevice& device, Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) :
+VulkanSwapChain::VulkanSwapChain(const VulkanDevice& device, Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync) :
m_impl(makePimpl(this, device))
{
- m_impl->initialize(surfaceFormat, renderArea, buffers);
+ m_impl->initialize(surfaceFormat, renderArea, buffers, enableVsync);
}
VulkanSwapChain::~VulkanSwapChain() noexcept = default;
@@ -1030,6 +1040,11 @@ const Size2d& VulkanSwapChain::renderArea() const noexcept
return m_impl->m_renderArea;
}
+bool VulkanSwapChain::verticalSynchronization() const noexcept
+{
+ return m_impl->m_vsync;
+}
+
IVulkanImage* VulkanSwapChain::image(UInt32 backBuffer) const
{
if (backBuffer >= m_impl->m_presentImages.size()) [[unlikely]]
@@ -1073,10 +1088,10 @@ void VulkanSwapChain::addTimingEvent(SharedPtr timingEvent)
m_impl->resetQueryPools(events);
}
-void VulkanSwapChain::reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers)
+void VulkanSwapChain::reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync)
{
- m_impl->reset(surfaceFormat, renderArea, buffers);
- this->reseted(this, { surfaceFormat, renderArea, buffers });
+ m_impl->reset(surfaceFormat, renderArea, buffers, enableVsync);
+ this->reseted(this, { surfaceFormat, renderArea, buffers, enableVsync });
}
UInt32 VulkanSwapChain::swapBackBuffer() const
diff --git a/src/Rendering/include/litefx/rendering_api.hpp b/src/Rendering/include/litefx/rendering_api.hpp
index da05e8d40..f57639890 100644
--- a/src/Rendering/include/litefx/rendering_api.hpp
+++ b/src/Rendering/include/litefx/rendering_api.hpp
@@ -7357,10 +7357,11 @@ namespace LiteFX::Rendering {
Format m_surfaceFormat;
const Size2d& m_renderArea;
UInt32 m_buffers;
+ bool m_vsync;
public:
- ResetEventArgs(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) :
- EventArgs(), m_surfaceFormat(surfaceFormat), m_renderArea(renderArea), m_buffers(buffers) { }
+ ResetEventArgs(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync) :
+ EventArgs(), m_surfaceFormat(surfaceFormat), m_renderArea(renderArea), m_buffers(buffers), m_vsync(enableVsync) { }
ResetEventArgs(const ResetEventArgs&) = default;
ResetEventArgs(ResetEventArgs&&) = default;
virtual ~ResetEventArgs() noexcept = default;
@@ -7393,6 +7394,14 @@ namespace LiteFX::Rendering {
inline UInt32 buffers() const noexcept {
return m_buffers;
}
+
+ ///
+ /// Returns `true` if vertical synchronization is enabled or `false` otherwise.
+ ///
+ /// `true` if vertical synchronization is enabled or `false` otherwise.
+ inline bool enableVsync() const noexcept {
+ return m_vsync;
+ }
};
public:
@@ -7466,6 +7475,12 @@ namespace LiteFX::Rendering {
/// The size of the render area.
virtual const Size2d& renderArea() const noexcept = 0;
+ ///
+ /// Returns `true`, if vertical synchronization should be used, otherwise `false`.
+ ///
+ /// `true`, if vertical synchronization should be used, otherwise `false`.
+ virtual bool verticalSynchronization() const noexcept = 0;
+
///
/// Returns the swap chain present image for .
///
@@ -7533,8 +7548,9 @@ namespace LiteFX::Rendering {
/// The swap chain image format.
/// The dimensions of the frame buffers.
/// The number of buffers in the swap chain.
+ /// `true`, if vertical synchronization should be used, otherwise `false`.
///
- virtual void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) = 0;
+ virtual void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync = false) = 0;
///
/// Swaps the front buffer with the next back buffer in order.
From 0eac6f8081637582641ebcdf6b7103f68fdb8738 Mon Sep 17 00:00:00 2001
From: Carsten Rudolph <18394207+crud89@users.noreply.github.com>
Date: Fri, 1 Mar 2024 16:28:07 +0100
Subject: [PATCH 2/5] Update samples to allow toggling vsync on and off.
---
src/Samples/BasicRendering/src/sample.cpp | 15 +++++++++++++--
src/Samples/Bindless/src/sample.cpp | 15 +++++++++++++--
src/Samples/Compute/src/sample.cpp | 15 +++++++++++++--
src/Samples/MeshShader/src/sample.cpp | 15 +++++++++++++--
src/Samples/Multisampling/src/sample.cpp | 15 +++++++++++++--
src/Samples/Multithreading/src/sample.cpp | 15 +++++++++++++--
src/Samples/PushConstants/src/sample.cpp | 15 +++++++++++++--
src/Samples/RayQueries/src/sample.cpp | 15 +++++++++++++--
src/Samples/RayTracing/src/sample.cpp | 15 +++++++++++++--
src/Samples/RenderPasses/src/sample.cpp | 15 +++++++++++++--
src/Samples/Textures/src/sample.cpp | 15 +++++++++++++--
src/Samples/UniformArrays/src/sample.cpp | 15 +++++++++++++--
12 files changed, 156 insertions(+), 24 deletions(-)
diff --git a/src/Samples/BasicRendering/src/sample.cpp b/src/Samples/BasicRendering/src/sample.cpp
index 79146467f..6dcddd3d5 100644
--- a/src/Samples/BasicRendering/src/sample.cpp
+++ b/src/Samples/BasicRendering/src/sample.cpp
@@ -209,7 +209,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -246,7 +246,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -328,6 +329,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/Bindless/src/sample.cpp b/src/Samples/Bindless/src/sample.cpp
index 89fda4a4d..cb0b6dbfe 100644
--- a/src/Samples/Bindless/src/sample.cpp
+++ b/src/Samples/Bindless/src/sample.cpp
@@ -257,7 +257,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -294,7 +294,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ bool vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -376,6 +377,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/Compute/src/sample.cpp b/src/Samples/Compute/src/sample.cpp
index c9ad3c69a..16c640975 100644
--- a/src/Samples/Compute/src/sample.cpp
+++ b/src/Samples/Compute/src/sample.cpp
@@ -232,7 +232,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -269,7 +269,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ bool vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
for (int i = 0; i < 3; ++i)
@@ -356,6 +357,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/MeshShader/src/sample.cpp b/src/Samples/MeshShader/src/sample.cpp
index ca9081c91..663d57c0e 100644
--- a/src/Samples/MeshShader/src/sample.cpp
+++ b/src/Samples/MeshShader/src/sample.cpp
@@ -183,7 +183,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, GraphicsDeviceFeatures { .MeshShaders = true });
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false, GraphicsDeviceFeatures { .MeshShaders = true });
// Initialize resources.
::initRenderGraph(backend);
@@ -220,7 +220,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ bool vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -302,6 +303,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/Multisampling/src/sample.cpp b/src/Samples/Multisampling/src/sample.cpp
index 8fbf88f4c..b4615f5f2 100644
--- a/src/Samples/Multisampling/src/sample.cpp
+++ b/src/Samples/Multisampling/src/sample.cpp
@@ -211,7 +211,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -248,7 +248,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ bool vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -330,6 +331,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/Multithreading/src/sample.cpp b/src/Samples/Multithreading/src/sample.cpp
index 932fa4e8e..08a2d5ecd 100644
--- a/src/Samples/Multithreading/src/sample.cpp
+++ b/src/Samples/Multithreading/src/sample.cpp
@@ -224,7 +224,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -261,7 +261,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ bool vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -343,6 +344,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/PushConstants/src/sample.cpp b/src/Samples/PushConstants/src/sample.cpp
index 496b0cbe5..b27849462 100644
--- a/src/Samples/PushConstants/src/sample.cpp
+++ b/src/Samples/PushConstants/src/sample.cpp
@@ -225,7 +225,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -259,7 +259,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -341,6 +342,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/RayQueries/src/sample.cpp b/src/Samples/RayQueries/src/sample.cpp
index 0e0b623a7..5f1534af5 100644
--- a/src/Samples/RayQueries/src/sample.cpp
+++ b/src/Samples/RayQueries/src/sample.cpp
@@ -388,7 +388,7 @@ void SampleApp::onInit()
m_scissor = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height)));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, Size2d(static_cast(width), static_cast(height)), 3, GraphicsDeviceFeatures { .RayQueries = true });
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, Size2d(static_cast(width), static_cast(height)), 3, false, GraphicsDeviceFeatures { .RayQueries = true });
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -425,7 +425,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -504,6 +505,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/RayTracing/src/sample.cpp b/src/Samples/RayTracing/src/sample.cpp
index db31ace51..c2d8bb071 100644
--- a/src/Samples/RayTracing/src/sample.cpp
+++ b/src/Samples/RayTracing/src/sample.cpp
@@ -393,7 +393,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, Size2d(static_cast(width), static_cast(height)), 3, GraphicsDeviceFeatures { .RayTracing = true });
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, Size2d(static_cast(width), static_cast(height)), 3, false, GraphicsDeviceFeatures { .RayTracing = true });
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -430,7 +430,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Recreate output images and re-bind them to the output descriptors.
auto backBuffers = m_device->factory().createTexture("Back Buffers", m_device->swapChain().surfaceFormat(), m_device->swapChain().renderArea(), ImageDimensions::DIM_2, 1u, m_device->swapChain().buffers(), MultiSamplingLevel::x1, ResourceUsage::AllowWrite | ResourceUsage::TransferSource);
@@ -512,6 +513,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/RenderPasses/src/sample.cpp b/src/Samples/RenderPasses/src/sample.cpp
index 17acda788..c8e9689b6 100644
--- a/src/Samples/RenderPasses/src/sample.cpp
+++ b/src/Samples/RenderPasses/src/sample.cpp
@@ -285,7 +285,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -319,7 +319,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -401,6 +402,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/Textures/src/sample.cpp b/src/Samples/Textures/src/sample.cpp
index 95768f654..6538de32c 100644
--- a/src/Samples/Textures/src/sample.cpp
+++ b/src/Samples/Textures/src/sample.cpp
@@ -277,7 +277,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- auto device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ auto device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
m_device = device;
// Initialize resources.
@@ -315,7 +315,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -397,6 +398,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
diff --git a/src/Samples/UniformArrays/src/sample.cpp b/src/Samples/UniformArrays/src/sample.cpp
index 27750b66f..1ae250d40 100644
--- a/src/Samples/UniformArrays/src/sample.cpp
+++ b/src/Samples/UniformArrays/src/sample.cpp
@@ -267,7 +267,7 @@ void SampleApp::onInit()
auto surface = backend->createSurface(::glfwGetWin32Window(window));
// Create the device.
- m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3);
+ m_device = backend->createDevice("Default", *adapter, std::move(surface), Format::B8G8R8A8_UNORM, m_viewport->getRectangle().extent(), 3, false);
// Initialize resources.
::initRenderGraph(backend, m_inputAssembler);
@@ -304,7 +304,8 @@ void SampleApp::onResize(const void* sender, ResizeEventArgs e)
// Resize the frame buffer and recreate the swap chain.
auto surfaceFormat = m_device->swapChain().surfaceFormat();
auto renderArea = Size2d(e.width(), e.height());
- m_device->swapChain().reset(surfaceFormat, renderArea, 3);
+ auto vsync = m_device->swapChain().verticalSynchronization();
+ m_device->swapChain().reset(surfaceFormat, renderArea, 3, vsync);
// Resize the frame buffers. Note that we could also use an event handler on the swap chain `reseted` event to do this automatically instead.
m_device->state().frameBuffer("Frame Buffer 0").resize(renderArea);
@@ -386,6 +387,16 @@ void SampleApp::keyDown(int key, int scancode, int action, int mods)
}
}
+ if (key == GLFW_KEY_F7 && action == GLFW_PRESS)
+ {
+ // Wait for the device.
+ m_device->wait();
+
+ // Toggle VSync on the swap chain.
+ auto& swapChain = m_device->swapChain();
+ swapChain.reset(swapChain.surfaceFormat(), swapChain.renderArea(), swapChain.buffers(), !swapChain.verticalSynchronization());
+ }
+
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
// Close the window with the next loop.
From f8ef5b30db6d06ff1be80ee907c3b62afa46b30e Mon Sep 17 00:00:00 2001
From: Carsten Rudolph <18394207+crud89@users.noreply.github.com>
Date: Mon, 4 Mar 2024 09:23:45 +0100
Subject: [PATCH 3/5] Use fences to sync render passes.
---
src/Samples/RenderPasses/src/sample.cpp | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/Samples/RenderPasses/src/sample.cpp b/src/Samples/RenderPasses/src/sample.cpp
index c8e9689b6..5d0303b3c 100644
--- a/src/Samples/RenderPasses/src/sample.cpp
+++ b/src/Samples/RenderPasses/src/sample.cpp
@@ -444,6 +444,7 @@ void SampleApp::drawFrame()
// Swap the back buffers for the next frame.
auto backBuffer = m_device->swapChain().swapBackBuffer();
auto& frameBuffer = m_device->state().frameBuffer(fmt::format("Frame Buffer {0}", backBuffer));
+ UInt64 fence = 0;
{
// Query state.
@@ -482,7 +483,7 @@ void SampleApp::drawFrame()
// Draw the object and end the render pass.
commandBuffer->drawIndexed(indexBuffer.elements());
- renderPass.end();
+ fence = renderPass.end();
}
{
@@ -493,6 +494,7 @@ void SampleApp::drawFrame()
auto& viewPlaneIndexBuffer = m_device->state().indexBuffer("View Plane Indices");
// Start the lighting pass.
+ renderPass.commandQueue().waitFor(fence);
renderPass.begin(frameBuffer);
auto commandBuffer = renderPass.commandBuffer(0);
commandBuffer->use(pipeline);
@@ -505,7 +507,7 @@ void SampleApp::drawFrame()
commandBuffer->drawIndexed(viewPlaneIndexBuffer.elements());
// End the lighting pass.
- renderPass.end();
+ fence = renderPass.end();
}
{
@@ -519,6 +521,7 @@ void SampleApp::drawFrame()
auto& indexBuffer = m_device->state().indexBuffer("Index Buffer");
// Begin rendering on the render pass and use the only pipeline we've created for it.
+ renderPass.commandQueue().waitFor(fence);
renderPass.begin(frameBuffer);
auto commandBuffer = renderPass.commandBuffer(0);
commandBuffer->use(pipeline);
From 255a014d80379ae80c6404f08a82c100ad46e710 Mon Sep 17 00:00:00 2001
From: Carsten Rudolph <18394207+crud89@users.noreply.github.com>
Date: Mon, 4 Mar 2024 09:51:20 +0100
Subject: [PATCH 4/5] Use immediate mode by default.
---
src/Backends/Vulkan/src/swapchain.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Backends/Vulkan/src/swapchain.cpp b/src/Backends/Vulkan/src/swapchain.cpp
index 2aa97835c..08e42bd2a 100644
--- a/src/Backends/Vulkan/src/swapchain.cpp
+++ b/src/Backends/Vulkan/src/swapchain.cpp
@@ -94,7 +94,7 @@ class VulkanSwapChain::VulkanSwapChainImpl : public Implement {
createInfo.imageExtent.width = std::max(1u, std::clamp(static_cast(renderArea.width()), deviceCaps.minImageExtent.width, deviceCaps.maxImageExtent.width));
// Set the present mode to VK_PRESENT_MODE_MAILBOX_KHR, since it offers best performance without tearing. For VSync use VK_PRESENT_MODE_FIFO_KHR, which is also the only one guaranteed to be available.
- createInfo.presentMode = vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_MAILBOX_KHR;
+ createInfo.presentMode = vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR;/* VK_PRESENT_MODE_MAILBOX_KHR;*/
m_vsync = vsync;
LITEFX_TRACE(VULKAN_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px, Format: {4}, VSync: {5} }}...", fmt::ptr(&m_device), images, createInfo.imageExtent.width, createInfo.imageExtent.height, selectedFormat, vsync);
From b02ad35690f991df4692b3faef33834cc326ba83 Mon Sep 17 00:00:00 2001
From: Carsten Rudolph <18394207+crud89@users.noreply.github.com>
Date: Mon, 4 Mar 2024 09:56:09 +0100
Subject: [PATCH 5/5] Document vertical synchronization feature.
---
docs/release-logs/0.4.1.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/release-logs/0.4.1.md b/docs/release-logs/0.4.1.md
index ec9b3e378..4000d8e6b 100644
--- a/docs/release-logs/0.4.1.md
+++ b/docs/release-logs/0.4.1.md
@@ -39,6 +39,7 @@
- Add optional support for ray-tracing and ray queries (enable `GraphicsDeviceFeatures::RayTracing` and/or `GraphicsDeviceFeatures::RayQueries` to turn it on). ([See PR #122](https://github.com/crud89/LiteFX/pull/122))
- Render passes have been improved and simplified, now supporting automatic input attachment binding and event-based resize handlers. ([See PR #124](https://github.com/crud89/LiteFX/pull/124))
- Frame buffers have been decoupled from render passes, allowing to share images between passes and binding resources of different sampling rates and resolutions to the same pass. ([See PR #125](https://github.com/crud89/LiteFX/pull/125))
+- Support for vertical synchronization has been added. ([See PR #127](https://github.com/crud89/LiteFX/pull/127))
**🌋 Vulkan:**