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:**