Skip to content

Commit

Permalink
Merge pull request #127 from crud89/vsync
Browse files Browse the repository at this point in the history
Support vertical synchronization.
  • Loading branch information
crud89 authored Mar 4, 2024
2 parents 131422d + b02ad35 commit 7d6e279
Show file tree
Hide file tree
Showing 20 changed files with 270 additions and 80 deletions.
1 change: 1 addition & 0 deletions docs/release-logs/0.4.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
15 changes: 10 additions & 5 deletions src/Backends/DirectX12/include/litefx/backends/dx12.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1713,8 +1713,9 @@ namespace LiteFX::Rendering::Backends {
/// <param name="device">The device that owns the swap chain.</param>
/// <param name="format">The initial surface format.</param>
/// <param name="renderArea">The initial size of the render area.</param>
/// <param name="enableVsync">`true` if vertical synchronization should be used, otherwise `false`.</param>
/// <param name="buffers">The initial number of buffers.</param>
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;
Expand Down Expand Up @@ -1756,6 +1757,9 @@ namespace LiteFX::Rendering::Backends {
/// <inheritdoc />
const Size2d& renderArea() const noexcept override;

/// <inheritdoc />
bool verticalSynchronization() const noexcept override;

/// <inheritdoc />
IDirectX12Image* image(UInt32 backBuffer) const override;

Expand All @@ -1776,7 +1780,7 @@ namespace LiteFX::Rendering::Backends {
void addTimingEvent(SharedPtr<TimingEvent> timingEvent) override;

/// <inheritdoc />
void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) override;
void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync = false) override;

/// <inheritdoc />
[[nodiscard]] UInt32 swapBackBuffer() const override;
Expand Down Expand Up @@ -1881,12 +1885,13 @@ namespace LiteFX::Rendering::Backends {
/// <param name="adapter">The adapter the device uses for drawing.</param>
/// <param name="surface">The surface, the device should draw to.</param>
/// <param name="format">The initial surface format, device uses for drawing.</param>
/// <param name="frameBufferSize">The initial size of the frame buffers.</param>
/// <param name="frameBuffers">The initial number of frame buffers.</param>
/// <param name="renderArea">The initial size of the render area.</param>
/// <param name="backBuffers">The initial number of back buffers.</param>
/// <param name="enableVsync">The initial setting for vertical synchronization.</param>
/// <param name="features">The features that should be supported by this device.</param>
/// <param name="globalBufferHeapSize">The size of the global heap for constant buffers, shader resources and images.</param>
/// <param name="globalSamplerHeapSize">The size of the global heap for samplers.</param>
explicit DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr<DirectX12Surface>&& 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<DirectX12Surface>&& 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;
Expand Down
10 changes: 5 additions & 5 deletions src/Backends/DirectX12/src/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement<DirectX12Device> {
m_factory = makeUnique<DirectX12GraphicsFactory>(*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<DirectX12SwapChain>(*m_parent, format, frameBufferSize, frameBuffers);
m_swapChain = makeUnique<DirectX12SwapChain>(*m_parent, format, renderArea, backBuffers, enableVsync);
}

void createQueues()
Expand Down Expand Up @@ -260,11 +260,11 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement<DirectX12Device> {
// ------------------------------------------------------------------------------------------------

DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr<DirectX12Surface>&& 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<DirectX12Surface>&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features, UInt32 globalBufferHeapSize, UInt32 globalSamplerHeapSize) :
DirectX12Device::DirectX12Device(const DirectX12Backend& backend, const DirectX12GraphicsAdapter& adapter, UniquePtr<DirectX12Surface>&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync, GraphicsDeviceFeatures features, UInt32 globalBufferHeapSize, UInt32 globalSamplerHeapSize) :
ComResource<ID3D12Device10>(nullptr), m_impl(makePimpl<DirectX12DeviceImpl>(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());
Expand All @@ -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();
}

Expand Down
39 changes: 26 additions & 13 deletions src/Backends/DirectX12/src/swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
Array<UniquePtr<IDirectX12Image>> m_presentImages{ };
Array<UInt64> m_presentFences{ };
bool m_supportsVariableRefreshRates{ false };
bool m_vsync{ false };
const DirectX12Device& m_device;

Array<SharedPtr<TimingEvent>> m_timingEvents;
Expand Down Expand Up @@ -46,7 +47,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa

public:
[[nodiscard]]
ComPtr<IDXGISwapChain4> initialize(Format format, const Size2d& frameBufferSize, UInt32 frameBuffers)
ComPtr<IDXGISwapChain4> 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());
Expand All @@ -57,16 +58,16 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
const auto& backend = m_device.backend();

// Create the swap chain.
auto size = Size2d{ std::max<UInt32>(1, frameBufferSize.width()), std::max<UInt32>(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<UInt32>(1, renderArea.width()), std::max<UInt32>(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<UInt32>(size.width());
swapChainDesc.Height = static_cast<UInt32>(size.height());
swapChainDesc.Format = DX12::getFormat(format);
swapChainDesc.Stereo = FALSE;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // NOTE: D3D12 does no longer allow UAV access to back buffers, so binding swap chain images to UAV in compute/ray-tracing shaders does not work.
swapChainDesc.BufferCount = std::max<UInt32>(2, frameBuffers);
swapChainDesc.BufferCount = std::max<UInt32>(2, backBuffers);
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
Expand All @@ -93,11 +94,12 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
m_format = format;
m_renderArea = size;
m_buffers = swapChainDesc.BufferCount;
m_vsync = enableVsync;

return swapChain;
}

void reset(Format format, const Size2d& frameBufferSize, UInt32 frameBuffers)
void reset(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());
Expand All @@ -110,9 +112,10 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
const auto& backend = m_device.backend();

// Resize the buffers.
UInt32 buffers = std::max<UInt32>(2, frameBuffers);
auto size = Size2d{ std::max<UInt32>(1, frameBufferSize.width()), std::max<UInt32>(1, frameBufferSize.height()) };
UInt32 buffers = std::max<UInt32>(2, backBuffers);
auto size = Size2d{ std::max<UInt32>(1, renderArea.width()), std::max<UInt32>(1, renderArea.height()) };
raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast<UInt32>(size.width()), static_cast<UInt32>(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);
Expand All @@ -127,6 +130,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
m_renderArea = size;
m_buffers = buffers;
m_currentImage = 0;
m_vsync = enableVsync;

// Initialize the query pools.
if (m_timingQueryHeaps.size() != buffers)
Expand Down Expand Up @@ -196,10 +200,10 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement<DirectX12Swa
// Shared interface.
// ------------------------------------------------------------------------------------------------

DirectX12SwapChain::DirectX12SwapChain(const DirectX12Device& device, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers) :
DirectX12SwapChain::DirectX12SwapChain(const DirectX12Device& device, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync) :
m_impl(makePimpl<DirectX12SwapChainImpl>(this, device)), ComResource<IDXGISwapChain4>(nullptr)
{
this->handle() = m_impl->initialize(format, frameBufferSize, frameBuffers);
this->handle() = m_impl->initialize(format, renderArea, backBuffers, enableVsync);
}

DirectX12SwapChain::~DirectX12SwapChain() noexcept = default;
Expand Down Expand Up @@ -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]]
Expand All @@ -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<Format> DirectX12SwapChain::getSurfaceFormats() const noexcept
Expand Down Expand Up @@ -315,10 +328,10 @@ void DirectX12SwapChain::addTimingEvent(SharedPtr<TimingEvent> 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
Expand Down
15 changes: 10 additions & 5 deletions src/Backends/Vulkan/include/litefx/backends/vulkan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1723,7 +1723,8 @@ namespace LiteFX::Rendering::Backends {
/// <param name="format">The initial surface format.</param>
/// <param name="renderArea">The initial size of the render area.</param>
/// <param name="buffers">The initial number of buffers.</param>
explicit VulkanSwapChain(const VulkanDevice& device, Format surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, UInt32 buffers = 3);
/// <param name="enableVsync">`true` if vertical synchronization should be used, otherwise `false`.</param>
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;
Expand Down Expand Up @@ -1759,6 +1760,9 @@ namespace LiteFX::Rendering::Backends {
/// <inheritdoc />
const Size2d& renderArea() const noexcept override;

/// <inheritdoc />
bool verticalSynchronization() const noexcept override;

/// <inheritdoc />
IVulkanImage* image(UInt32 backBuffer) const override;

Expand All @@ -1779,7 +1783,7 @@ namespace LiteFX::Rendering::Backends {
void addTimingEvent(SharedPtr<TimingEvent> timingEvent) override;

/// <inheritdoc />
void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers) override;
void reset(Format surfaceFormat, const Size2d& renderArea, UInt32 buffers, bool enableVsync = false) override;

/// <inheritdoc />
[[nodiscard]] UInt32 swapBackBuffer() const override;
Expand Down Expand Up @@ -1882,11 +1886,12 @@ namespace LiteFX::Rendering::Backends {
/// <param name="adapter">The adapter the device uses for drawing.</param>
/// <param name="surface">The surface, the device should draw to.</param>
/// <param name="format">The initial surface format, device uses for drawing.</param>
/// <param name="frameBufferSize">The initial size of the frame buffers.</param>
/// <param name="frameBuffers">The initial number of frame buffers.</param>
/// <param name="renderArea">The initial size of the render area.</param>
/// <param name="backBuffers">The initial number of back buffers.</param>
/// <param name="enableVsync">The initial setting for vertical synchronization.</param>
/// <param name="features">The features that should be supported by this device.</param>
/// <param name="extensions">The required extensions the device gets initialized with.</param>
explicit VulkanDevice(const VulkanBackend& backend, const VulkanGraphicsAdapter& adapter, UniquePtr<VulkanSurface>&& surface, Format format, const Size2d& frameBufferSize, UInt32 frameBuffers, GraphicsDeviceFeatures features = { }, Span<String> extensions = { });
explicit VulkanDevice(const VulkanBackend& backend, const VulkanGraphicsAdapter& adapter, UniquePtr<VulkanSurface>&& surface, Format format, const Size2d& renderArea, UInt32 backBuffers, bool enableVsync = false, GraphicsDeviceFeatures features = { }, Span<String> extensions = { });

VulkanDevice(const VulkanDevice&) = delete;
VulkanDevice(VulkanDevice&&) = delete;
Expand Down
Loading

1 comment on commit 7d6e279

@crud89
Copy link
Owner Author

@crud89 crud89 commented on 7d6e279 Mar 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's been a small whoopsie so to proper merge commit is the following: f2444d6.

Please sign in to comment.