From 2f657a468874904c8795da0adcccefa71180b718 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Wed, 23 Jun 2021 16:29:45 +0200 Subject: [PATCH 01/31] Remove obsolete `dx12_builder.hpp`. --- .../DirectX12/include/litefx/backends/dx12_builders.hpp | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/Backends/DirectX12/include/litefx/backends/dx12_builders.hpp diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12_builders.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12_builders.hpp deleted file mode 100644 index f70a413e0..000000000 --- a/src/Backends/DirectX12/include/litefx/backends/dx12_builders.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include "dx12_api.hpp" - -namespace LiteFX::Rendering::Backends { - using namespace LiteFX::Rendering; -} \ No newline at end of file From 515a0f6d7a5d1a9dd33e3d2a758aa67782a999b4 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Wed, 23 Jun 2021 16:36:41 +0200 Subject: [PATCH 02/31] Expose depth/stencil state. Fixes issue #17. --- docs/release-logs/0.2.1.md | 4 + .../include/litefx/backends/dx12.hpp | 17 +- .../include/litefx/backends/dx12_api.hpp | 10 + src/Backends/DirectX12/src/convert.cpp | 30 ++ src/Backends/DirectX12/src/pipeline.cpp | 42 +-- src/Backends/DirectX12/src/rasterizer.cpp | 44 ++- .../Vulkan/include/litefx/backends/vulkan.hpp | 16 +- .../include/litefx/backends/vulkan_api.hpp | 10 + src/Backends/Vulkan/src/convert.cpp | 30 ++ src/Backends/Vulkan/src/pipeline.cpp | 32 +- src/Backends/Vulkan/src/rasterizer.cpp | 34 +-- src/Rendering/CMakeLists.txt | 1 + .../include/litefx/rendering_api.hpp | 280 ++++++++++++++---- src/Rendering/src/depth_stencil_state.cpp | 82 +++++ src/Rendering/src/rasterizer.cpp | 53 +--- 15 files changed, 480 insertions(+), 205 deletions(-) create mode 100644 docs/release-logs/0.2.1.md create mode 100644 src/Rendering/src/depth_stencil_state.cpp diff --git a/docs/release-logs/0.2.1.md b/docs/release-logs/0.2.1.md new file mode 100644 index 000000000..732587a89 --- /dev/null +++ b/docs/release-logs/0.2.1.md @@ -0,0 +1,4 @@ +# LiteFX 0.1.1 - Alpha 02 + +- Fixes issue that prevented DirectX 12 samples from being run in Release mode. +- Introduces rasterizer depth/stencil state. \ No newline at end of file diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index b3bfcd54b..d02a746b2 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -3,7 +3,6 @@ #include #include "dx12_api.hpp" -#include "dx12_builders.hpp" #include "dx12_formatters.hpp" namespace LiteFX::Rendering::Backends { @@ -769,11 +768,8 @@ namespace LiteFX::Rendering::Backends { /// The cull mode used by the pipeline. /// The cull order used by the pipeline. /// The line width used by the pipeline. - /// true, if the depth bias should be enabled. - /// The clamp value of the depth bias state. - /// The constant factor of the depth bias state. - /// The slope factor of the depth bias state. - explicit DirectX12Rasterizer(const DirectX12RenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const bool& useDepthBias = false, const Float& depthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP, const Float& depthBiasConstantFactor = D3D12_DEFAULT_DEPTH_BIAS, const Float& depthBiasSlopeFactor = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS) noexcept; + /// The rasterizer depth/stencil state. + explicit DirectX12Rasterizer(const DirectX12RenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const DepthStencilState& depthStencilState = {}) noexcept; DirectX12Rasterizer(DirectX12Rasterizer&&) noexcept = delete; DirectX12Rasterizer(const DirectX12Rasterizer&) noexcept = delete; virtual ~DirectX12Rasterizer() noexcept; @@ -819,16 +815,13 @@ namespace LiteFX::Rendering::Backends { virtual DirectX12RasterizerBuilder& withLineWidth(const Float& lineWidth = 1.f) noexcept override; /// - virtual DirectX12RasterizerBuilder& enableDepthBias(const bool& enable = false) noexcept override; + virtual DirectX12RasterizerBuilder& withDepthBias(const DepthStencilState::DepthBias& depthBias) noexcept override; /// - virtual DirectX12RasterizerBuilder& withDepthBiasClamp(const Float& clamp = 0.f) noexcept override; + virtual DirectX12RasterizerBuilder& withDepthState(const DepthStencilState::DepthState& depthState) noexcept override; /// - virtual DirectX12RasterizerBuilder& withDepthBiasConstantFactor(const Float& factor = 0.f) noexcept override; - - /// - virtual DirectX12RasterizerBuilder& withDepthBiasSlopeFactor(const Float& factor = 0.f) noexcept override; + virtual DirectX12RasterizerBuilder& withStencilState(const DepthStencilState::StencilState& stencilState) noexcept override; }; /// diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp index 625772304..27fbdae38 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp @@ -147,6 +147,16 @@ namespace LiteFX::Rendering::Backends { /// String LITEFX_DIRECTX12_API getVendorName(const UInt32& vendorId); + /// + /// + /// + D3D12_COMPARISON_FUNC LITEFX_DIRECTX12_API getCompareOp(const CompareOperation& compareOp); + + /// + /// + /// + D3D12_STENCIL_OP LITEFX_DIRECTX12_API getStencilOp(const StencilOperation& stencilOp); + /// /// Implements a DirectX12 . /// diff --git a/src/Backends/DirectX12/src/convert.cpp b/src/Backends/DirectX12/src/convert.cpp index 990c183c7..5b62ea8c2 100644 --- a/src/Backends/DirectX12/src/convert.cpp +++ b/src/Backends/DirectX12/src/convert.cpp @@ -503,4 +503,34 @@ String LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getVendorName(const UIn case 0x5143: return "Qualcomm"; default: return "Unknown"; } +} + +D3D12_COMPARISON_FUNC LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getCompareOp(const CompareOperation& compareOp) +{ + switch (compareOp) { + case CompareOperation::Never: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_NEVER; + case CompareOperation::Less: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_LESS; + case CompareOperation::Greater: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_GREATER; + case CompareOperation::Equal: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_EQUAL; + case CompareOperation::LessEqual: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_LESS_EQUAL; + case CompareOperation::GreaterEqual: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_GREATER_EQUAL; + case CompareOperation::NotEqual: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_NOT_EQUAL; + case CompareOperation::Always: return D3D12_COMPARISON_FUNC::D3D12_COMPARISON_FUNC_ALWAYS; + default: throw InvalidArgumentException("Unsupported compare operation."); + } +} + +D3D12_STENCIL_OP LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getStencilOp(const StencilOperation& stencilOp) +{ + switch (stencilOp) { + case StencilOperation::Keep: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_KEEP; + case StencilOperation::Zero: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_ZERO; + case StencilOperation::Invert: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_INVERT; + case StencilOperation::Replace: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_REPLACE; + case StencilOperation::IncrementClamp: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_INCR_SAT; + case StencilOperation::IncrementWrap: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_INCR; + case StencilOperation::DecrementClamp: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_DECR_SAT; + case StencilOperation::DecrementWrap: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_DECR; + default: throw InvalidArgumentException("Unsupported stencil operation."); + } } \ No newline at end of file diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index 169bc211f..fa2155cff 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -39,7 +39,7 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement(rasterizer.depthBiasConstantFactor()); - rasterizerState.SlopeScaledDepthBias = rasterizer.depthBiasSlopeFactor(); + LITEFX_TRACE(DIRECTX12_LOG, "\tRasterizer depth bias: {{ Clamp: {0}, ConstantFactor: {1}, SlopeFactor: {2} }}", rasterizer.depthStencilState().depthBias().Clamp, rasterizer.depthStencilState().depthBias().ConstantFactor, rasterizer.depthStencilState().depthBias().SlopeFactor); + rasterizerState.DepthBiasClamp = rasterizer.depthStencilState().depthBias().Clamp; + rasterizerState.DepthBias = static_cast(rasterizer.depthStencilState().depthBias().ConstantFactor); + rasterizerState.SlopeScaledDepthBias = rasterizer.depthStencilState().depthBias().SlopeFactor; } // Setup input assembler state. @@ -123,21 +123,21 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement(pipeline, pipeline.getDevice()) +DirectX12Rasterizer::DirectX12Rasterizer(const DirectX12RenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const DepthStencilState& depthStencilState) noexcept : + Rasterizer(polygonMode, cullMode, cullOrder, lineWidth, depthStencilState), DirectX12RuntimeObject(pipeline, pipeline.getDevice()) { } @@ -31,8 +31,9 @@ class DirectX12RasterizerBuilder::DirectX12RasterizerBuilderImpl : public Implem CullMode m_cullMode = CullMode::BackFaces; CullOrder m_cullOrder = CullOrder::CounterClockWise; Float m_lineWidth = 1.f; - Float m_depthBiasClamp = 0.f, m_depthBiasConstantFactor = 0.f, m_depthBiasSlopeFactor = 0.f; - bool m_depthBias = false; + DepthStencilState::DepthBias m_depthBias; + DepthStencilState::DepthState m_depthState; + DepthStencilState::StencilState m_stencilState; public: DirectX12RasterizerBuilderImpl(DirectX12RasterizerBuilder* parent) : @@ -45,7 +46,7 @@ class DirectX12RasterizerBuilder::DirectX12RasterizerBuilderImpl : public Implem // Builder shared interface. // ------------------------------------------------------------------------------------------------ -DirectX12RasterizerBuilder::DirectX12RasterizerBuilder(DirectX12RenderPipelineBuilder& parent) noexcept : +DirectX12RasterizerBuilder::DirectX12RasterizerBuilder(DirectX12RenderPipelineBuilder & parent) noexcept : m_impl(makePimpl(this)), RasterizerBuilder(parent, SharedPtr(new DirectX12Rasterizer(*std::as_const(parent).instance()))) { } @@ -58,58 +59,51 @@ DirectX12RenderPipelineBuilder& DirectX12RasterizerBuilder::go() this->instance()->cullMode() = m_impl->m_cullMode; this->instance()->cullOrder() = m_impl->m_cullOrder; this->instance()->lineWidth() = m_impl->m_lineWidth; - this->instance()->useDepthBias() = m_impl->m_depthBias; - this->instance()->depthBiasClamp() = m_impl->m_depthBiasClamp; - this->instance()->depthBiasConstantFactor() = m_impl->m_depthBiasConstantFactor; - this->instance()->depthBiasSlopeFactor() = m_impl->m_depthBiasSlopeFactor; + this->instance()->depthStencilState().depthBias() = m_impl->m_depthBias; + this->instance()->depthStencilState().depthState() = m_impl->m_depthState; + this->instance()->depthStencilState().stencilState() = m_impl->m_stencilState; return RasterizerBuilder::go(); } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withPolygonMode(const PolygonMode& mode) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withPolygonMode(const PolygonMode & mode) noexcept { m_impl->m_polygonMode = mode; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withCullMode(const CullMode& cullMode) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withCullMode(const CullMode & cullMode) noexcept { m_impl->m_cullMode = cullMode; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withCullOrder(const CullOrder& cullOrder) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withCullOrder(const CullOrder & cullOrder) noexcept { m_impl->m_cullOrder = cullOrder; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withLineWidth(const Float& lineWidth) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withLineWidth(const Float & lineWidth) noexcept { m_impl->m_lineWidth = lineWidth; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::enableDepthBias(const bool& enable) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withDepthBias(const DepthStencilState::DepthBias & depthBias) noexcept { - m_impl->m_depthBias = enable; + m_impl->m_depthBias = depthBias; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withDepthBiasClamp(const Float& clamp) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withDepthState(const DepthStencilState::DepthState & depthState) noexcept { - m_impl->m_depthBiasClamp = clamp; + m_impl->m_depthState = depthState; return *this; } -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withDepthBiasConstantFactor(const Float& factor) noexcept +DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withStencilState(const DepthStencilState::StencilState & stencilState) noexcept { - m_impl->m_depthBiasConstantFactor = factor; - return *this; -} - -DirectX12RasterizerBuilder& DirectX12RasterizerBuilder::withDepthBiasSlopeFactor(const Float& factor) noexcept -{ - m_impl->m_depthBiasSlopeFactor = factor; + m_impl->m_stencilState = stencilState; return *this; } \ No newline at end of file diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index 2c7546351..1146323c2 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -788,11 +788,8 @@ namespace LiteFX::Rendering::Backends { /// The cull mode used by the pipeline. /// The cull order used by the pipeline. /// The line width used by the pipeline. - /// true, if the depth bias should be enabled. - /// The clamp value of the depth bias state. - /// The constant factor of the depth bias state. - /// The slope factor of the depth bias state. - explicit VulkanRasterizer(const VulkanRenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const bool& useDepthBias = false, const Float& depthBiasClamp = 1.f, const Float& depthBiasConstantFactor = 0.f, const Float& depthBiasSlopeFactor = 0.f) noexcept; + /// The rasterizer depth/stencil state. + explicit VulkanRasterizer(const VulkanRenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const DepthStencilState& depthStencilState = {}) noexcept; VulkanRasterizer(VulkanRasterizer&&) noexcept = delete; VulkanRasterizer(const VulkanRasterizer&) noexcept = delete; virtual ~VulkanRasterizer() noexcept; @@ -838,16 +835,13 @@ namespace LiteFX::Rendering::Backends { virtual VulkanRasterizerBuilder& withLineWidth(const Float& lineWidth = 1.f) noexcept override; /// - virtual VulkanRasterizerBuilder& enableDepthBias(const bool& enable = false) noexcept override; + virtual VulkanRasterizerBuilder& withDepthBias(const DepthStencilState::DepthBias& depthBias) noexcept override; /// - virtual VulkanRasterizerBuilder& withDepthBiasClamp(const Float& clamp = 0.f) noexcept override; + virtual VulkanRasterizerBuilder& withDepthState(const DepthStencilState::DepthState& depthState) noexcept override; /// - virtual VulkanRasterizerBuilder& withDepthBiasConstantFactor(const Float& factor = 0.f) noexcept override; - - /// - virtual VulkanRasterizerBuilder& withDepthBiasSlopeFactor(const Float& factor = 0.f) noexcept override; + virtual VulkanRasterizerBuilder& withStencilState(const DepthStencilState::StencilState& stencilState) noexcept override; }; /// diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp index cbf4ed802..6f0aa8f49 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp @@ -142,6 +142,16 @@ namespace LiteFX::Rendering::Backends { /// VkSampleCountFlagBits LITEFX_VULKAN_API getSamples(const MultiSamplingLevel& samples); + /// + /// + /// + VkCompareOp LITEFX_VULKAN_API getCompareOp(const CompareOperation& compareOp); + + /// + /// + /// + VkStencilOp LITEFX_VULKAN_API getStencilOp(const StencilOperation& stencilOp); + /// /// Represents a Vulkan . /// diff --git a/src/Backends/Vulkan/src/convert.cpp b/src/Backends/Vulkan/src/convert.cpp index 7e368e516..0b853ac98 100644 --- a/src/Backends/Vulkan/src/convert.cpp +++ b/src/Backends/Vulkan/src/convert.cpp @@ -850,4 +850,34 @@ VkSampleCountFlagBits LiteFX::Rendering::Backends::getSamples(const MultiSamplin default: throw std::invalid_argument("Unsupported number of samples."); } +} + +VkCompareOp LiteFX::Rendering::Backends::getCompareOp(const CompareOperation& compareOp) +{ + switch (compareOp) { + case CompareOperation::Never: return VkCompareOp::VK_COMPARE_OP_NEVER; + case CompareOperation::Less: return VkCompareOp::VK_COMPARE_OP_LESS; + case CompareOperation::Greater: return VkCompareOp::VK_COMPARE_OP_GREATER; + case CompareOperation::Equal: return VkCompareOp::VK_COMPARE_OP_EQUAL; + case CompareOperation::LessEqual: return VkCompareOp::VK_COMPARE_OP_LESS_OR_EQUAL; + case CompareOperation::GreaterEqual: return VkCompareOp::VK_COMPARE_OP_GREATER_OR_EQUAL; + case CompareOperation::NotEqual: return VkCompareOp::VK_COMPARE_OP_NOT_EQUAL; + case CompareOperation::Always: return VkCompareOp::VK_COMPARE_OP_ALWAYS; + default: throw InvalidArgumentException("Unsupported compare operation."); + } +} + +VkStencilOp LiteFX::Rendering::Backends::getStencilOp(const StencilOperation& stencilOp) +{ + switch (stencilOp) { + case StencilOperation::Keep: return VkStencilOp::VK_STENCIL_OP_KEEP; + case StencilOperation::Zero: return VkStencilOp::VK_STENCIL_OP_ZERO; + case StencilOperation::Invert: return VkStencilOp::VK_STENCIL_OP_INVERT; + case StencilOperation::Replace: return VkStencilOp::VK_STENCIL_OP_REPLACE; + case StencilOperation::IncrementClamp: return VkStencilOp::VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case StencilOperation::IncrementWrap: return VkStencilOp::VK_STENCIL_OP_INCREMENT_AND_WRAP; + case StencilOperation::DecrementClamp: return VkStencilOp::VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case StencilOperation::DecrementWrap: return VkStencilOp::VK_STENCIL_OP_DECREMENT_AND_WRAP; + default: throw InvalidArgumentException("Unsupported stencil operation."); + } } \ No newline at end of file diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 29e653fad..11d1101bc 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -49,15 +49,15 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implementprogram().modules(); diff --git a/src/Backends/Vulkan/src/rasterizer.cpp b/src/Backends/Vulkan/src/rasterizer.cpp index 713342013..9ea6bf803 100644 --- a/src/Backends/Vulkan/src/rasterizer.cpp +++ b/src/Backends/Vulkan/src/rasterizer.cpp @@ -6,8 +6,8 @@ using namespace LiteFX::Rendering::Backends; // Shared interface. // ------------------------------------------------------------------------------------------------ -VulkanRasterizer::VulkanRasterizer(const VulkanRenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const bool& useDepthBias, const Float& depthBiasClamp, const Float& depthBiasConstantFactor, const Float& depthBiasSlopeFactor) noexcept : - Rasterizer(polygonMode, cullMode, cullOrder, lineWidth, useDepthBias, depthBiasClamp, depthBiasConstantFactor, depthBiasSlopeFactor), VulkanRuntimeObject(pipeline, pipeline.getDevice()) +VulkanRasterizer::VulkanRasterizer(const VulkanRenderPipeline& pipeline, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const DepthStencilState& depthStencilState) noexcept : + Rasterizer(polygonMode, cullMode, cullOrder, lineWidth, depthStencilState), VulkanRuntimeObject(pipeline, pipeline.getDevice()) { } @@ -31,8 +31,9 @@ class VulkanRasterizerBuilder::VulkanRasterizerBuilderImpl : public Implementinstance()->cullMode() = m_impl->m_cullMode; this->instance()->cullOrder() = m_impl->m_cullOrder; this->instance()->lineWidth() = m_impl->m_lineWidth; - this->instance()->useDepthBias() = m_impl->m_depthBias; - this->instance()->depthBiasClamp() = m_impl->m_depthBiasClamp; - this->instance()->depthBiasConstantFactor() = m_impl->m_depthBiasConstantFactor; - this->instance()->depthBiasSlopeFactor() = m_impl->m_depthBiasSlopeFactor; + this->instance()->depthStencilState().depthBias() = m_impl->m_depthBias; + this->instance()->depthStencilState().depthState() = m_impl->m_depthState; + this->instance()->depthStencilState().stencilState() = m_impl->m_stencilState; return RasterizerBuilder::go(); } @@ -90,26 +90,20 @@ VulkanRasterizerBuilder& VulkanRasterizerBuilder::withLineWidth(const Float& lin return *this; } -VulkanRasterizerBuilder& VulkanRasterizerBuilder::enableDepthBias(const bool& enable) noexcept +VulkanRasterizerBuilder& VulkanRasterizerBuilder::withDepthBias(const DepthStencilState::DepthBias& depthBias) noexcept { - m_impl->m_depthBias = enable; + m_impl->m_depthBias = depthBias; return *this; } -VulkanRasterizerBuilder& VulkanRasterizerBuilder::withDepthBiasClamp(const Float& clamp) noexcept +VulkanRasterizerBuilder& VulkanRasterizerBuilder::withDepthState(const DepthStencilState::DepthState& depthState) noexcept { - m_impl->m_depthBiasClamp = clamp; + m_impl->m_depthState = depthState; return *this; } -VulkanRasterizerBuilder& VulkanRasterizerBuilder::withDepthBiasConstantFactor(const Float& factor) noexcept +VulkanRasterizerBuilder& VulkanRasterizerBuilder::withStencilState(const DepthStencilState::StencilState& stencilState) noexcept { - m_impl->m_depthBiasConstantFactor = factor; - return *this; -} - -VulkanRasterizerBuilder& VulkanRasterizerBuilder::withDepthBiasSlopeFactor(const Float& factor) noexcept -{ - m_impl->m_depthBiasSlopeFactor = factor; + m_impl->m_stencilState = stencilState; return *this; } \ No newline at end of file diff --git a/src/Rendering/CMakeLists.txt b/src/Rendering/CMakeLists.txt index 4f2080f5b..bc22827b8 100644 --- a/src/Rendering/CMakeLists.txt +++ b/src/Rendering/CMakeLists.txt @@ -17,6 +17,7 @@ SET(RENDERING_HEADERS SET(RENDERING_SOURCES "src/convert.cpp" "src/buffer_attribute.cpp" + "src/depth_stencil_state.cpp" "src/rasterizer.cpp" "src/viewport.cpp" "src/scissor.cpp" diff --git a/src/Rendering/include/litefx/rendering_api.hpp b/src/Rendering/include/litefx/rendering_api.hpp index 1c1a7a932..a5642c643 100644 --- a/src/Rendering/include/litefx/rendering_api.hpp +++ b/src/Rendering/include/litefx/rendering_api.hpp @@ -461,6 +461,28 @@ namespace LiteFX::Rendering { ClampToBorder = 0x00000003, }; + enum class LITEFX_RENDERING_API CompareOperation { + Never = 0x00000000, + Less = 0x00000001, + Greater = 0x0000002, + Equal = 0x00000003, + LessEqual = 0x00000004, + GreaterEqual = 0x00000005, + NotEqual = 0x00000006, + Always = 0x00000007 + }; + + enum class LITEFX_RENDERING_API StencilOperation { + Keep = 0x00000000, + Zero = 0x00000001, + Replace = 0x00000002, + IncrementClamp = 0x00000003, + DecrementClamp = 0x00000004, + Invert = 0x00000005, + IncrementWrap = 0x00000006, + DecrementWrap = 0x00000007 + }; + // Define flags. LITEFX_DEFINE_FLAGS(QueueType); LITEFX_DEFINE_FLAGS(ShaderStage); @@ -775,6 +797,184 @@ namespace LiteFX::Rendering { virtual void submit(const bool& wait = false) const = 0; }; + /// + /// Stores the depth/stencil state of a see . + /// + class LITEFX_RENDERING_API DepthStencilState { + LITEFX_IMPLEMENTATION(DepthStencilStateImpl); + + public: + /// + /// Describes the rasterizer depth state. + /// + struct DepthState { + public: + /// + /// Specifies, if depth testing should be enabled (default: true). + /// + bool Enable{ true }; + + /// + /// Specifies, if depth should be written (default: true). + /// + bool Write{ true }; + + /// + /// The compare operation used to pass the depth test (default: CompareOperation::Always). + /// + CompareOperation Operation{ CompareOperation::Always }; + }; + + /// + /// Describes the rasterizer depth bias. + /// + /// + /// The depth bias can be used to alter the depth value function, i.e. how the values within the depth buffer are distributed. By default, the depth buffer + /// uses an exponential function scale to increase precision for closer objects. The values provided with , + /// and are used to change the domain clamping, offset and steepness of the depth + /// value distribution. + /// + struct DepthBias { + public: + /// + /// Specifies, if depth bias should be used (default: false). + /// + bool Enable{ false }; + + /// + /// Specifies the depth bias clamp (default: 0.0). + /// + Float Clamp{ 0.f }; + + /// + /// Specifies the depth bias slope factor (default: 0.0). + /// + Float SlopeFactor{ 0.f }; + + /// + /// Specifies the depth bias constant factor (default: 0.0). + /// + Float ConstantFactor{ 0.f }; + }; + + /// + /// Describes a stencil test for either front or back faces. + /// + struct StencilTest { + public: + /// + /// The operation to apply to the stencil buffer, if the stencil test fails (default: StencilOperation::Keep). + /// + StencilOperation StencilFailOp{ StencilOperation::Keep }; + + /// + /// The operation to apply to the stencil buffer, if the stencil test passes (default: StencilOperation::Keep). + /// + StencilOperation StencilPassOp{ StencilOperation::Replace }; + + /// + /// The operation to apply to the stencil buffer, if the depth test fails (default: StencilOperation::Keep). + /// + StencilOperation DepthFailOp{ StencilOperation::Keep }; + + /// + /// The operation use for stencil testing (default: CompareOperation::Never). + /// + CompareOperation Operation{ CompareOperation::Never }; + }; + + /// + /// Describes the rasterizer stencil state. + /// + struct StencilState { + public: + /// + /// Specifies, if stencil state should be used (default: false). + /// + bool Enable{ false }; + + /// + /// Specifies the bits to write to the stencil state (default: 0xFF). + /// + Byte WriteMask{ 0xFF }; + + /// + /// Specifies the bits to read from the stencil state (default: 0xFF). + /// + Byte ReadMask{ 0xFF }; + + /// + /// Describes the stencil test for faces that point towards the camera. + /// + StencilTest FrontFace{}; + + /// + /// Describes the stencil test for faces that point away from the camera. + /// + StencilTest BackFace{}; + }; + + public: + /// + /// Initializes a new rasterizer depth/stencil state. + /// + /// The depth state of the rasterizer. + /// The depth bias configuration of the rasterizer. + /// The stencil state of the rasterizer. + explicit DepthStencilState(const DepthState& depthState, const DepthBias& depthBias, const StencilState& stencilState) noexcept; + + /// + /// Initializes a new rasterizer depth/stencil state. + /// + DepthStencilState() noexcept; + + /// + /// Creates a copy of a depth/stencil state. + /// + DepthStencilState(const DepthStencilState&) noexcept; + + /// + /// Moves a depth/stencil state. + /// + DepthStencilState(DepthStencilState&&) noexcept; + + /// + /// Destroys a depth/stencil state. + /// + virtual ~DepthStencilState() noexcept; + + /// + /// Copies a depth/stencil state. + /// + /// A reference to the current depth/stencil state instance. + DepthStencilState& operator=(const DepthStencilState&) noexcept; + + /// + /// Moves a depth/stencil state. + /// + /// A reference to the current depth/stencil state instance. + DepthStencilState& operator=(DepthStencilState&&) noexcept; + + public: + /// + /// Returns the depth state. + /// + /// The depth state. + virtual DepthState& depthState() const noexcept; + + /// + /// Returns the depth bias. + /// + /// The depth bias. + virtual DepthBias& depthBias() const noexcept; + + /// + /// Returns the stencil state. + /// + /// The stencil state. + virtual StencilState& stencilState() const noexcept; + }; + /// /// Represents the rasterizer state of a . /// @@ -812,37 +1012,10 @@ namespace LiteFX::Rendering { virtual const Float& lineWidth() const noexcept = 0; /// - /// Returns true, if the depth bias is used. + /// Returns the depth/stencil state of the rasterizer. /// - /// - /// The depth bias can be used to alter the depth value function, i.e. how the values within the depth buffer are distributed. By default, the depth buffer - /// uses an exponential function scale to increase precision for closer objects. The values provided with , - /// and are used to change the domain clamping, offset and steepness of the depth - /// value distribution. - /// - /// true, if the depth bias is used. - virtual bool useDepthBias() const noexcept = 0; - - /// - /// Returns the depth bias clamp. - /// - /// The depth bias clamp. - /// - virtual const Float& depthBiasClamp() const noexcept = 0; - - /// - /// Returns the constant depth bias factor. - /// - /// The constant depth bias factor. - /// - virtual const Float& depthBiasConstantFactor() const noexcept = 0; - - /// - /// Returns the depth bias slope factor. - /// - /// The depth bias slope factor. - /// - virtual const Float& depthBiasSlopeFactor() const noexcept = 0; + /// The depth/stencil state of the rasterizer. + virtual const DepthStencilState& depthStencilState() const noexcept = 0; }; /// @@ -859,11 +1032,8 @@ namespace LiteFX::Rendering { /// The cull mode of the rasterizer state. /// The cull order of the rasterizer state. /// The line width of the rasterizer state. - /// true, if the rasterizer state uses the depth bias. - /// The depth bias clamp value of the rasterizer state. - /// The depth bias constant factor of the rasterizer state. - /// The depth bias slope factor of the rasterizer state. - explicit Rasterizer(const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const bool& useDepthBias = false, const Float& depthBiasClamp = 1.f, const Float& depthBiasConstantFactor = 0.f, const Float& depthBiasSlopeFactor = 0.f) noexcept; + /// The rasterizer depth/stencil state. + explicit Rasterizer(const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth = 1.f, const DepthStencilState& depthStencilState = {}) noexcept; Rasterizer(Rasterizer&&) noexcept; Rasterizer(const Rasterizer&) noexcept; virtual ~Rasterizer() noexcept; @@ -882,26 +1052,14 @@ namespace LiteFX::Rendering { virtual const Float& lineWidth() const noexcept override; /// - virtual bool useDepthBias() const noexcept override; - - /// - virtual const Float& depthBiasClamp() const noexcept override; - - /// - virtual const Float& depthBiasConstantFactor() const noexcept override; - - /// - virtual const Float& depthBiasSlopeFactor() const noexcept override; + virtual const DepthStencilState& depthStencilState() const noexcept override; protected: virtual PolygonMode& polygonMode() noexcept; virtual CullMode& cullMode() noexcept; virtual CullOrder& cullOrder() noexcept; virtual Float& lineWidth() noexcept; - virtual bool& useDepthBias() noexcept; - virtual Float& depthBiasClamp() noexcept; - virtual Float& depthBiasConstantFactor() noexcept; - virtual Float& depthBiasSlopeFactor() noexcept; + virtual DepthStencilState& depthStencilState() noexcept; }; /// @@ -939,28 +1097,22 @@ namespace LiteFX::Rendering { virtual TDerived& withLineWidth(const Float& width) noexcept = 0; /// - /// Initializes the rasterizer state to use depth bias. - /// - /// true, if the rasterizer should use depth bias. - virtual TDerived& enableDepthBias(const bool& enable) noexcept = 0; - - /// - /// Initializes the rasterizer state with the provided depth bias clamp. + /// Initializes the rasterizer depth bias. /// - /// The depth bias clamp to initialize the rasterizer state with. - virtual TDerived& withDepthBiasClamp(const Float& clamp) noexcept = 0; + /// The depth bias the rasterizer should use. + virtual TDerived& withDepthBias(const DepthStencilState::DepthBias& depthBias) noexcept = 0; /// - /// Initializes the rasterizer state with the provided depth bias constant factor. + /// Initializes the rasterizer depth state. /// - /// The depth bias constant factor to initialize the rasterizer state with. - virtual TDerived& withDepthBiasConstantFactor(const Float& factor) noexcept = 0; + /// The depth state of the rasterizer. + virtual TDerived& withDepthState(const DepthStencilState::DepthState& depthState) noexcept = 0; /// - /// Initializes the rasterizer state with the provided depth bias slope factor. + /// Initializes the rasterizer stencil state. /// - /// The depth bias slope factor to initialize the rasterizer state with. - virtual TDerived& withDepthBiasSlopeFactor(const Float& factor) noexcept = 0; + /// The stencil state of the rasterizer. + virtual TDerived& withStencilState(const DepthStencilState::StencilState& stencilState) noexcept = 0; }; /// diff --git a/src/Rendering/src/depth_stencil_state.cpp b/src/Rendering/src/depth_stencil_state.cpp new file mode 100644 index 000000000..9a1499953 --- /dev/null +++ b/src/Rendering/src/depth_stencil_state.cpp @@ -0,0 +1,82 @@ +#include + +using namespace LiteFX::Rendering; + +// ------------------------------------------------------------------------------------------------ +// Implementation. +// ------------------------------------------------------------------------------------------------ + +class DepthStencilState::DepthStencilStateImpl : public Implement { +public: + friend class DepthStencilState; + +private: + DepthState m_depthState; + DepthBias m_depthBias; + StencilState m_stencilState; + +public: + DepthStencilStateImpl(DepthStencilState* parent, const DepthState& depthState, const DepthBias& depthBias, const StencilState& stencilState) : + base(parent), m_depthState(depthState), m_stencilState(stencilState), m_depthBias(depthBias) + { + } +}; + +// ------------------------------------------------------------------------------------------------ +// Shared interface. +// ------------------------------------------------------------------------------------------------ + +DepthStencilState::DepthStencilState(const DepthState& depthState, const DepthBias& depthBias, const StencilState& stencilState) noexcept : + m_impl(makePimpl(this, depthState, depthBias, stencilState)) +{ +} + +DepthStencilState::DepthStencilState() noexcept : + m_impl(makePimpl(this, DepthState{}, DepthBias{}, StencilState{})) +{ +} + +DepthStencilState::DepthStencilState(const DepthStencilState& _other) noexcept : + m_impl(makePimpl(this, _other.depthState(), _other.depthBias(), _other.stencilState())) +{ +} + +DepthStencilState::DepthStencilState(DepthStencilState&& _other) noexcept : + m_impl(makePimpl(this, _other.depthState(), _other.depthBias(), _other.stencilState())) +{ +} + +DepthStencilState::~DepthStencilState() noexcept = default; + +DepthStencilState& DepthStencilState::operator=(const DepthStencilState& _other) noexcept +{ + m_impl->m_depthState = _other.depthState(); + m_impl->m_depthBias = _other.depthBias(); + m_impl->m_stencilState = _other.stencilState(); + + return *this; +} + +DepthStencilState& DepthStencilState::operator=(DepthStencilState&& _other) noexcept +{ + m_impl->m_depthState = std::move(_other.m_impl->m_depthState); + m_impl->m_depthBias = std::move(_other.m_impl->m_depthBias); + m_impl->m_stencilState = std::move(_other.m_impl->m_stencilState); + + return *this; +} + +DepthStencilState::DepthState& DepthStencilState::depthState() const noexcept +{ + return m_impl->m_depthState; +} + +DepthStencilState::DepthBias& DepthStencilState::depthBias() const noexcept +{ + return m_impl->m_depthBias; +} + +DepthStencilState::StencilState& DepthStencilState::stencilState() const noexcept +{ + return m_impl->m_stencilState; +} \ No newline at end of file diff --git a/src/Rendering/src/rasterizer.cpp b/src/Rendering/src/rasterizer.cpp index f3978fafe..fb6ebd056 100644 --- a/src/Rendering/src/rasterizer.cpp +++ b/src/Rendering/src/rasterizer.cpp @@ -15,12 +15,11 @@ class Rasterizer::RasterizerImpl : public Implement { CullMode m_cullMode = CullMode::BackFaces; CullOrder m_cullOrder = CullOrder::CounterClockWise; Float m_lineWidth = 1.f; - Float m_depthBiasClamp = 0.f, m_depthBiasConstantFactor = 0.f, m_depthBiasSlopeFactor = 0.f; - bool m_depthBias = false; + DepthStencilState m_depthStencilState; public: - RasterizerImpl(Rasterizer* parent, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const bool& useDepthBias, const Float& depthBiasClamp, const Float& depthBiasConstantFactor, const Float& depthBiasSlopeFactor) : - base(parent), m_polygonMode(polygonMode), m_cullMode(cullMode), m_cullOrder(cullOrder), m_lineWidth(lineWidth), m_depthBias(useDepthBias), m_depthBiasClamp(depthBiasClamp), m_depthBiasConstantFactor(depthBiasConstantFactor), m_depthBiasSlopeFactor(depthBiasSlopeFactor) + RasterizerImpl(Rasterizer* parent, const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const DepthStencilState& depthStencilState) : + base(parent), m_polygonMode(polygonMode), m_cullMode(cullMode), m_cullOrder(cullOrder), m_lineWidth(lineWidth), m_depthStencilState(depthStencilState) { } }; @@ -29,19 +28,19 @@ class Rasterizer::RasterizerImpl : public Implement { // Shared interface. // ------------------------------------------------------------------------------------------------ -Rasterizer::Rasterizer(const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const bool& useDepthBias, const Float& depthBiasClamp, const Float& depthBiasConstantFactor, const Float& depthBiasSlopeFactor) noexcept : - m_impl(makePimpl(this, polygonMode, cullMode, cullOrder, lineWidth, useDepthBias, depthBiasClamp, depthBiasConstantFactor, depthBiasSlopeFactor)) +Rasterizer::Rasterizer(const PolygonMode& polygonMode, const CullMode& cullMode, const CullOrder& cullOrder, const Float& lineWidth, const DepthStencilState& depthStencilState) noexcept : + m_impl(makePimpl(this, polygonMode, cullMode, cullOrder, lineWidth, depthStencilState)) { } Rasterizer::Rasterizer(const Rasterizer& _other) noexcept : - m_impl(makePimpl(this, _other.polygonMode(), _other.cullMode(), _other.cullOrder(), _other.lineWidth(), _other.useDepthBias(), _other.depthBiasClamp(), _other.depthBiasConstantFactor(), _other.depthBiasSlopeFactor())) + m_impl(makePimpl(this, _other.polygonMode(), _other.cullMode(), _other.cullOrder(), _other.lineWidth(), _other.depthStencilState())) { } Rasterizer::Rasterizer(Rasterizer&& _other) noexcept : - m_impl(makePimpl(this, _other.polygonMode(), _other.cullMode(), _other.cullOrder(), _other.lineWidth(), _other.useDepthBias(), _other.depthBiasClamp(), _other.depthBiasConstantFactor(), _other.depthBiasSlopeFactor())) + m_impl(makePimpl(this, _other.polygonMode(), _other.cullMode(), _other.cullOrder(), _other.lineWidth(), _other.depthStencilState())) { // TODO: We could move out the properties of `_other`, but I guess moving around rasterizer states really should not be the bottleneck in most applications. } @@ -68,24 +67,9 @@ const Float& Rasterizer::lineWidth() const noexcept return m_impl->m_lineWidth; } -bool Rasterizer::useDepthBias() const noexcept +const DepthStencilState& Rasterizer::depthStencilState() const noexcept { - return m_impl->m_depthBias; -} - -const Float& Rasterizer::depthBiasClamp() const noexcept -{ - return m_impl->m_depthBiasClamp; -} - -const Float& Rasterizer::depthBiasConstantFactor() const noexcept -{ - return m_impl->m_depthBiasConstantFactor; -} - -const Float& Rasterizer::depthBiasSlopeFactor() const noexcept -{ - return m_impl->m_depthBiasSlopeFactor; + return m_impl->m_depthStencilState; } PolygonMode& Rasterizer::polygonMode() noexcept @@ -108,22 +92,7 @@ Float& Rasterizer::lineWidth() noexcept return m_impl->m_lineWidth; } -bool& Rasterizer::useDepthBias() noexcept -{ - return m_impl->m_depthBias; -} - -Float& Rasterizer::depthBiasClamp() noexcept -{ - return m_impl->m_depthBiasClamp; -} - -Float& Rasterizer::depthBiasConstantFactor() noexcept -{ - return m_impl->m_depthBiasConstantFactor; -} - -Float& Rasterizer::depthBiasSlopeFactor() noexcept +DepthStencilState& Rasterizer::depthStencilState() noexcept { - return m_impl->m_depthBiasSlopeFactor; + return m_impl->m_depthStencilState; } \ No newline at end of file From 73b9ce7d8c892a74022b0ffe1091ee75b16af657 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:11:05 +0200 Subject: [PATCH 03/31] Set line width in dynamic state. Fixes issue #18. --- docs/release-logs/0.2.1.md | 7 +++++-- .../Vulkan/include/litefx/backends/vulkan.hpp | 15 +++++++++++++++ src/Backends/Vulkan/src/pipeline.cpp | 3 ++- src/Backends/Vulkan/src/rasterizer.cpp | 5 +++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/release-logs/0.2.1.md b/docs/release-logs/0.2.1.md index 732587a89..cd3b28a71 100644 --- a/docs/release-logs/0.2.1.md +++ b/docs/release-logs/0.2.1.md @@ -1,4 +1,7 @@ -# LiteFX 0.1.1 - Alpha 02 +# LiteFX 0.2.1 - Alpha 02 - Fixes issue that prevented DirectX 12 samples from being run in Release mode. -- Introduces rasterizer depth/stencil state. \ No newline at end of file +- Introduces rasterizer depth/stencil state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) +- Adds support for multisampling. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) +- Introduces render target blend states ([See PR #36](https://github.com/crud89/LiteFX/pull/36)). +- Vulkan 🌋: line width is part of the dynamic rasterizer state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) \ No newline at end of file diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index 1146323c2..c4898d822 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -796,6 +796,21 @@ namespace LiteFX::Rendering::Backends { private: explicit VulkanRasterizer(const VulkanRenderPipeline& pipeline) noexcept; + + public: + /// + /// Sets the line width on the rasterizer. + /// + /// + /// Note that updating the line width requires the "wide lines" feature to be available. If it is not, the line width **must** be `1.0`. This + /// constraint is not enforced by the engine and you are responsible of making sure that it is fulfilled. + /// + /// Furthermore, note that the DirectX 12 back-end does have any representation for the line width concept. Thus you should only use the line + /// width, if you plan to only support Vulkan. + /// + /// A reference to the line width. + /// + virtual void updateLineWidth(const Float& lineWidth) noexcept; }; /// diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 11d1101bc..479c2eda4 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -121,7 +121,7 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement(m_scissors.size()); // Setup dynamic state. - Array dynamicStates { VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, VkDynamicState::VK_DYNAMIC_STATE_SCISSOR }; + Array dynamicStates { VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, VkDynamicState::VK_DYNAMIC_STATE_SCISSOR, VkDynamicState::VK_DYNAMIC_STATE_LINE_WIDTH }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStates.data(); @@ -310,6 +310,7 @@ void VulkanRenderPipeline::use() const ::vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, this->handle()); ::vkCmdSetViewport(commandBuffer, 0, static_cast(viewports.size()), viewports.data()); ::vkCmdSetScissor(commandBuffer, 0, static_cast(scissors.size()), scissors.data()); + ::vkCmdSetLineWidth(commandBuffer, std::as_const(*m_impl->m_rasterizer).lineWidth()); } void VulkanRenderPipeline::draw(const UInt32& vertices, const UInt32& instances, const UInt32& firstVertex, const UInt32& firstInstance) const diff --git a/src/Backends/Vulkan/src/rasterizer.cpp b/src/Backends/Vulkan/src/rasterizer.cpp index 9ea6bf803..5be1ac59e 100644 --- a/src/Backends/Vulkan/src/rasterizer.cpp +++ b/src/Backends/Vulkan/src/rasterizer.cpp @@ -18,6 +18,11 @@ VulkanRasterizer::VulkanRasterizer(const VulkanRenderPipeline& pipeline) noexcep VulkanRasterizer::~VulkanRasterizer() noexcept = default; +void VulkanRasterizer::updateLineWidth(const Float& lineWidth) noexcept +{ + this->lineWidth() = lineWidth; +} + // ------------------------------------------------------------------------------------------------ // Builder implementation. // ------------------------------------------------------------------------------------------------ From 38d11db5bbc2a6c3638f095dedcc5c8e455e02a7 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:29:02 +0200 Subject: [PATCH 04/31] Minor typo fixes. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 859aa055b..91c54245b 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ auto pipeline = renderPass->makePipeline(0, "Basic Pipeline") .go(); ``` -LiteFX is written in modern C++20, following established design patterns to make it easy to learn and adapt. It's focus is make the performance of modern graphics APIs easily accessible, whilst retaining full flexibility. +LiteFX is written in modern C++20, following established design patterns to make it easy to learn and adapt. Its focus is make the performance of modern graphics APIs easily accessible, whilst retaining full flexibility. -† Shader can be built using *glslc* or *DXC*. *glslc* can be used to compile HLSL and GLSL shaders into SPIR-V for the Vulkan backend. *DXC* can only compile HLSL, but can target SPIR-V and DXIL, that's why it is preferred over *glslc*. +† Shaders can be built using *glslc* or *DXC*. *glslc* can be used to compile HLSL and GLSL shaders into SPIR-V for the Vulkan backend. *DXC* can only compile HLSL, but can target SPIR-V and DXIL, that's why it is preferred over *glslc*. ## Installation From 12be166063ddb8e72a23e6d6e5d380bfe1729edb Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:34:02 +0200 Subject: [PATCH 05/31] Add blend state to render target. --- .../include/litefx/rendering_api.hpp | 97 ++++++++++++++++++- src/Rendering/src/render_target.cpp | 22 +++-- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/Rendering/include/litefx/rendering_api.hpp b/src/Rendering/include/litefx/rendering_api.hpp index a5642c643..f6b3d5dbc 100644 --- a/src/Rendering/include/litefx/rendering_api.hpp +++ b/src/Rendering/include/litefx/rendering_api.hpp @@ -483,10 +483,48 @@ namespace LiteFX::Rendering { DecrementWrap = 0x00000007 }; + enum class LITEFX_RENDERING_API BlendFactor { + Zero = 0, + One = 1, + SourceColor = 2, + OneMinusSourceColor = 3, + DestinationColor = 4, + OneMinusDestinationColor = 5, + SourceAlpha = 6, + OneMinusSourceAlpha = 7, + DestinationAlpha = 8, + OneMinusDestinationAlpha = 9, + ConstantColor = 10, + OneMinusConstantColor = 11, + ConstantAlpha = 12, + OneMinusConstantAlpha = 13, + SourceAlphaSaturate = 14, + Source1Color = 15, + OneMinusSource1Color = 16, + Source1Alpha = 17, + OneMinusSource1Alpha = 18 + }; + + enum class LITEFX_RENDERING_API WriteMask { + R = 0x01, + G = 0x02, + B = 0x04, + A = 0x08 + }; + + enum class LITEFX_RENDERING_API BlendOperation { + Add = 0x01, + Subtract = 0x02, + ReverseSubtract = 0x03, + Minimum = 0x04, + Maximum = 0x05 + }; + // Define flags. LITEFX_DEFINE_FLAGS(QueueType); LITEFX_DEFINE_FLAGS(ShaderStage); LITEFX_DEFINE_FLAGS(BufferFormat); + LITEFX_DEFINE_FLAGS(WriteMask) // Helper functions. inline UInt32 getBufferFormatChannels(const BufferFormat& format) { @@ -625,6 +663,53 @@ namespace LiteFX::Rendering { /// /// class LITEFX_RENDERING_API IRenderTarget { + public: + /// + /// Describes the blend state of the render target. + /// + struct BlendState { + public: + /// + /// Specifies, if the target should be blended (default: false). + /// + bool Enable{ false }; + + /// + /// The blend factor for the source color channels (default: BlendFactor::One). + /// + BlendFactor SourceColor{ BlendFactor::One }; + + /// + /// The blend factor for the destination color channels (default: BlendFactor::Zero). + /// + BlendFactor DestinationColor{ BlendFactor::Zero }; + + /// + /// The blend factor for the source alpha channel (default: BlendFactor::One). + /// + BlendFactor SourceAlpha{ BlendFactor::One }; + + /// + /// The blend factor for the destination alpha channels (default: BlendFactor::Zero). + /// + BlendFactor DestinationAlpha{ BlendFactor::Zero }; + + /// + /// The blend operation for the color channels (default: BlendOperation::Add). + /// + BlendOperation ColorOperation{ BlendOperation::Add }; + + /// + /// The blend operation for the alpha channel (default: BlendOperation::Add). + /// + BlendOperation AlphaOperation{ BlendOperation::Add }; + + /// + /// The channel write mask, determining which channels are written to (default: WriteMask::R | WriteMask::G | WriteMask::B | WriteMask::A). + /// + WriteMask WriteMask{ WriteMask::R | WriteMask::G | WriteMask::B | WriteMask::A }; + }; + public: virtual ~IRenderTarget() noexcept = default; @@ -697,6 +782,12 @@ namespace LiteFX::Rendering { /// /// true, if the target should not be made persistent for access after the render pass has finished. virtual const bool& isVolatile() const noexcept = 0; + + /// + /// Returns the render targets blend state. + /// + /// The render targets blend state. + virtual const BlendState& blendState() const noexcept = 0; }; /// @@ -720,7 +811,8 @@ namespace LiteFX::Rendering { /// true, if the render target stencil should be cleared, when a render pass is started. /// The number of samples of the render target when used with multi-sampling. /// true, if the target should not be made persistent for access after the render pass has finished. - explicit RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues = { 0.f , 0.f, 0.f, 0.f }, const bool& clearStencil = true, const MultiSamplingLevel& samples = MultiSamplingLevel::x1, const bool& isVolatile = false); + /// The render target blend state. + explicit RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues = { 0.f , 0.f, 0.f, 0.f }, const bool& clearStencil = true, const MultiSamplingLevel& samples = MultiSamplingLevel::x1, const bool& isVolatile = false, const BlendState& blendState = {}); RenderTarget(const RenderTarget&) noexcept; RenderTarget(RenderTarget&&) noexcept; virtual ~RenderTarget() noexcept; @@ -753,6 +845,9 @@ namespace LiteFX::Rendering { /// virtual const bool& isVolatile() const noexcept override; + + /// + virtual const BlendState& blendState() const noexcept override; }; /// diff --git a/src/Rendering/src/render_target.cpp b/src/Rendering/src/render_target.cpp index 1df23349b..969cd79a2 100644 --- a/src/Rendering/src/render_target.cpp +++ b/src/Rendering/src/render_target.cpp @@ -17,10 +17,11 @@ class RenderTarget::RenderTargetImpl : public Implement { bool m_clearBuffer = false, m_clearStencil = false, m_volatile = false; Vector4f m_clearValues; UInt32 m_location; + BlendState m_blendState; public: - RenderTargetImpl(RenderTarget* parent, const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile) : - base(parent), m_location(location), m_type(type), m_format(format), m_clearBuffer(clearBuffer), m_clearValues(clearValues), m_clearStencil(clearStencil), m_samples(samples), m_volatile(isVolatile) + RenderTargetImpl(RenderTarget* parent, const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile, const BlendState& blendState) : + base(parent), m_location(location), m_type(type), m_format(format), m_clearBuffer(clearBuffer), m_clearValues(clearValues), m_clearStencil(clearStencil), m_samples(samples), m_volatile(isVolatile), m_blendState(blendState) { } }; @@ -30,22 +31,22 @@ class RenderTarget::RenderTargetImpl : public Implement { // ------------------------------------------------------------------------------------------------ RenderTarget::RenderTarget() noexcept : - m_impl(makePimpl(this, 0, RenderTargetType::Color, Format::None, false, Vector4f { 0.f, 0.f, 0.f, 0.f }, false, MultiSamplingLevel::x1, false)) + m_impl(makePimpl(this, 0, RenderTargetType::Color, Format::None, false, Vector4f { 0.f, 0.f, 0.f, 0.f }, false, MultiSamplingLevel::x1, false, BlendState{})) { } -RenderTarget::RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile) : - m_impl(makePimpl(this, location, type, format, clearBuffer, clearValues, clearStencil, samples, isVolatile)) +RenderTarget::RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile, const BlendState& blendState) : + m_impl(makePimpl(this, location, type, format, clearBuffer, clearValues, clearStencil, samples, isVolatile, blendState)) { } RenderTarget::RenderTarget(const RenderTarget& _other) noexcept : - m_impl(makePimpl(this, _other.location(), _other.type(), _other.format(), _other.clearBuffer(), _other.clearValues(), _other.clearStencil(), _other.samples(), _other.isVolatile())) + m_impl(makePimpl(this, _other.location(), _other.type(), _other.format(), _other.clearBuffer(), _other.clearValues(), _other.clearStencil(), _other.samples(), _other.isVolatile(), _other.blendState())) { } RenderTarget::RenderTarget(RenderTarget&& _other) noexcept : - m_impl(makePimpl(this, std::move(_other.m_impl->m_location), std::move(_other.m_impl->m_type), std::move(_other.m_impl->m_format), std::move(_other.m_impl->m_clearBuffer), std::move(_other.m_impl->m_clearValues), std::move(_other.m_impl->m_clearStencil), std::move(_other.m_impl->m_samples), std::move(_other.m_impl->m_volatile))) + m_impl(makePimpl(this, std::move(_other.m_impl->m_location), std::move(_other.m_impl->m_type), std::move(_other.m_impl->m_format), std::move(_other.m_impl->m_clearBuffer), std::move(_other.m_impl->m_clearValues), std::move(_other.m_impl->m_clearStencil), std::move(_other.m_impl->m_samples), std::move(_other.m_impl->m_volatile), std::move(_other.m_impl->m_blendState))) { } @@ -61,6 +62,7 @@ RenderTarget& RenderTarget::operator=(const RenderTarget& _other) noexcept m_impl->m_clearStencil = _other.m_impl->m_clearStencil; m_impl->m_samples = _other.m_impl->m_samples; m_impl->m_volatile = _other.m_impl->m_volatile; + m_impl->m_blendState = _other.m_impl->m_blendState; return *this; } @@ -75,6 +77,7 @@ RenderTarget& RenderTarget::operator=(RenderTarget&& _other) noexcept m_impl->m_clearStencil = std::move(_other.m_impl->m_clearStencil); m_impl->m_samples = std::move(_other.m_impl->m_samples); m_impl->m_volatile = std::move(_other.m_impl->m_volatile); + m_impl->m_blendState = std::move(_other.m_impl->m_blendState); return *this; } @@ -117,4 +120,9 @@ const Vector4f& RenderTarget::clearValues() const noexcept const bool& RenderTarget::isVolatile() const noexcept { return m_impl->m_volatile; +} + +const IRenderTarget::BlendState& RenderTarget::blendState() const noexcept +{ + return m_impl->m_blendState; } \ No newline at end of file From d49b79ac218f4e6d2f95cfe30f8cbe7ff1c2223f Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:49:00 +0200 Subject: [PATCH 06/31] Initialize DX 12 blend state. Implements #16 for the DX 12 backend. --- .../include/litefx/backends/dx12_api.hpp | 10 ++++++ src/Backends/DirectX12/src/convert.cpp | 36 +++++++++++++++++++ src/Backends/DirectX12/src/pipeline.cpp | 12 +++++-- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp index 27fbdae38..f435225d2 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12_api.hpp @@ -157,6 +157,16 @@ namespace LiteFX::Rendering::Backends { /// D3D12_STENCIL_OP LITEFX_DIRECTX12_API getStencilOp(const StencilOperation& stencilOp); + /// + /// + /// + D3D12_BLEND LITEFX_DIRECTX12_API getBlendFactor(const BlendFactor& blendFactor); + + /// + /// + /// + D3D12_BLEND_OP LITEFX_DIRECTX12_API getBlendOperation(const BlendOperation& blendOperation); + /// /// Implements a DirectX12 . /// diff --git a/src/Backends/DirectX12/src/convert.cpp b/src/Backends/DirectX12/src/convert.cpp index 5b62ea8c2..bff3940cd 100644 --- a/src/Backends/DirectX12/src/convert.cpp +++ b/src/Backends/DirectX12/src/convert.cpp @@ -533,4 +533,40 @@ D3D12_STENCIL_OP LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getStencilOp( case StencilOperation::DecrementWrap: return D3D12_STENCIL_OP::D3D12_STENCIL_OP_DECR; default: throw InvalidArgumentException("Unsupported stencil operation."); } +} + +D3D12_BLEND LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getBlendFactor(const BlendFactor& blendFactor) +{ + switch (blendFactor) { + case BlendFactor::Zero: return D3D12_BLEND_ZERO; + case BlendFactor::One: return D3D12_BLEND_ONE; + case BlendFactor::SourceColor: return D3D12_BLEND_SRC_COLOR; + case BlendFactor::OneMinusSourceColor: return D3D12_BLEND_INV_SRC_COLOR; + case BlendFactor::DestinationColor: return D3D12_BLEND_DEST_COLOR; + case BlendFactor::OneMinusDestinationColor: return D3D12_BLEND_INV_DEST_COLOR; + case BlendFactor::SourceAlpha: return D3D12_BLEND_SRC_ALPHA; + case BlendFactor::OneMinusSourceAlpha: return D3D12_BLEND_INV_SRC_ALPHA; + case BlendFactor::DestinationAlpha: return D3D12_BLEND_DEST_ALPHA; + case BlendFactor::OneMinusDestinationAlpha: return D3D12_BLEND_INV_DEST_ALPHA; + case BlendFactor::ConstantColor: return D3D12_BLEND_BLEND_FACTOR; + case BlendFactor::OneMinusConstantColor: return D3D12_BLEND_INV_BLEND_FACTOR; + case BlendFactor::ConstantAlpha: return D3D12_BLEND_BLEND_FACTOR; + case BlendFactor::OneMinusConstantAlpha: return D3D12_BLEND_INV_BLEND_FACTOR; + case BlendFactor::SourceAlphaSaturate: return D3D12_BLEND_SRC_ALPHA_SAT; + case BlendFactor::Source1Color: return D3D12_BLEND_SRC1_COLOR; + case BlendFactor::OneMinusSource1Color: return D3D12_BLEND_INV_SRC1_COLOR; + case BlendFactor::Source1Alpha: return D3D12_BLEND_SRC1_ALPHA; + case BlendFactor::OneMinusSource1Alpha: return D3D12_BLEND_INV_SRC1_ALPHA; + } +} + +D3D12_BLEND_OP LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getBlendOperation(const BlendOperation& blendOperation) +{ + switch (blendOperation) { + case BlendOperation::Add: return D3D12_BLEND_OP_ADD; + case BlendOperation::Subtract: return D3D12_BLEND_OP_SUBTRACT; + case BlendOperation::ReverseSubtract: return D3D12_BLEND_OP_REV_SUBTRACT; + case BlendOperation::Minimum: return D3D12_BLEND_OP_MIN; + case BlendOperation::Maximum: return D3D12_BLEND_OP_MAX; + } } \ No newline at end of file diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index fa2155cff..81ae154b6 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -147,8 +147,16 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement(renderTarget.blendState().WriteMask); + targetBlendState.SrcBlend = ::getBlendFactor(renderTarget.blendState().SourceColor); + targetBlendState.SrcBlendAlpha = ::getBlendFactor(renderTarget.blendState().SourceAlpha); + targetBlendState.DestBlend = ::getBlendFactor(renderTarget.blendState().DestinationColor); + targetBlendState.DestBlendAlpha = ::getBlendFactor(renderTarget.blendState().DestinationAlpha); + targetBlendState.BlendOp = ::getBlendOperation(renderTarget.blendState().ColorOperation); + targetBlendState.BlendOpAlpha = ::getBlendOperation(renderTarget.blendState().AlphaOperation); + + // TODO: We should also implement this, but this restricts all blend states to be equal and IndependentBlendEnable set to false. targetBlendState.LogicOp = D3D12_LOGIC_OP::D3D12_LOGIC_OP_COPY; targetBlendState.LogicOpEnable = FALSE; } From 4e4a9268103923d500a3f32a8fe92e6ce2b080bf Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:01:04 +0200 Subject: [PATCH 07/31] Implement Vulkan render target blend state. This implements issue #16 for the Vulkan backend. --- .../include/litefx/backends/vulkan_api.hpp | 10 ++++++ src/Backends/Vulkan/src/convert.cpp | 36 +++++++++++++++++++ src/Backends/Vulkan/src/pipeline.cpp | 29 ++++++++------- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp index 6f0aa8f49..d7af0d41e 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan_api.hpp @@ -152,6 +152,16 @@ namespace LiteFX::Rendering::Backends { /// VkStencilOp LITEFX_VULKAN_API getStencilOp(const StencilOperation& stencilOp); + /// + /// + /// + VkBlendFactor LITEFX_VULKAN_API getBlendFactor(const BlendFactor& blendFactor); + + /// + /// + /// + VkBlendOp LITEFX_VULKAN_API getBlendOperation(const BlendOperation& blendOperation); + /// /// Represents a Vulkan . /// diff --git a/src/Backends/Vulkan/src/convert.cpp b/src/Backends/Vulkan/src/convert.cpp index 0b853ac98..1a18af302 100644 --- a/src/Backends/Vulkan/src/convert.cpp +++ b/src/Backends/Vulkan/src/convert.cpp @@ -880,4 +880,40 @@ VkStencilOp LiteFX::Rendering::Backends::getStencilOp(const StencilOperation& st case StencilOperation::DecrementWrap: return VkStencilOp::VK_STENCIL_OP_DECREMENT_AND_WRAP; default: throw InvalidArgumentException("Unsupported stencil operation."); } +} + +VkBlendFactor LITEFX_VULKAN_API LiteFX::Rendering::Backends::getBlendFactor(const BlendFactor& blendFactor) +{ + switch (blendFactor) { + case BlendFactor::Zero: return VkBlendFactor::VK_BLEND_FACTOR_ZERO; + case BlendFactor::One: return VkBlendFactor::VK_BLEND_FACTOR_ONE; + case BlendFactor::SourceColor: return VkBlendFactor::VK_BLEND_FACTOR_SRC_COLOR; + case BlendFactor::OneMinusSourceColor: return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case BlendFactor::DestinationColor: return VK_BLEND_FACTOR_DST_COLOR; + case BlendFactor::OneMinusDestinationColor: return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case BlendFactor::SourceAlpha: return VK_BLEND_FACTOR_SRC_ALPHA; + case BlendFactor::OneMinusSourceAlpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case BlendFactor::DestinationAlpha: return VK_BLEND_FACTOR_DST_ALPHA; + case BlendFactor::OneMinusDestinationAlpha: return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case BlendFactor::ConstantColor: return VK_BLEND_FACTOR_CONSTANT_COLOR; + case BlendFactor::OneMinusConstantColor: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; + case BlendFactor::ConstantAlpha: return VK_BLEND_FACTOR_CONSTANT_ALPHA; + case BlendFactor::OneMinusConstantAlpha: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + case BlendFactor::SourceAlphaSaturate: return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; + case BlendFactor::Source1Color: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; + case BlendFactor::OneMinusSource1Color: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; + case BlendFactor::Source1Alpha: return VK_BLEND_FACTOR_SRC1_ALPHA; + case BlendFactor::OneMinusSource1Alpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; + } +} + +VkBlendOp LITEFX_VULKAN_API LiteFX::Rendering::Backends::getBlendOperation(const BlendOperation& blendOperation) +{ + switch (blendOperation) { + case BlendOperation::Add: return VkBlendOp::VK_BLEND_OP_ADD; + case BlendOperation::Subtract: return VkBlendOp::VK_BLEND_OP_SUBTRACT; + case BlendOperation::ReverseSubtract: return VkBlendOp::VK_BLEND_OP_REVERSE_SUBTRACT; + case BlendOperation::Minimum: return VkBlendOp::VK_BLEND_OP_MIN; + case BlendOperation::Maximum: return VkBlendOp::VK_BLEND_OP_MAX; + } } \ No newline at end of file diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 479c2eda4..aae6d999d 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -121,7 +121,7 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement(m_scissors.size()); // Setup dynamic state. - Array dynamicStates { VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, VkDynamicState::VK_DYNAMIC_STATE_SCISSOR, VkDynamicState::VK_DYNAMIC_STATE_LINE_WIDTH }; + Array dynamicStates { VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, VkDynamicState::VK_DYNAMIC_STATE_SCISSOR, VkDynamicState::VK_DYNAMIC_STATE_LINE_WIDTH, VkDynamicState::VK_DYNAMIC_STATE_BLEND_CONSTANTS }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStates.data(); @@ -139,18 +139,21 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implementparent().renderTargets(); - auto colorAttachments = std::ranges::count_if(targets, [](const RenderTarget& target) { return target.type() != RenderTargetType::DepthStencil; }); - - Array colorBlendAttachments(colorAttachments); - std::ranges::generate(colorBlendAttachments, []() { - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; - - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - return colorBlendAttachment; + Array colorBlendAttachments; + std::ranges::for_each(m_parent->parent().renderTargets(), [&colorBlendAttachments](const RenderTarget& renderTarget) { + if (renderTarget.type() == RenderTargetType::DepthStencil) + return; + + colorBlendAttachments.push_back(VkPipelineColorBlendAttachmentState { + .blendEnable = renderTarget.blendState().Enable, + .srcColorBlendFactor = ::getBlendFactor(renderTarget.blendState().SourceColor), + .dstColorBlendFactor = ::getBlendFactor(renderTarget.blendState().DestinationColor), + .colorBlendOp = ::getBlendOperation(renderTarget.blendState().ColorOperation), + .srcAlphaBlendFactor = ::getBlendFactor(renderTarget.blendState().SourceAlpha), + .dstAlphaBlendFactor = ::getBlendFactor(renderTarget.blendState().DestinationAlpha), + .alphaBlendOp = ::getBlendOperation(renderTarget.blendState().AlphaOperation), + .colorWriteMask = static_cast(renderTarget.blendState().WriteMask) + }); }); VkPipelineColorBlendStateCreateInfo colorBlending = {}; From 3cf8848cee9cb61a47a182630ec817a81336b302 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:09:31 +0200 Subject: [PATCH 08/31] Expose constant blend factors. This closes issue #16. --- .../DirectX12/include/litefx/backends/dx12.hpp | 3 +++ src/Backends/DirectX12/src/pipeline.cpp | 9 +++++++++ .../Vulkan/include/litefx/backends/vulkan.hpp | 3 +++ src/Backends/Vulkan/src/pipeline.cpp | 17 +++++++++++++---- src/Rendering/include/litefx/rendering.hpp | 11 +++++++++++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index d02a746b2..863bc57cd 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -870,6 +870,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Array scissors() const noexcept override; + /// + virtual Vector4f& blendFactors() const noexcept override; + public: /// /// diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index 81ae154b6..d1cc15743 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -19,6 +19,7 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement> m_scissors; UInt32 m_id; String m_name; + Vector4f m_blendFactors; public: DirectX12RenderPipelineImpl(DirectX12RenderPipeline* parent, const UInt32& id, const String& name, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : @@ -273,6 +274,11 @@ Array DirectX12RenderPipeline::scissors() const noexcept ranges::to>(); } +Vector4f& DirectX12RenderPipeline::blendFactors() const noexcept +{ + return m_impl->m_blendFactors; +} + void DirectX12RenderPipeline::bind(const IDirectX12VertexBuffer& buffer) const { const auto& commandBuffer = this->parent().activeFrameBuffer().commandBuffer(); @@ -312,11 +318,14 @@ void DirectX12RenderPipeline::use() const std::views::transform([](const SharedPtr& scissor) { return CD3DX12_RECT(scissor->getRectangle().x(), scissor->getRectangle().y(), scissor->getRectangle().width(), scissor->getRectangle().height()); }) | ranges::to>(); + Float blendFactor[4] = { m_impl->m_blendFactors.x(), m_impl->m_blendFactors.y(), m_impl->m_blendFactors.z(), m_impl->m_blendFactors.w() }; + const auto& commandBuffer = this->parent().activeFrameBuffer().commandBuffer(); commandBuffer.handle()->SetPipelineState(this->handle().Get()); commandBuffer.handle()->SetGraphicsRootSignature(std::as_const(*m_impl->m_layout).handle().Get()); commandBuffer.handle()->RSSetViewports(viewports.size(), viewports.data()); commandBuffer.handle()->RSSetScissorRects(scissors.size(), scissors.data()); + commandBuffer.handle()->OMSetBlendFactor(blendFactor); } void DirectX12RenderPipeline::draw(const UInt32& vertices, const UInt32& instances, const UInt32& firstVertex, const UInt32& firstInstance) const diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index c4898d822..22fce17a4 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -905,6 +905,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Array scissors() const noexcept override; + /// + virtual Vector4f& blendFactors() const noexcept override; + public: /// virtual void bind(const IVulkanVertexBuffer& buffer) const override; diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index aae6d999d..519a54e0c 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -19,6 +19,7 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement> m_scissors; UInt32 m_id; String m_name; + Vector4f m_blendFactors; public: VulkanRenderPipelineImpl(VulkanRenderPipeline* parent, const UInt32& id, const String& name, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : @@ -162,10 +163,10 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement(colorBlendAttachments.size()); colorBlending.pAttachments = colorBlendAttachments.data(); - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; + colorBlending.blendConstants[0] = m_blendFactors.x(); + colorBlending.blendConstants[1] = m_blendFactors.y(); + colorBlending.blendConstants[2] = m_blendFactors.z(); + colorBlending.blendConstants[3] = m_blendFactors.w(); // Setup depth/stencil state. VkPipelineDepthStencilStateCreateInfo depthStencilState = {}; @@ -282,6 +283,11 @@ Array VulkanRenderPipeline::scissors() const noexcept ranges::to>(); } +Vector4f& VulkanRenderPipeline::blendFactors() const noexcept +{ + return m_impl->m_blendFactors; +} + void VulkanRenderPipeline::bind(const IVulkanVertexBuffer& buffer) const { constexpr VkDeviceSize offsets[] = { 0 }; @@ -308,12 +314,15 @@ void VulkanRenderPipeline::use() const std::views::transform([](const auto& scissor) { return VkRect2D{VkOffset2D{.x = static_cast(scissor->getRectangle().x()), .y = static_cast(scissor->getRectangle().y())}, VkExtent2D{.width = static_cast(scissor->getRectangle().width()), .height = static_cast(scissor->getRectangle().height())} };}) | ranges::to>(); + Float blendFactor[4] = { m_impl->m_blendFactors.x(), m_impl->m_blendFactors.y(), m_impl->m_blendFactors.z(), m_impl->m_blendFactors.w() }; + // Bind the pipeline and setup the dynamic state. auto commandBuffer = this->parent().activeFrameBuffer().commandBuffer().handle(); ::vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, this->handle()); ::vkCmdSetViewport(commandBuffer, 0, static_cast(viewports.size()), viewports.data()); ::vkCmdSetScissor(commandBuffer, 0, static_cast(scissors.size()), scissors.data()); ::vkCmdSetLineWidth(commandBuffer, std::as_const(*m_impl->m_rasterizer).lineWidth()); + ::vkCmdSetBlendConstants(commandBuffer, blendFactor); } void VulkanRenderPipeline::draw(const UInt32& vertices, const UInt32& instances, const UInt32& firstVertex, const UInt32& firstInstance) const diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 524394952..296224d61 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1046,6 +1046,17 @@ namespace LiteFX::Rendering { /// The scissors of the render pipeline. virtual Array scissors() const noexcept = 0; + /// + /// Returns a reference of the constant blend factors for the pipeline. + /// + /// + /// You can change the values inside this vector reference to influence the constant blend factors. Blend factors are set for all render targets that use the + /// blend factors BlendFactor::ConstantColor, BlendFactor::OneMinusConstantColor, BlendFactor::ConstantAlpha or + /// BlendFactor::OneMinusConstantAlpha. They are set on each call to . + /// + /// A reference of the constant blend factors for the pipeline. + virtual Vector4f& blendFactors() const noexcept = 0; + public: /// /// Binds a vertex buffer to the pipeline. From 6ab36cdd389c773253c728dd38dcd3a5b398abf5 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:18:19 +0200 Subject: [PATCH 09/31] Throw exceptions for unsupported types. --- src/Backends/DirectX12/src/convert.cpp | 2 ++ src/Backends/Vulkan/src/convert.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Backends/DirectX12/src/convert.cpp b/src/Backends/DirectX12/src/convert.cpp index bff3940cd..2953387b8 100644 --- a/src/Backends/DirectX12/src/convert.cpp +++ b/src/Backends/DirectX12/src/convert.cpp @@ -557,6 +557,7 @@ D3D12_BLEND LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getBlendFactor(con case BlendFactor::OneMinusSource1Color: return D3D12_BLEND_INV_SRC1_COLOR; case BlendFactor::Source1Alpha: return D3D12_BLEND_SRC1_ALPHA; case BlendFactor::OneMinusSource1Alpha: return D3D12_BLEND_INV_SRC1_ALPHA; + default: throw InvalidArgumentException("Unsupported blend factor."); } } @@ -568,5 +569,6 @@ D3D12_BLEND_OP LITEFX_DIRECTX12_API LiteFX::Rendering::Backends::getBlendOperati case BlendOperation::ReverseSubtract: return D3D12_BLEND_OP_REV_SUBTRACT; case BlendOperation::Minimum: return D3D12_BLEND_OP_MIN; case BlendOperation::Maximum: return D3D12_BLEND_OP_MAX; + default: throw InvalidArgumentException("Unsupported blend operation."); } } \ No newline at end of file diff --git a/src/Backends/Vulkan/src/convert.cpp b/src/Backends/Vulkan/src/convert.cpp index 1a18af302..04684ab47 100644 --- a/src/Backends/Vulkan/src/convert.cpp +++ b/src/Backends/Vulkan/src/convert.cpp @@ -904,6 +904,7 @@ VkBlendFactor LITEFX_VULKAN_API LiteFX::Rendering::Backends::getBlendFactor(cons case BlendFactor::OneMinusSource1Color: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; case BlendFactor::Source1Alpha: return VK_BLEND_FACTOR_SRC1_ALPHA; case BlendFactor::OneMinusSource1Alpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; + default: throw InvalidArgumentException("Unsupported blend factor."); } } @@ -915,5 +916,6 @@ VkBlendOp LITEFX_VULKAN_API LiteFX::Rendering::Backends::getBlendOperation(const case BlendOperation::ReverseSubtract: return VkBlendOp::VK_BLEND_OP_REVERSE_SUBTRACT; case BlendOperation::Minimum: return VkBlendOp::VK_BLEND_OP_MIN; case BlendOperation::Maximum: return VkBlendOp::VK_BLEND_OP_MAX; + default: throw InvalidArgumentException("Unsupported blend operation."); } } \ No newline at end of file From 24aed2d886c5690f48f736f729e5b8e694411bf7 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:18:41 +0200 Subject: [PATCH 10/31] Add method to set the stencil reference. Relates to issue #17. --- .../DirectX12/include/litefx/backends/dx12.hpp | 3 +++ src/Backends/DirectX12/src/pipeline.cpp | 9 ++++++++- .../Vulkan/include/litefx/backends/vulkan.hpp | 3 +++ src/Backends/Vulkan/src/pipeline.cpp | 18 ++++++++++++++++-- src/Rendering/include/litefx/rendering.hpp | 9 +++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 863bc57cd..519628127 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -870,6 +870,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Array scissors() const noexcept override; + /// + virtual UInt32& stencilRef() const noexcept override; + /// virtual Vector4f& blendFactors() const noexcept override; diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index d1cc15743..b021b3f0e 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -19,7 +19,8 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement> m_scissors; UInt32 m_id; String m_name; - Vector4f m_blendFactors; + Vector4f m_blendFactors{ 0.f, 0.f, 0.f, 0.f }; + UInt32 m_stencilRef{ 0 }; public: DirectX12RenderPipelineImpl(DirectX12RenderPipeline* parent, const UInt32& id, const String& name, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : @@ -274,6 +275,11 @@ Array DirectX12RenderPipeline::scissors() const noexcept ranges::to>(); } +UInt32& DirectX12RenderPipeline::stencilRef() const noexcept +{ + return m_impl->m_stencilRef; +} + Vector4f& DirectX12RenderPipeline::blendFactors() const noexcept { return m_impl->m_blendFactors; @@ -325,6 +331,7 @@ void DirectX12RenderPipeline::use() const commandBuffer.handle()->SetGraphicsRootSignature(std::as_const(*m_impl->m_layout).handle().Get()); commandBuffer.handle()->RSSetViewports(viewports.size(), viewports.data()); commandBuffer.handle()->RSSetScissorRects(scissors.size(), scissors.data()); + commandBuffer.handle()->OMSetStencilRef(m_impl->m_stencilRef); commandBuffer.handle()->OMSetBlendFactor(blendFactor); } diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index 22fce17a4..bb72728f8 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -905,6 +905,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Array scissors() const noexcept override; + /// + virtual UInt32& stencilRef() const noexcept override; + /// virtual Vector4f& blendFactors() const noexcept override; diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 519a54e0c..85048271e 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -19,7 +19,8 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement> m_scissors; UInt32 m_id; String m_name; - Vector4f m_blendFactors; + Vector4f m_blendFactors{ 0.f, 0.f, 0.f, 0.f }; + UInt32 m_stencilRef{ 0 }; public: VulkanRenderPipelineImpl(VulkanRenderPipeline* parent, const UInt32& id, const String& name, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : @@ -122,7 +123,14 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement(m_scissors.size()); // Setup dynamic state. - Array dynamicStates { VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, VkDynamicState::VK_DYNAMIC_STATE_SCISSOR, VkDynamicState::VK_DYNAMIC_STATE_LINE_WIDTH, VkDynamicState::VK_DYNAMIC_STATE_BLEND_CONSTANTS }; + Array dynamicStates { + VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, + VkDynamicState::VK_DYNAMIC_STATE_SCISSOR, + VkDynamicState::VK_DYNAMIC_STATE_LINE_WIDTH, + VkDynamicState::VK_DYNAMIC_STATE_BLEND_CONSTANTS, + VkDynamicState::VK_DYNAMIC_STATE_STENCIL_REFERENCE + }; + VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStates.data(); @@ -283,6 +291,11 @@ Array VulkanRenderPipeline::scissors() const noexcept ranges::to>(); } +UInt32& VulkanRenderPipeline::stencilRef() const noexcept +{ + return m_impl->m_stencilRef; +} + Vector4f& VulkanRenderPipeline::blendFactors() const noexcept { return m_impl->m_blendFactors; @@ -323,6 +336,7 @@ void VulkanRenderPipeline::use() const ::vkCmdSetScissor(commandBuffer, 0, static_cast(scissors.size()), scissors.data()); ::vkCmdSetLineWidth(commandBuffer, std::as_const(*m_impl->m_rasterizer).lineWidth()); ::vkCmdSetBlendConstants(commandBuffer, blendFactor); + ::vkCmdSetStencilReference(commandBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, m_impl->m_stencilRef); } void VulkanRenderPipeline::draw(const UInt32& vertices, const UInt32& instances, const UInt32& firstVertex, const UInt32& firstInstance) const diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 296224d61..b7496c43d 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1046,6 +1046,15 @@ namespace LiteFX::Rendering { /// The scissors of the render pipeline. virtual Array scissors() const noexcept = 0; + /// + /// Returns a reference to the stencil reference value. + /// + /// + /// The stencil reference value is used by the stencil test and is set with each call to . + /// + /// A reference to the stencil reference value. + virtual UInt32& stencilRef() const noexcept = 0; + /// /// Returns a reference of the constant blend factors for the pipeline. /// From 73f1343fa80fa5f222438a396a8b9489608aad9b Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:51:10 +0200 Subject: [PATCH 11/31] Added a2c test. --- .../include/litefx/backends/dx12.hpp | 15 ++++++++---- src/Backends/DirectX12/src/pipeline.cpp | 24 +++++++++++++++---- .../Vulkan/include/litefx/backends/vulkan.hpp | 15 ++++++++---- src/Backends/Vulkan/src/pipeline.cpp | 24 +++++++++++++++---- src/Rendering/include/litefx/rendering.hpp | 24 +++++++++++++++++++ 5 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 519628127..76d5d82af 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -836,10 +836,11 @@ namespace LiteFX::Rendering::Backends { /// /// Initializes a new DirectX12 render pipeline. /// - /// - /// - /// - explicit DirectX12RenderPipeline(const DirectX12RenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const String& name = ""); + /// The parent render pass. + /// The unique ID of the pipeline within the render pass. + /// The optional debug name of the render pipeline. + /// Whether or not to enable Alpha-to-Coverage multi-sampling. + explicit DirectX12RenderPipeline(const DirectX12RenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const bool enableAlphaToCoverage = false, const String& name = ""); DirectX12RenderPipeline(DirectX12RenderPipeline&&) noexcept = delete; DirectX12RenderPipeline(const DirectX12RenderPipeline&) noexcept = delete; virtual ~DirectX12RenderPipeline() noexcept; @@ -876,6 +877,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Vector4f& blendFactors() const noexcept override; + /// + virtual const bool& alphaToCoverage() const noexcept override; + public: /// /// @@ -947,6 +951,9 @@ namespace LiteFX::Rendering::Backends { /// virtual void use(SharedPtr scissor) override; + /// + virtual DirectX12RenderPipelineBuilder& enableAlphaToCoverage(const bool& enable = true) override; + // DirectX12RenderPipelineBuilder. public: /// diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index b021b3f0e..44b9e4304 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -21,10 +21,11 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : - base(parent), m_id(id), m_name(name), m_layout(std::move(layout)), m_inputAssembler(std::move(inputAssembler)), m_rasterizer(std::move(rasterizer)), m_viewports(std::move(viewports)), m_scissors(std::move(scissors)) + DirectX12RenderPipelineImpl(DirectX12RenderPipeline* parent, const UInt32& id, const String& name, const bool& alphaToCoverage, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : + base(parent), m_id(id), m_name(name), m_alphaToCoverage(alphaToCoverage), m_layout(std::move(layout)), m_inputAssembler(std::move(inputAssembler)), m_rasterizer(std::move(rasterizer)), m_viewports(std::move(viewports)), m_scissors(std::move(scissors)) { } @@ -164,7 +165,7 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const String& name) : - m_impl(makePimpl(this, id, name, std::move(layout), std::move(inputAssembler), std::move(rasterizer), std::move(viewports), std::move(scissors))), DirectX12RuntimeObject(renderPass, renderPass.getDevice()), ComResource(nullptr) +DirectX12RenderPipeline::DirectX12RenderPipeline(const DirectX12RenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const bool enableAlphaToCoverage, const String& name) : + m_impl(makePimpl(this, id, name, enableAlphaToCoverage, std::move(layout), std::move(inputAssembler), std::move(rasterizer), std::move(viewports), std::move(scissors))), DirectX12RuntimeObject(renderPass, renderPass.getDevice()), ComResource(nullptr) { this->handle() = m_impl->initialize(); } @@ -285,6 +286,11 @@ Vector4f& DirectX12RenderPipeline::blendFactors() const noexcept return m_impl->m_blendFactors; } +const bool& DirectX12RenderPipeline::alphaToCoverage() const noexcept +{ + return m_impl->m_alphaToCoverage; +} + void DirectX12RenderPipeline::bind(const IDirectX12VertexBuffer& buffer) const { const auto& commandBuffer = this->parent().activeFrameBuffer().commandBuffer(); @@ -362,6 +368,7 @@ class DirectX12RenderPipelineBuilder::DirectX12RenderPipelineBuilderImpl : publi SharedPtr m_rasterizer; Array> m_viewports; Array> m_scissors; + bool m_alphaToCoverage{ false }; public: DirectX12RenderPipelineBuilderImpl(DirectX12RenderPipelineBuilder* parent) : @@ -391,6 +398,7 @@ UniquePtr DirectX12RenderPipelineBuilder::go() instance->m_impl->m_rasterizer = std::move(m_impl->m_rasterizer); instance->m_impl->m_viewports = std::move(m_impl->m_viewports); instance->m_impl->m_scissors = std::move(m_impl->m_scissors); + instance->m_impl->m_alphaToCoverage = std::move(m_impl->m_alphaToCoverage); instance->handle() = instance->m_impl->initialize(); return RenderPipelineBuilder::go(); @@ -441,6 +449,12 @@ void DirectX12RenderPipelineBuilder::use(SharedPtr scissor) m_impl->m_scissors.push_back(scissor); } +DirectX12RenderPipelineBuilder& DirectX12RenderPipelineBuilder::enableAlphaToCoverage(const bool& enable) +{ + m_impl->m_alphaToCoverage = enable; + return *this; +} + DirectX12RenderPipelineLayoutBuilder DirectX12RenderPipelineBuilder::layout() { return DirectX12RenderPipelineLayoutBuilder(*this); diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index bb72728f8..dfd7b2904 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -871,10 +871,11 @@ namespace LiteFX::Rendering::Backends { /// /// Initializes a new Vulkan render pipeline. /// - /// - /// - /// - explicit VulkanRenderPipeline(const VulkanRenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const String& name = ""); + /// The parent render pass. + /// The unique ID of the pipeline within the render pass. + /// The optional debug name of the render pipeline. + /// Whether or not to enable Alpha-to-Coverage multi-sampling. + explicit VulkanRenderPipeline(const VulkanRenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const bool& enableAlphaToCoverage = false, const String& name = ""); VulkanRenderPipeline(VulkanRenderPipeline&&) noexcept = delete; VulkanRenderPipeline(const VulkanRenderPipeline&) noexcept = delete; virtual ~VulkanRenderPipeline() noexcept; @@ -911,6 +912,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Vector4f& blendFactors() const noexcept override; + /// + virtual const bool& alphaToCoverage() const noexcept override; + public: /// virtual void bind(const IVulkanVertexBuffer& buffer) const override; @@ -972,6 +976,9 @@ namespace LiteFX::Rendering::Backends { /// virtual void use(SharedPtr scissor) override; + /// + virtual VulkanRenderPipelineBuilder& enableAlphaToCoverage(const bool& enable = true) override; + // VulkanRenderPipelineBuilder. public: /// diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 85048271e..004f0a2d5 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -21,10 +21,11 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : - base(parent), m_id(id), m_name(name), m_layout(std::move(layout)), m_inputAssembler(std::move(inputAssembler)), m_rasterizer(std::move(rasterizer)), m_viewports(std::move(viewports)), m_scissors(std::move(scissors)) + VulkanRenderPipelineImpl(VulkanRenderPipeline* parent, const UInt32& id, const String& name, const bool& alphaToCoverage, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors) : + base(parent), m_id(id), m_name(name), m_alphaToCoverage(alphaToCoverage), m_layout(std::move(layout)), m_inputAssembler(std::move(inputAssembler)), m_rasterizer(std::move(rasterizer)), m_viewports(std::move(viewports)), m_scissors(std::move(scissors)) { } @@ -144,7 +145,7 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const String& name) : - m_impl(makePimpl(this, id, name, std::move(layout), std::move(inputAssembler), std::move(rasterizer), std::move(viewports), std::move(scissors))), VulkanRuntimeObject(renderPass, renderPass.getDevice()), Resource(VK_NULL_HANDLE) +VulkanRenderPipeline::VulkanRenderPipeline(const VulkanRenderPass& renderPass, const UInt32& id, UniquePtr&& layout, SharedPtr&& inputAssembler, SharedPtr&& rasterizer, Array>&& viewports, Array>&& scissors, const bool& enableAlphaToCoverage, const String& name) : + m_impl(makePimpl(this, id, name, enableAlphaToCoverage, std::move(layout), std::move(inputAssembler), std::move(rasterizer), std::move(viewports), std::move(scissors))), VulkanRuntimeObject(renderPass, renderPass.getDevice()), Resource(VK_NULL_HANDLE) { this->handle() = m_impl->initialize(); } @@ -301,6 +302,11 @@ Vector4f& VulkanRenderPipeline::blendFactors() const noexcept return m_impl->m_blendFactors; } +const bool& VulkanRenderPipeline::alphaToCoverage() const noexcept +{ + return m_impl->m_alphaToCoverage; +} + void VulkanRenderPipeline::bind(const IVulkanVertexBuffer& buffer) const { constexpr VkDeviceSize offsets[] = { 0 }; @@ -364,6 +370,7 @@ class VulkanRenderPipelineBuilder::VulkanRenderPipelineBuilderImpl : public Impl SharedPtr m_rasterizer; Array> m_viewports; Array> m_scissors; + bool m_alphaToCoverage{ false }; public: VulkanRenderPipelineBuilderImpl(VulkanRenderPipelineBuilder* parent) : @@ -393,6 +400,7 @@ UniquePtr VulkanRenderPipelineBuilder::go() instance->m_impl->m_rasterizer = std::move(m_impl->m_rasterizer); instance->m_impl->m_viewports = std::move(m_impl->m_viewports); instance->m_impl->m_scissors = std::move(m_impl->m_scissors); + instance->m_impl->m_alphaToCoverage = std::move(m_impl->m_alphaToCoverage); instance->handle() = instance->m_impl->initialize(); return RenderPipelineBuilder::go(); @@ -443,6 +451,12 @@ void VulkanRenderPipelineBuilder::use(SharedPtr scissor) m_impl->m_scissors.push_back(scissor); } +VulkanRenderPipelineBuilder& VulkanRenderPipelineBuilder::enableAlphaToCoverage(const bool& enable) +{ + m_impl->m_alphaToCoverage = enable; + return *this; +} + VulkanRenderPipelineLayoutBuilder VulkanRenderPipelineBuilder::layout() { return VulkanRenderPipelineLayoutBuilder(*this); diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index b7496c43d..09d795681 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1066,6 +1066,21 @@ namespace LiteFX::Rendering { /// A reference of the constant blend factors for the pipeline. virtual Vector4f& blendFactors() const noexcept = 0; + /// + /// Returns true, if the pipeline uses Alpha-to-Coverage multi-sampling. + /// + /// + /// Alpha-to-Coverage is a multi-sampling technique used for partially transparent sprites or textures (such as foliage) to prevent visible flickering + /// along edges. If enabled, the alpha-channel of the first (non-depth/stencil) render target is used to generate a temporary coverage mask that is combined + /// with the fragment coverage mask using a logical AND. + /// + /// true, if the pipeline uses Alpha-to-Coverage multi-sampling. + /// + /// + /// + /// + virtual const bool& alphaToCoverage() const noexcept = 0; + public: /// /// Binds a vertex buffer to the pipeline. @@ -1211,6 +1226,15 @@ namespace LiteFX::Rendering { /// /// A scissor to initialize the render pipeline with. virtual void use(SharedPtr scissor) = 0; + + /// + /// Enables Alpha-to-Coverage multi-sampling on the pipeline. + /// + /// + /// For more information on Alpha-to-Coverage multi-sampling see the remarks of . + /// + /// Whether or not to use Alpha-to-Coverage multi-sampling. + virtual TDerived& enableAlphaToCoverage(const bool& enable = true) = 0; }; /// From df163d41a5f3fddb061c6f220324d419cc0c73b1 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 19:41:51 +0200 Subject: [PATCH 12/31] Add method to request maximum number of samples. --- .../include/litefx/backends/dx12.hpp | 4 +++ src/Backends/DirectX12/src/device.cpp | 20 +++++++++++++ src/Backends/DirectX12/src/pipeline.cpp | 7 ++--- .../Vulkan/include/litefx/backends/vulkan.hpp | 3 ++ src/Backends/Vulkan/src/device.cpp | 28 +++++++++++++++++++ src/Rendering/include/litefx/rendering.hpp | 11 ++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 76d5d82af..73a1d6472 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -1544,6 +1544,10 @@ namespace LiteFX::Rendering::Backends { /// virtual const DirectX12Queue& bufferQueue() const noexcept override; + /// + /// + virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept override; + public: /// virtual void wait() const override; diff --git a/src/Backends/DirectX12/src/device.cpp b/src/Backends/DirectX12/src/device.cpp index 65df1b452..0146a9365 100644 --- a/src/Backends/DirectX12/src/device.cpp +++ b/src/Backends/DirectX12/src/device.cpp @@ -325,6 +325,26 @@ const DirectX12Queue& DirectX12Device::bufferQueue() const noexcept return *m_impl->m_bufferQueue; } +MultiSamplingLevel DirectX12Device::maximumMultisamplingLevel(const Format& format) const noexcept +{ + constexpr std::array allLevels = { MultiSamplingLevel::x64, MultiSamplingLevel::x32, MultiSamplingLevel::x16, MultiSamplingLevel::x8, MultiSamplingLevel::x4, MultiSamplingLevel::x2, MultiSamplingLevel::x1 }; + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS levels{ .Format = ::getFormat(format) }; + + for (int level(0); level < allLevels.size(); ++level) + { + levels.SampleCount = static_cast(allLevels[level]); + + if (FAILED(this->handle()->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &levels, sizeof(levels)))) + continue; + + if (levels.NumQualityLevels > 0) + return allLevels[level]; + } + + LITEFX_WARNING(DIRECTX12_LOG, "No supported multi-sampling levels could be queried. Assuming that multi-sampling is disabled."); + return MultiSamplingLevel::x1; +} + void DirectX12Device::wait() const { // NOTE: Currently we are only waiting for the graphics queue here - all other queues may continue their work. diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index 44b9e4304..f8bdaae3e 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -94,16 +94,13 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement(inputLayoutElements.size()); - // Setup multisampling state. - // TODO: Abstract me! - //device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS); + // Setup multi-sampling state. DXGI_SAMPLE_DESC multisamplingState = {}; multisamplingState.Count = 1; - multisamplingState.Quality = 0; + multisamplingState.Quality = DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN; // TODO: Or 0, if sample count = 1. // Setup render target states. // NOTE: We assume, that the targets are returned sorted by location and the location range is contiguous. - // TODO: Add blend parameters to render target. D3D12_BLEND_DESC blendState = {}; D3D12_DEPTH_STENCIL_DESC depthStencilState = {}; auto targets = m_parent->parent().renderTargets(); diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index dfd7b2904..55571e258 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -1473,6 +1473,9 @@ namespace LiteFX::Rendering::Backends { /// virtual const VulkanQueue& bufferQueue() const noexcept override; + /// + virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept override; + public: /// virtual void wait() const override; diff --git a/src/Backends/Vulkan/src/device.cpp b/src/Backends/Vulkan/src/device.cpp index 82b71ece6..7a90e4b14 100644 --- a/src/Backends/Vulkan/src/device.cpp +++ b/src/Backends/Vulkan/src/device.cpp @@ -318,6 +318,34 @@ const VulkanQueue& VulkanDevice::bufferQueue() const noexcept return *m_impl->m_bufferQueue; } +MultiSamplingLevel VulkanDevice::maximumMultisamplingLevel(const Format& format) const noexcept +{ + auto limits = m_impl->m_adapter.getLimits(); + VkSampleCountFlags sampleCounts = limits.framebufferColorSampleCounts; + + if (::hasDepth(format) && ::hasStencil(format)) + sampleCounts = limits.framebufferDepthSampleCounts & limits.framebufferStencilSampleCounts; + else if (::hasDepth(format)) + sampleCounts = limits.framebufferDepthSampleCounts; + else if (::hasStencil(format)) + sampleCounts = limits.framebufferStencilSampleCounts; + + if (sampleCounts & VK_SAMPLE_COUNT_64_BIT) + return MultiSamplingLevel::x64; + else if (sampleCounts & VK_SAMPLE_COUNT_32_BIT) + return MultiSamplingLevel::x32; + else if (sampleCounts & VK_SAMPLE_COUNT_16_BIT) + return MultiSamplingLevel::x16; + else if (sampleCounts & VK_SAMPLE_COUNT_8_BIT) + return MultiSamplingLevel::x8; + else if (sampleCounts & VK_SAMPLE_COUNT_4_BIT) + return MultiSamplingLevel::x4; + else if (sampleCounts & VK_SAMPLE_COUNT_2_BIT) + return MultiSamplingLevel::x2; + else + return MultiSamplingLevel::x1; +} + void VulkanDevice::wait() const { raiseIfFailed(::vkDeviceWaitIdle(this->handle()), "Unable to wait for the device."); diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 09d795681..4da295aa1 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1885,6 +1885,17 @@ namespace LiteFX::Rendering { /// The instance of the queue used for host-device transfers. virtual const TCommandQueue& bufferQueue() const noexcept = 0; + /// + /// Queries the device for the maximum supported number of multi-sampling levels. + /// + /// + /// This method returns the maximum supported multi-sampling level for a certain format. Typically you want to pass a back-buffer format for your swap-chain here. All lower + /// multi-sampling levels are implicitly supported for this format. + /// + /// The target (i.e. back-buffer) format. + /// The maximum multi-sampling level. + virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept = 0; + public: /// /// Waits until the device is idle. From 2b13e094c8598c8721ec868a226a247845c53ecf Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:26:53 +0200 Subject: [PATCH 13/31] Add multi-sampling level to swap chain. --- .../include/litefx/backends/dx12.hpp | 15 +++- src/Backends/DirectX12/src/swapchain.cpp | 74 ++++++++++++------- src/Rendering/include/litefx/rendering.hpp | 15 +++- 3 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 73a1d6472..f44552878 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -1243,8 +1243,9 @@ namespace LiteFX::Rendering::Backends { /// The device that owns the swap chain. /// The initial surface format. /// The initial size of the render area. + /// The target multi sampling level. If it is not supported by the parent device, it will be reduced until a supported level is found. /// The initial number of buffers. - explicit DirectX12SwapChain(const DirectX12Device& device, const Format& surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, const UInt32& buffers = 3); + explicit DirectX12SwapChain(const DirectX12Device& device, const Format& surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, const MultiSamplingLevel& multiSampleLevel = MultiSamplingLevel::x1, const UInt32& buffers = 3); DirectX12SwapChain(const DirectX12SwapChain&) = delete; DirectX12SwapChain(DirectX12SwapChain&&) = delete; virtual ~DirectX12SwapChain() noexcept; @@ -1271,12 +1272,19 @@ namespace LiteFX::Rendering::Backends { /// virtual Array images() const noexcept override; + /// + virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept override; + public: /// virtual Array getSurfaceFormats() const noexcept override; /// - virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) override; + /// + /// Note that changing the number of samples requires the swap chain to be completely re-created. Make sure, you have no references to the swap chain + /// before attempting this operation. + /// + virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) override; /// [[nodiscard]] virtual UInt32 swapBackBuffer() const override; @@ -1464,10 +1472,11 @@ namespace LiteFX::Rendering::Backends { /// The backend from which the device got created. /// The initial surface format, device uses for drawing. /// The initial size of the frame buffers. + /// The target multi-sampling level to initialize the swap-chain with. /// The initial number of frame buffers. /// The size of the global heap for constant buffers, shader resources and images. /// The size of the global heap for samplers. - explicit DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize = 524287, const UInt32& globalSamplerHeapSize = 2048); + explicit DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSamplingLevel, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize = 524287, const UInt32& globalSamplerHeapSize = 2048); DirectX12Device(const DirectX12Device&) = delete; DirectX12Device(DirectX12Device&&) = delete; diff --git a/src/Backends/DirectX12/src/swapchain.cpp b/src/Backends/DirectX12/src/swapchain.cpp index bb24beb36..e2b8e1af9 100644 --- a/src/Backends/DirectX12/src/swapchain.cpp +++ b/src/Backends/DirectX12/src/swapchain.cpp @@ -18,6 +18,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement> m_presentImages{ }; bool m_supportsVariableRefreshRates{ false }; + MultiSamplingLevel m_multiSampleLevel; public: DirectX12SwapChainImpl(DirectX12SwapChain* parent) : @@ -36,9 +37,15 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(allowTearing); } + MultiSamplingLevel findSupportedMultiSamplingLevel(const MultiSamplingLevel& inputLevel, const Format& format) const + { + auto maxLevel = this->m_parent->getDevice()->maximumMultisamplingLevel(format); + return std::min(maxLevel, inputLevel); + } + public: [[nodiscard]] - ComPtr initialize(const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers) + ComPtr initialize(const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSampleLevel, const UInt32& frameBuffers) { if (!std::ranges::any_of(m_parent->getSurfaceFormats(), [&format](const Format& surfaceFormat) { return surfaceFormat == format; })) throw InvalidArgumentException("The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats()); @@ -47,6 +54,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetDevice()->surface().handle(); auto graphicsQueue = m_parent->getDevice()->graphicsQueue().handle(); const auto& backend = m_parent->getDevice()->backend(); + MultiSamplingLevel samples = this->findSupportedMultiSamplingLevel(format); // Create the swap chain. LITEFX_TRACE(DIRECTX12_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px }}...", fmt::ptr(m_parent->getDevice()), frameBuffers, frameBufferSize.width(), frameBufferSize.height()); @@ -56,13 +64,13 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(frameBufferSize.height()); swapChainDesc.Format = ::getFormat(format); swapChainDesc.Stereo = FALSE; - swapChainDesc.SampleDesc = { 1, 0 }; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = std::max(2, frameBuffers); swapChainDesc.Scaling = DXGI_SCALING_STRETCH; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; swapChainDesc.Flags = (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; + swapChainDesc.SampleDesc = samples == MultiSamplingLevel::x1 ? DXGI_SAMPLE_DESC{ 1, 0 } : DXGI_SAMPLE_DESC{ static_cast(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; ComPtr swapChainBase; ComPtr swapChain; @@ -83,36 +91,47 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetSurfaceFormats(), [&format](const Format& surfaceFormat) { return surfaceFormat == format; })) throw InvalidArgumentException("The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats()); + auto samples = this->findSupportedMultiSamplingLevel(multiSampleLevel, format); + // Release all back buffers. m_presentImages.clear(); - // Store a backend reference. - const auto& backend = m_parent->getDevice()->backend(); - - // Resize the buffers. - UInt32 buffers = std::max(2, frameBuffers); - raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); - - // Acquire the swap chain images. - m_presentImages.resize(buffers); - std::ranges::generate(m_presentImages, [this, &frameBufferSize, &format, i = 0]() mutable { - ComPtr resource; - raiseIfFailed(m_parent->handle()->GetBuffer(i++, IID_PPV_ARGS(&resource)), "Unable to acquire image resource from swap chain back buffer {0}.", i); - return makeUnique(m_parent->parent(), std::move(resource), frameBufferSize, format, D3D12_RESOURCE_STATE_PRESENT); - }); - - m_format = format; - m_renderArea = frameBufferSize; - m_buffers = buffers; + if (samples == m_multiSampleLevel) + { + // Store a backend reference. + const auto& backend = m_parent->getDevice()->backend(); + + // Resize the buffers. + UInt32 buffers = std::max(2, frameBuffers); + raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); + + // Acquire the swap chain images. + m_presentImages.resize(buffers); + std::ranges::generate(m_presentImages, [this, &frameBufferSize, &format, i = 0]() mutable { + ComPtr resource; + raiseIfFailed(m_parent->handle()->GetBuffer(i++, IID_PPV_ARGS(&resource)), "Unable to acquire image resource from swap chain back buffer {0}.", i); + return makeUnique(m_parent->parent(), std::move(resource), frameBufferSize, format, D3D12_RESOURCE_STATE_PRESENT); + }); + + m_format = format; + m_renderArea = frameBufferSize; + m_buffers = buffers; + } + else + { + // We need to re-create the swap chain to support more or less samples. + m_parent->handle() = this->initialize(format, frameBufferSize, samples, frameBuffers); + } } UInt32 swapBackBuffer() @@ -138,10 +157,10 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(this)), DirectX12RuntimeObject(device, &device), ComResource(nullptr) { - this->handle() = m_impl->initialize(format, frameBufferSize, frameBuffers); + this->handle() = m_impl->initialize(format, frameBufferSize, multiSampleLevel, frameBuffers); } DirectX12SwapChain::~DirectX12SwapChain() noexcept = default; @@ -171,6 +190,11 @@ Array DirectX12SwapChain::images() const noexcept return m_impl->m_presentImages | std::views::transform([](const UniquePtr& image) { return image.get(); }) | ranges::to>(); } +const MultiSamplingLevel& DirectX12SwapChain::multiSamplingLevel() const noexcept +{ + return m_impl->m_multiSampleLevel; +} + Array DirectX12SwapChain::getSurfaceFormats() const noexcept { // NOTE: Those formats are actually the only ones that are supported for flip-model swap chains, which is currently the only @@ -184,9 +208,9 @@ Array DirectX12SwapChain::getSurfaceFormats() const noexcept }; } -void DirectX12SwapChain::reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) +void DirectX12SwapChain::reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) { - m_impl->reset(surfaceFormat, renderArea, buffers); + m_impl->reset(surfaceFormat, renderArea, multiSampleLevel, buffers); } UInt32 DirectX12SwapChain::swapBackBuffer() const diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 4da295aa1..8d27fc6bd 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1570,6 +1570,17 @@ namespace LiteFX::Rendering { /// Returns an array of the swap chain present images. virtual Array images() const noexcept = 0; + /// + /// Returns the current back buffer multi sampling level. + /// + /// + /// Note that before a swap chain is created or reset, it is checked, if the multi-sampling level is supported by the device for the target back buffer format. If it is not + /// supported, the number of samples will be lowered, until a fitting sample count is found. Thus, the result of this property may not equal the levels you've used to create + /// the swap chain. + /// + /// The current back buffer multi sampling level. + virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept = 0; + public: /// /// Returns an array of supported formats, that can be drawn to the surface. @@ -1591,8 +1602,10 @@ namespace LiteFX::Rendering { /// /// The swap chain image format. /// The dimensions of the frame buffers. + /// The number of back-buffer samples. /// The number of buffers in the swap chain. - virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) = 0; + /// + virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) = 0; /// /// Swaps the front buffer with the next back buffer in order. From bf38c1333a4620276c65393b0d6a5e7ada8d25b1 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:55:24 +0200 Subject: [PATCH 14/31] Implement multi sampling for DirectX 12 back-end. Partially resolves issue #20. --- .../include/litefx/backends/dx12.hpp | 32 +++++------ src/Backends/DirectX12/src/device.cpp | 14 ++--- src/Backends/DirectX12/src/frame_buffer.cpp | 2 +- src/Backends/DirectX12/src/pipeline.cpp | 5 +- src/Backends/DirectX12/src/render_pass.cpp | 46 ++++++++++------ src/Backends/DirectX12/src/swapchain.cpp | 54 ++++++++----------- src/Rendering/include/litefx/rendering.hpp | 18 ++++--- .../include/litefx/rendering_api.hpp | 12 +---- src/Rendering/src/render_target.cpp | 22 +++----- 9 files changed, 99 insertions(+), 106 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index f44552878..1fd7f264d 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -1089,10 +1089,11 @@ namespace LiteFX::Rendering::Backends { /// /// Creates and initializes a new DirectX12 render pass instance. /// - /// - /// - /// - explicit DirectX12RenderPass(const DirectX12Device& device, Span renderTargets, Span inputAttachments = { }); + /// The parent device instance. + /// The render targets that are output by the render pass. + /// The number of samples for the render targets in this render pass. + /// The input attachments that are read by the render pass. + explicit DirectX12RenderPass(const DirectX12Device& device, Span renderTargets, const MultiSamplingLevel& samples = MultiSamplingLevel::x1, Span inputAttachments = { }); DirectX12RenderPass(const DirectX12RenderPass&) = delete; DirectX12RenderPass(DirectX12RenderPass&&) = delete; virtual ~DirectX12RenderPass() noexcept; @@ -1139,6 +1140,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Span inputAttachments() const noexcept override; + /// + virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept override; + public: /// virtual void begin(const UInt32& buffer) override; @@ -1172,7 +1176,7 @@ namespace LiteFX::Rendering::Backends { LITEFX_IMPLEMENTATION(DirectX12RenderPassBuilderImpl) public: - explicit DirectX12RenderPassBuilder(const DirectX12Device& device) noexcept; + explicit DirectX12RenderPassBuilder(const DirectX12Device& device, const MultiSamplingLevel& multiSamplingLevel = MultiSamplingLevel::x1) noexcept; DirectX12RenderPassBuilder(const DirectX12RenderPassBuilder&) noexcept = delete; DirectX12RenderPassBuilder(DirectX12RenderPassBuilder&&) noexcept = delete; virtual ~DirectX12RenderPassBuilder() noexcept; @@ -1181,10 +1185,11 @@ namespace LiteFX::Rendering::Backends { virtual void use(DirectX12InputAttachmentMapping&& inputAttachment) override; public: - virtual DirectX12RenderPassBuilder& renderTarget(const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual DirectX12RenderPassBuilder& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual DirectX12RenderPassBuilder& renderTarget(DirectX12InputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual DirectX12RenderPassBuilder& renderTarget(DirectX12InputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual DirectX12RenderPassBuilder& renderTarget(const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual DirectX12RenderPassBuilder& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual DirectX12RenderPassBuilder& renderTarget(DirectX12InputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual DirectX12RenderPassBuilder& renderTarget(DirectX12InputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual DirectX12RenderPassBuilder& setMultiSamplingLevel(const MultiSamplingLevel& samples = MultiSamplingLevel::x4) override; virtual DirectX12RenderPassBuilder& inputAttachment(const DirectX12InputAttachmentMapping& inputAttachment) override; virtual DirectX12RenderPassBuilder& inputAttachment(const UInt32& inputLocation, const DirectX12RenderPass& renderPass, const UInt32& outputLocation) override; virtual DirectX12RenderPassBuilder& inputAttachment(const UInt32& inputLocation, const DirectX12RenderPass& renderPass, const RenderTarget& renderTarget) override; @@ -1280,11 +1285,7 @@ namespace LiteFX::Rendering::Backends { virtual Array getSurfaceFormats() const noexcept override; /// - /// - /// Note that changing the number of samples requires the swap chain to be completely re-created. Make sure, you have no references to the swap chain - /// before attempting this operation. - /// - virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) override; + virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) override; /// [[nodiscard]] virtual UInt32 swapBackBuffer() const override; @@ -1520,9 +1521,10 @@ namespace LiteFX::Rendering::Backends { /// /// Returns a builder for a . /// + /// The number of samples, the render targets of the render pass should be sampled with. /// An instance of a builder that is used to create a new render pass. /// - DirectX12RenderPassBuilder buildRenderPass() const; + DirectX12RenderPassBuilder buildRenderPass(const MultiSamplingLevel& samples = MultiSamplingLevel::x1) const; /// /// Returns a reference of the swap chain. diff --git a/src/Backends/DirectX12/src/device.cpp b/src/Backends/DirectX12/src/device.cpp index 0146a9365..2b953ba2c 100644 --- a/src/Backends/DirectX12/src/device.cpp +++ b/src/Backends/DirectX12/src/device.cpp @@ -146,9 +146,9 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement { m_factory = makeUnique(*m_parent); } - void createSwapChain(const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers) + void createSwapChain(const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSampleLevel, const UInt32& frameBuffers) { - m_swapChain = makeUnique(*m_parent, format, frameBufferSize, frameBuffers); + m_swapChain = makeUnique(*m_parent, format, frameBufferSize, multiSampleLevel, frameBuffers); } void createQueues() @@ -180,11 +180,11 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement { // ------------------------------------------------------------------------------------------------ DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend) : - DirectX12Device(adapter, surface, backend, Format::B8G8R8A8_SRGB, { 800, 600 }, 3) + DirectX12Device(adapter, surface, backend, Format::B8G8R8A8_SRGB, { 800, 600 }, MultiSamplingLevel::x1, 3) { } -DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize, const UInt32& globalSamplerHeapSize) : +DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSamplingLevel, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize, const UInt32& globalSamplerHeapSize) : ComResource(nullptr), m_impl(makePimpl(this, adapter, surface, backend, globalBufferHeapSize, globalSamplerHeapSize)) { LITEFX_DEBUG(DIRECTX12_LOG, "Creating DirectX 12 device {{ Surface: {0}, Adapter: {1} }}...", fmt::ptr(&surface), adapter.getDeviceId()); @@ -201,7 +201,7 @@ DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const this->handle() = m_impl->initialize(); m_impl->createQueues(); m_impl->createFactory(); - m_impl->createSwapChain(format, frameBufferSize, frameBuffers); + m_impl->createSwapChain(format, frameBufferSize, multiSamplingLevel, frameBuffers); } DirectX12Device::~DirectX12Device() noexcept = default; @@ -280,9 +280,9 @@ void DirectX12Device::bindGlobalDescriptorHeaps(const DirectX12CommandBuffer& co commandBuffer.handle()->SetDescriptorHeaps(globalHeaps.size(), globalHeaps.data()); } -DirectX12RenderPassBuilder DirectX12Device::buildRenderPass() const +DirectX12RenderPassBuilder DirectX12Device::buildRenderPass(const MultiSamplingLevel& samples) const { - return DirectX12RenderPassBuilder(*this); + return DirectX12RenderPassBuilder(*this, samples); } DirectX12SwapChain& DirectX12Device::swapChain() noexcept diff --git a/src/Backends/DirectX12/src/frame_buffer.cpp b/src/Backends/DirectX12/src/frame_buffer.cpp index c95528a8e..0f1d9f757 100644 --- a/src/Backends/DirectX12/src/frame_buffer.cpp +++ b/src/Backends/DirectX12/src/frame_buffer.cpp @@ -74,7 +74,7 @@ class DirectX12FrameBuffer::DirectX12FrameBufferImpl : public ImplementgetDevice()->factory().createAttachment(renderTarget.format(), m_size, renderTarget.samples()); + auto image = m_parent->getDevice()->factory().createAttachment(renderTarget.format(), m_size, m_parent->parent().multiSamplingLevel()); renderTargetView = image.get(); m_outputAttachments.push_back(std::move(image)); } diff --git a/src/Backends/DirectX12/src/pipeline.cpp b/src/Backends/DirectX12/src/pipeline.cpp index f8bdaae3e..2432ac49f 100644 --- a/src/Backends/DirectX12/src/pipeline.cpp +++ b/src/Backends/DirectX12/src/pipeline.cpp @@ -95,9 +95,8 @@ class DirectX12RenderPipeline::DirectX12RenderPipelineImpl : public Implement(inputLayoutElements.size()); // Setup multi-sampling state. - DXGI_SAMPLE_DESC multisamplingState = {}; - multisamplingState.Count = 1; - multisamplingState.Quality = DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN; // TODO: Or 0, if sample count = 1. + auto samples = m_parent->parent().multiSamplingLevel(); + DXGI_SAMPLE_DESC multisamplingState = samples == MultiSamplingLevel::x1 ? DXGI_SAMPLE_DESC{ 1, 0 } : DXGI_SAMPLE_DESC{ static_cast(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; // Setup render target states. // NOTE: We assume, that the targets are returned sorted by location and the location range is contiguous. diff --git a/src/Backends/DirectX12/src/render_pass.cpp b/src/Backends/DirectX12/src/render_pass.cpp index df9a1bea8..eecd555fb 100644 --- a/src/Backends/DirectX12/src/render_pass.cpp +++ b/src/Backends/DirectX12/src/render_pass.cpp @@ -20,10 +20,11 @@ class DirectX12RenderPass::DirectX12RenderPassImpl : public Implement renderTargets, Span inputAttachments) : - base(parent) + DirectX12RenderPassImpl(DirectX12RenderPass* parent, Span renderTargets, const MultiSamplingLevel& samples, Span inputAttachments) : + base(parent), m_multiSamplingLevel(samples) { this->mapRenderTargets(renderTargets); this->mapInputAttachments(inputAttachments); @@ -64,8 +65,8 @@ class DirectX12RenderPass::DirectX12RenderPassImpl : public Implement renderTargets, Span inputAttachments) : - m_impl(makePimpl(this, renderTargets, inputAttachments)), DirectX12RuntimeObject(device, &device) +DirectX12RenderPass::DirectX12RenderPass(const DirectX12Device& device, Span renderTargets, const MultiSamplingLevel& samples, Span inputAttachments) : + m_impl(makePimpl(this, renderTargets, samples, inputAttachments)), DirectX12RuntimeObject(device, &device) { // Initialize the frame buffers. m_impl->m_frameBuffers.resize(this->getDevice()->swapChain().buffers()); @@ -140,6 +141,11 @@ Span DirectX12RenderPass::inputAttachment return m_impl->m_inputAttachments; } +const MultiSamplingLevel& DirectX12RenderPass::multiSamplingLevel() const noexcept +{ + return m_impl->m_multiSamplingLevel; +} + void DirectX12RenderPass::begin(const UInt32& buffer) { // Only begin, if we are currently not running. @@ -305,10 +311,11 @@ class DirectX12RenderPassBuilder::DirectX12RenderPassBuilderImpl : public Implem Array> m_pipelines; Array m_inputAttachments; Array m_renderTargets; + MultiSamplingLevel m_multiSamplingLevel; public: - DirectX12RenderPassBuilderImpl(DirectX12RenderPassBuilder* parent) : - base(parent) + DirectX12RenderPassBuilderImpl(DirectX12RenderPassBuilder* parent, const MultiSamplingLevel& samples) : + base(parent), m_multiSamplingLevel(samples) { } }; @@ -317,8 +324,8 @@ class DirectX12RenderPassBuilder::DirectX12RenderPassBuilderImpl : public Implem // Builder shared interface. // ------------------------------------------------------------------------------------------------ -DirectX12RenderPassBuilder::DirectX12RenderPassBuilder(const DirectX12Device& device) noexcept : - m_impl(makePimpl(this)), RenderPassBuilder(UniquePtr(new DirectX12RenderPass(device))) +DirectX12RenderPassBuilder::DirectX12RenderPassBuilder(const DirectX12Device& device, const MultiSamplingLevel& samples) noexcept : + m_impl(makePimpl(this, samples)), RenderPassBuilder(UniquePtr(new DirectX12RenderPass(device))) { } @@ -329,6 +336,7 @@ UniquePtr DirectX12RenderPassBuilder::go() auto instance = this->instance(); instance->m_impl->mapRenderTargets(m_impl->m_renderTargets); instance->m_impl->mapInputAttachments(m_impl->m_inputAttachments); + instance->m_impl->m_multiSamplingLevel = std::move(m_impl->m_multiSamplingLevel); // Initialize the frame buffers. instance->m_impl->m_frameBuffers.resize(instance->getDevice()->swapChain().buffers()); @@ -347,34 +355,40 @@ void DirectX12RenderPassBuilder::use(DirectX12InputAttachmentMapping&& attachmen m_impl->m_inputAttachments.push_back(std::move(attachment)); } -DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { // TODO: This might be invalid, if another target is already defined with a custom location, however in this case we have no guarantee that the location range will be contiguous // until the render pass is initialized, so we silently ignore this for now. - return this->renderTarget(static_cast(m_impl->m_renderTargets.size()), type, format, samples, clearValues, clear, clearStencil, isVolatile); + return this->renderTarget(static_cast(m_impl->m_renderTargets.size()), type, format, clearValues, clear, clearStencil, isVolatile); } -DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { - m_impl->m_renderTargets.push_back(RenderTarget(location, type, format, clear, clearValues, clearStencil, samples, isVolatile)); + m_impl->m_renderTargets.push_back(RenderTarget(location, type, format, clear, clearValues, clearStencil, isVolatile)); return *this; } -DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(DirectX12InputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(DirectX12InputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { // TODO: This might be invalid, if another target is already defined with a custom location, however in this case we have no guarantee that the location range will be contiguous // until the render pass is initialized, so we silently ignore this for now. - return this->renderTarget(output, static_cast(m_impl->m_renderTargets.size()), type, format, samples, clearValues, clear, clearStencil, isVolatile); + return this->renderTarget(output, static_cast(m_impl->m_renderTargets.size()), type, format, clearValues, clear, clearStencil, isVolatile); } -DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(DirectX12InputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::renderTarget(DirectX12InputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { - auto renderTarget = RenderTarget(location, type, format, clear, clearValues, clearStencil, samples, isVolatile); + auto renderTarget = RenderTarget(location, type, format, clear, clearValues, clearStencil, isVolatile); output = std::move(DirectX12InputAttachmentMapping(*this->instance(), renderTarget, location)); m_impl->m_renderTargets.push_back(renderTarget); return *this; } +DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::setMultiSamplingLevel(const MultiSamplingLevel& samples) +{ + m_impl->m_multiSamplingLevel = samples; + return *this; +} + DirectX12RenderPassBuilder& DirectX12RenderPassBuilder::inputAttachment(const DirectX12InputAttachmentMapping& inputAttachment) { m_impl->m_inputAttachments.push_back(inputAttachment); diff --git a/src/Backends/DirectX12/src/swapchain.cpp b/src/Backends/DirectX12/src/swapchain.cpp index e2b8e1af9..3cf684734 100644 --- a/src/Backends/DirectX12/src/swapchain.cpp +++ b/src/Backends/DirectX12/src/swapchain.cpp @@ -54,7 +54,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetDevice()->surface().handle(); auto graphicsQueue = m_parent->getDevice()->graphicsQueue().handle(); const auto& backend = m_parent->getDevice()->backend(); - MultiSamplingLevel samples = this->findSupportedMultiSamplingLevel(format); + auto samples = this->findSupportedMultiSamplingLevel(multiSampleLevel, format); // Create the swap chain. LITEFX_TRACE(DIRECTX12_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px }}...", fmt::ptr(m_parent->getDevice()), frameBuffers, frameBufferSize.width(), frameBufferSize.height()); @@ -96,42 +96,32 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetSurfaceFormats(), [&format](const Format& surfaceFormat) { return surfaceFormat == format; })) throw InvalidArgumentException("The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats()); - auto samples = this->findSupportedMultiSamplingLevel(multiSampleLevel, format); - // Release all back buffers. m_presentImages.clear(); - if (samples == m_multiSampleLevel) - { - // Store a backend reference. - const auto& backend = m_parent->getDevice()->backend(); - - // Resize the buffers. - UInt32 buffers = std::max(2, frameBuffers); - raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); - - // Acquire the swap chain images. - m_presentImages.resize(buffers); - std::ranges::generate(m_presentImages, [this, &frameBufferSize, &format, i = 0]() mutable { - ComPtr resource; - raiseIfFailed(m_parent->handle()->GetBuffer(i++, IID_PPV_ARGS(&resource)), "Unable to acquire image resource from swap chain back buffer {0}.", i); - return makeUnique(m_parent->parent(), std::move(resource), frameBufferSize, format, D3D12_RESOURCE_STATE_PRESENT); - }); - - m_format = format; - m_renderArea = frameBufferSize; - m_buffers = buffers; - } - else - { - // We need to re-create the swap chain to support more or less samples. - m_parent->handle() = this->initialize(format, frameBufferSize, samples, frameBuffers); - } + // Store a backend reference. + const auto& backend = m_parent->getDevice()->backend(); + + // Resize the buffers. + UInt32 buffers = std::max(2, frameBuffers); + raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); + + // Acquire the swap chain images. + m_presentImages.resize(buffers); + std::ranges::generate(m_presentImages, [this, &frameBufferSize, &format, i = 0]() mutable { + ComPtr resource; + raiseIfFailed(m_parent->handle()->GetBuffer(i++, IID_PPV_ARGS(&resource)), "Unable to acquire image resource from swap chain back buffer {0}.", i); + return makeUnique(m_parent->parent(), std::move(resource), frameBufferSize, format, D3D12_RESOURCE_STATE_PRESENT); + }); + + m_format = format; + m_renderArea = frameBufferSize; + m_buffers = buffers; } UInt32 swapBackBuffer() @@ -208,9 +198,9 @@ Array DirectX12SwapChain::getSurfaceFormats() const noexcept }; } -void DirectX12SwapChain::reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) +void DirectX12SwapChain::reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) { - m_impl->reset(surfaceFormat, renderArea, multiSampleLevel, buffers); + m_impl->reset(surfaceFormat, renderArea, buffers); } UInt32 DirectX12SwapChain::swapBackBuffer() const diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 8d27fc6bd..3d4d0dbb5 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1480,6 +1480,12 @@ namespace LiteFX::Rendering { /// An array of input attachment mappings, that are mapped to the render pass. virtual Span inputAttachments() const noexcept = 0; + /// + /// Returns the number of samples, the render targets are sampled with. + /// + /// The number of samples, the render targets are sampled with. + virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept = 0; + public: /// /// Begins the render pass. @@ -1523,10 +1529,11 @@ namespace LiteFX::Rendering { virtual void use(TInputAttachmentMapping&& inputAttachment) = 0; public: - virtual TDerived& renderTarget(const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; - virtual TDerived& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; - virtual TDerived& renderTarget(TInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; - virtual TDerived& renderTarget(TInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; + virtual TDerived& renderTarget(const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; + virtual TDerived& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; + virtual TDerived& renderTarget(TInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; + virtual TDerived& renderTarget(TInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) = 0; + virtual TDerived& setMultiSamplingLevel(const MultiSamplingLevel& samples = MultiSamplingLevel::x4) = 0; virtual TDerived& inputAttachment(const TInputAttachmentMapping& inputAttachment) = 0; virtual TDerived& inputAttachment(const UInt32& inputLocation, const TRenderPass& renderPass, const UInt32& outputLocation) = 0; virtual TDerived& inputAttachment(const UInt32& inputLocation, const TRenderPass& renderPass, const RenderTarget& renderTarget) = 0; @@ -1602,10 +1609,9 @@ namespace LiteFX::Rendering { /// /// The swap chain image format. /// The dimensions of the frame buffers. - /// The number of back-buffer samples. /// The number of buffers in the swap chain. /// - virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const MultiSamplingLevel& multiSampleLevel, const UInt32& buffers) = 0; + virtual void reset(const Format& surfaceFormat, const Size2d& renderArea, const UInt32& buffers) = 0; /// /// Swaps the front buffer with the next back buffer in order. diff --git a/src/Rendering/include/litefx/rendering_api.hpp b/src/Rendering/include/litefx/rendering_api.hpp index f6b3d5dbc..6a585c732 100644 --- a/src/Rendering/include/litefx/rendering_api.hpp +++ b/src/Rendering/include/litefx/rendering_api.hpp @@ -730,12 +730,6 @@ namespace LiteFX::Rendering { /// The type of the render target. virtual const RenderTargetType& type() const noexcept = 0; - /// - /// Returns the number of samples of the render target when used for multi-sampling. - /// - /// The number of samples of the render target. - virtual const MultiSamplingLevel& samples() const noexcept = 0; - /// /// Returns the internal format of the render target. /// @@ -809,10 +803,9 @@ namespace LiteFX::Rendering { /// true, if the render target should be cleared, when a render pass is started. /// The values with which the render target gets cleared. /// true, if the render target stencil should be cleared, when a render pass is started. - /// The number of samples of the render target when used with multi-sampling. /// true, if the target should not be made persistent for access after the render pass has finished. /// The render target blend state. - explicit RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues = { 0.f , 0.f, 0.f, 0.f }, const bool& clearStencil = true, const MultiSamplingLevel& samples = MultiSamplingLevel::x1, const bool& isVolatile = false, const BlendState& blendState = {}); + explicit RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues = { 0.f , 0.f, 0.f, 0.f }, const bool& clearStencil = true, const bool& isVolatile = false, const BlendState& blendState = {}); RenderTarget(const RenderTarget&) noexcept; RenderTarget(RenderTarget&&) noexcept; virtual ~RenderTarget() noexcept; @@ -828,9 +821,6 @@ namespace LiteFX::Rendering { /// virtual const RenderTargetType& type() const noexcept override; - /// - virtual const MultiSamplingLevel& samples() const noexcept override; - /// virtual const Format& format() const noexcept override; diff --git a/src/Rendering/src/render_target.cpp b/src/Rendering/src/render_target.cpp index 969cd79a2..fab60006b 100644 --- a/src/Rendering/src/render_target.cpp +++ b/src/Rendering/src/render_target.cpp @@ -13,15 +13,14 @@ class RenderTarget::RenderTargetImpl : public Implement { private: RenderTargetType m_type = RenderTargetType::Color; Format m_format = Format::B8G8R8A8_SRGB; - MultiSamplingLevel m_samples = MultiSamplingLevel::x1; bool m_clearBuffer = false, m_clearStencil = false, m_volatile = false; Vector4f m_clearValues; UInt32 m_location; BlendState m_blendState; public: - RenderTargetImpl(RenderTarget* parent, const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile, const BlendState& blendState) : - base(parent), m_location(location), m_type(type), m_format(format), m_clearBuffer(clearBuffer), m_clearValues(clearValues), m_clearStencil(clearStencil), m_samples(samples), m_volatile(isVolatile), m_blendState(blendState) + RenderTargetImpl(RenderTarget* parent, const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const bool& isVolatile, const BlendState& blendState) : + base(parent), m_location(location), m_type(type), m_format(format), m_clearBuffer(clearBuffer), m_clearValues(clearValues), m_clearStencil(clearStencil), m_volatile(isVolatile), m_blendState(blendState) { } }; @@ -31,22 +30,22 @@ class RenderTarget::RenderTargetImpl : public Implement { // ------------------------------------------------------------------------------------------------ RenderTarget::RenderTarget() noexcept : - m_impl(makePimpl(this, 0, RenderTargetType::Color, Format::None, false, Vector4f { 0.f, 0.f, 0.f, 0.f }, false, MultiSamplingLevel::x1, false, BlendState{})) + m_impl(makePimpl(this, 0, RenderTargetType::Color, Format::None, false, Vector4f { 0.f, 0.f, 0.f, 0.f }, false, false, BlendState{})) { } -RenderTarget::RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const MultiSamplingLevel& samples, const bool& isVolatile, const BlendState& blendState) : - m_impl(makePimpl(this, location, type, format, clearBuffer, clearValues, clearStencil, samples, isVolatile, blendState)) +RenderTarget::RenderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const bool& clearBuffer, const Vector4f& clearValues, const bool& clearStencil, const bool& isVolatile, const BlendState& blendState) : + m_impl(makePimpl(this, location, type, format, clearBuffer, clearValues, clearStencil, isVolatile, blendState)) { } RenderTarget::RenderTarget(const RenderTarget& _other) noexcept : - m_impl(makePimpl(this, _other.location(), _other.type(), _other.format(), _other.clearBuffer(), _other.clearValues(), _other.clearStencil(), _other.samples(), _other.isVolatile(), _other.blendState())) + m_impl(makePimpl(this, _other.location(), _other.type(), _other.format(), _other.clearBuffer(), _other.clearValues(), _other.clearStencil(), _other.isVolatile(), _other.blendState())) { } RenderTarget::RenderTarget(RenderTarget&& _other) noexcept : - m_impl(makePimpl(this, std::move(_other.m_impl->m_location), std::move(_other.m_impl->m_type), std::move(_other.m_impl->m_format), std::move(_other.m_impl->m_clearBuffer), std::move(_other.m_impl->m_clearValues), std::move(_other.m_impl->m_clearStencil), std::move(_other.m_impl->m_samples), std::move(_other.m_impl->m_volatile), std::move(_other.m_impl->m_blendState))) + m_impl(makePimpl(this, std::move(_other.m_impl->m_location), std::move(_other.m_impl->m_type), std::move(_other.m_impl->m_format), std::move(_other.m_impl->m_clearBuffer), std::move(_other.m_impl->m_clearValues), std::move(_other.m_impl->m_clearStencil), std::move(_other.m_impl->m_volatile), std::move(_other.m_impl->m_blendState))) { } @@ -60,7 +59,6 @@ RenderTarget& RenderTarget::operator=(const RenderTarget& _other) noexcept m_impl->m_clearBuffer = _other.m_impl->m_clearBuffer; m_impl->m_clearValues = _other.m_impl->m_clearValues; m_impl->m_clearStencil = _other.m_impl->m_clearStencil; - m_impl->m_samples = _other.m_impl->m_samples; m_impl->m_volatile = _other.m_impl->m_volatile; m_impl->m_blendState = _other.m_impl->m_blendState; @@ -75,7 +73,6 @@ RenderTarget& RenderTarget::operator=(RenderTarget&& _other) noexcept m_impl->m_clearBuffer = std::move(_other.m_impl->m_clearBuffer); m_impl->m_clearValues = std::move(_other.m_impl->m_clearValues); m_impl->m_clearStencil = std::move(_other.m_impl->m_clearStencil); - m_impl->m_samples = std::move(_other.m_impl->m_samples); m_impl->m_volatile = std::move(_other.m_impl->m_volatile); m_impl->m_blendState = std::move(_other.m_impl->m_blendState); @@ -92,11 +89,6 @@ const RenderTargetType& RenderTarget::type() const noexcept return m_impl->m_type; } -const MultiSamplingLevel& RenderTarget::samples() const noexcept -{ - return m_impl->m_samples; -} - const Format& RenderTarget::format() const noexcept { return m_impl->m_format; From 0e1b6cdd920d7d5404b56f91edba3b67964f38bb Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:55:36 +0200 Subject: [PATCH 15/31] Update quick start guide, with regards to multi-sampling. --- docs/tutorials/quick-start.markdown | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/quick-start.markdown b/docs/tutorials/quick-start.markdown index 307ef2ed7..06697f424 100644 --- a/docs/tutorials/quick-start.markdown +++ b/docs/tutorials/quick-start.markdown @@ -193,12 +193,12 @@ auto surface = makeUnique(::glfwGetWin32Window(m_window)); With the surface and adapter, we can now proceed to creating our device. Creating a device automatically initializes the *Swap Chain*, which we will talk about in detail later. We can simply create it with a default extent, but it is more efficient to directly tell the swap chain how large the surface is from the beginning. This way, we prevent it from beeing re-created after the window first gets drawn to. In order to do this, we can request the frame buffer size from *GLFW*. Note that the frame buffer size is not always equal to the window size, depending on the monitor. High DPI monitors use a more coarse window coordinate system. You can read about it in more detail [here](https://www.glfw.org/docs/3.3/group__window.html#ga0e2637a4161afb283f5300c7f94785c9). -With the adapter, surface and frame buffer extent, we can go ahead to create our device. We also specify the output format (`Format::B8G8R8A8_SRGB`) and the number of frames, which we concurrently want to draw. This is commonly referred to as *frames in flight*, or *back buffers* throughout the engine, though there is a slight difference. Back buffers refer to the number of frame buffers in the swap chain, whilst frames in flight is a broader concept, that for example influences how many buffers or descriptor sets you want to allocate later. +With the adapter, surface and frame buffer extent, we can go ahead to create our device. We also specify the output format (`Format::B8G8R8A8_SRGB`) and the number of frames, which we concurrently want to draw. This is commonly referred to as *frames in flight*, or *back buffers* throughout the engine, though there is a slight difference. Back buffers refer to the number of frame buffers in the swap chain, whilst frames in flight is a broader concept, that for example influences how many buffers or descriptor sets you want to allocate later. Furthermore, we specify the multi-sampling level for the back-buffer, however we only specify `MultiSamplingLevel::x1`, since we do not want to use multi-sampling yet. ```cxx int width, height; ::glfwGetFramebufferSize(m_window, &width, &height); -m_device = backend->createDevice(*adapter, *surface, Format::B8G8R8A8_SRGB, Size2d(width, height), 3); +m_device = backend->createDevice(*adapter, *surface, Format::B8G8R8A8_SRGB, Size2d(width, height), MultiSamplingLevel::x1, 3); ``` We store the device in a variable `m_device`, which we define as a member variable of `SampleApp`, since we are going to make heavy use of it throughout the whole application. @@ -220,7 +220,6 @@ In our example, however, we do not use multiple render passes and instead only c The other values that are provided to a render target are: - The render target format, which in our example is dictated by the swap chain format we've chosen earlier. -- The multisampling level, which we set to `x1`, since we do not yet want to use multi sampling. - A clear value vector, which contains the values that the render target will be cleared with when starting the render pass. For our *BGRA* image, we want to clear it with black and an alpha value of `0.0`. - A boolean switch to enable or disable clearing the values, which we set to true, since we want to clear our image with the clear values specified earlier. - A boolean switch to enable clearing for stencil buffers. This switch is only used, if the render target is a `DepthStencil` target and the format supports stencil values. It can be used to disable clearing stencil values and only clear depth values for depth/stencil targets. @@ -228,7 +227,7 @@ The other values that are provided to a render target are: ```cxx m_renderPass = m_device->buildRenderPass() - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 0.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, { 0.f, 0.f, 0.f, 0.f }, true, false, false) .go(); ``` From c62052cb1ea654aaa365518ad7c4b8aa48b25ebf Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 21:22:18 +0200 Subject: [PATCH 16/31] Remove multi sampling back buffers in swap chain. This is unsupported and we need to manually resolve multi-sampled resources. --- docs/tutorials/quick-start.markdown | 4 ++-- .../include/litefx/backends/dx12.hpp | 9 ++----- src/Backends/DirectX12/src/device.cpp | 10 ++++---- src/Backends/DirectX12/src/swapchain.cpp | 24 ++++--------------- src/Rendering/include/litefx/rendering.hpp | 11 --------- 5 files changed, 14 insertions(+), 44 deletions(-) diff --git a/docs/tutorials/quick-start.markdown b/docs/tutorials/quick-start.markdown index 06697f424..a9c6993fc 100644 --- a/docs/tutorials/quick-start.markdown +++ b/docs/tutorials/quick-start.markdown @@ -193,12 +193,12 @@ auto surface = makeUnique(::glfwGetWin32Window(m_window)); With the surface and adapter, we can now proceed to creating our device. Creating a device automatically initializes the *Swap Chain*, which we will talk about in detail later. We can simply create it with a default extent, but it is more efficient to directly tell the swap chain how large the surface is from the beginning. This way, we prevent it from beeing re-created after the window first gets drawn to. In order to do this, we can request the frame buffer size from *GLFW*. Note that the frame buffer size is not always equal to the window size, depending on the monitor. High DPI monitors use a more coarse window coordinate system. You can read about it in more detail [here](https://www.glfw.org/docs/3.3/group__window.html#ga0e2637a4161afb283f5300c7f94785c9). -With the adapter, surface and frame buffer extent, we can go ahead to create our device. We also specify the output format (`Format::B8G8R8A8_SRGB`) and the number of frames, which we concurrently want to draw. This is commonly referred to as *frames in flight*, or *back buffers* throughout the engine, though there is a slight difference. Back buffers refer to the number of frame buffers in the swap chain, whilst frames in flight is a broader concept, that for example influences how many buffers or descriptor sets you want to allocate later. Furthermore, we specify the multi-sampling level for the back-buffer, however we only specify `MultiSamplingLevel::x1`, since we do not want to use multi-sampling yet. +With the adapter, surface and frame buffer extent, we can go ahead to create our device. We also specify the output format (`Format::B8G8R8A8_SRGB`) and the number of frames, which we concurrently want to draw. This is commonly referred to as *frames in flight*, or *back buffers* throughout the engine, though there is a slight difference. Back buffers refer to the number of frame buffers in the swap chain, whilst frames in flight is a broader concept, that for example influences how many buffers or descriptor sets you want to allocate later. ```cxx int width, height; ::glfwGetFramebufferSize(m_window, &width, &height); -m_device = backend->createDevice(*adapter, *surface, Format::B8G8R8A8_SRGB, Size2d(width, height), MultiSamplingLevel::x1, 3); +m_device = backend->createDevice(*adapter, *surface, Format::B8G8R8A8_SRGB, Size2d(width, height), 3); ``` We store the device in a variable `m_device`, which we define as a member variable of `SampleApp`, since we are going to make heavy use of it throughout the whole application. diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 1fd7f264d..8e8d45482 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -1248,9 +1248,8 @@ namespace LiteFX::Rendering::Backends { /// The device that owns the swap chain. /// The initial surface format. /// The initial size of the render area. - /// The target multi sampling level. If it is not supported by the parent device, it will be reduced until a supported level is found. /// The initial number of buffers. - explicit DirectX12SwapChain(const DirectX12Device& device, const Format& surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, const MultiSamplingLevel& multiSampleLevel = MultiSamplingLevel::x1, const UInt32& buffers = 3); + explicit DirectX12SwapChain(const DirectX12Device& device, const Format& surfaceFormat = Format::B8G8R8A8_SRGB, const Size2d& renderArea = { 800, 600 }, const UInt32& buffers = 3); DirectX12SwapChain(const DirectX12SwapChain&) = delete; DirectX12SwapChain(DirectX12SwapChain&&) = delete; virtual ~DirectX12SwapChain() noexcept; @@ -1277,9 +1276,6 @@ namespace LiteFX::Rendering::Backends { /// virtual Array images() const noexcept override; - /// - virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept override; - public: /// virtual Array getSurfaceFormats() const noexcept override; @@ -1473,11 +1469,10 @@ namespace LiteFX::Rendering::Backends { /// The backend from which the device got created. /// The initial surface format, device uses for drawing. /// The initial size of the frame buffers. - /// The target multi-sampling level to initialize the swap-chain with. /// The initial number of frame buffers. /// The size of the global heap for constant buffers, shader resources and images. /// The size of the global heap for samplers. - explicit DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSamplingLevel, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize = 524287, const UInt32& globalSamplerHeapSize = 2048); + explicit DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize = 524287, const 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 2b953ba2c..275d38475 100644 --- a/src/Backends/DirectX12/src/device.cpp +++ b/src/Backends/DirectX12/src/device.cpp @@ -146,9 +146,9 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement { m_factory = makeUnique(*m_parent); } - void createSwapChain(const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSampleLevel, const UInt32& frameBuffers) + void createSwapChain(const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers) { - m_swapChain = makeUnique(*m_parent, format, frameBufferSize, multiSampleLevel, frameBuffers); + m_swapChain = makeUnique(*m_parent, format, frameBufferSize, frameBuffers); } void createQueues() @@ -180,11 +180,11 @@ class DirectX12Device::DirectX12DeviceImpl : public Implement { // ------------------------------------------------------------------------------------------------ DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend) : - DirectX12Device(adapter, surface, backend, Format::B8G8R8A8_SRGB, { 800, 600 }, MultiSamplingLevel::x1, 3) + DirectX12Device(adapter, surface, backend, Format::B8G8R8A8_SRGB, { 800, 600 }, 3) { } -DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSamplingLevel, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize, const UInt32& globalSamplerHeapSize) : +DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const DirectX12Surface& surface, const DirectX12Backend& backend, const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers, const UInt32& globalBufferHeapSize, const UInt32& globalSamplerHeapSize) : ComResource(nullptr), m_impl(makePimpl(this, adapter, surface, backend, globalBufferHeapSize, globalSamplerHeapSize)) { LITEFX_DEBUG(DIRECTX12_LOG, "Creating DirectX 12 device {{ Surface: {0}, Adapter: {1} }}...", fmt::ptr(&surface), adapter.getDeviceId()); @@ -201,7 +201,7 @@ DirectX12Device::DirectX12Device(const DirectX12GraphicsAdapter& adapter, const this->handle() = m_impl->initialize(); m_impl->createQueues(); m_impl->createFactory(); - m_impl->createSwapChain(format, frameBufferSize, multiSamplingLevel, frameBuffers); + m_impl->createSwapChain(format, frameBufferSize, frameBuffers); } DirectX12Device::~DirectX12Device() noexcept = default; diff --git a/src/Backends/DirectX12/src/swapchain.cpp b/src/Backends/DirectX12/src/swapchain.cpp index 3cf684734..98149a216 100644 --- a/src/Backends/DirectX12/src/swapchain.cpp +++ b/src/Backends/DirectX12/src/swapchain.cpp @@ -18,7 +18,6 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement> m_presentImages{ }; bool m_supportsVariableRefreshRates{ false }; - MultiSamplingLevel m_multiSampleLevel; public: DirectX12SwapChainImpl(DirectX12SwapChain* parent) : @@ -37,15 +36,9 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(allowTearing); } - MultiSamplingLevel findSupportedMultiSamplingLevel(const MultiSamplingLevel& inputLevel, const Format& format) const - { - auto maxLevel = this->m_parent->getDevice()->maximumMultisamplingLevel(format); - return std::min(maxLevel, inputLevel); - } - public: [[nodiscard]] - ComPtr initialize(const Format& format, const Size2d& frameBufferSize, const MultiSamplingLevel& multiSampleLevel, const UInt32& frameBuffers) + ComPtr initialize(const Format& format, const Size2d& frameBufferSize, const UInt32& frameBuffers) { if (!std::ranges::any_of(m_parent->getSurfaceFormats(), [&format](const Format& surfaceFormat) { return surfaceFormat == format; })) throw InvalidArgumentException("The provided surface format {0} it not a supported. Must be one of the following: {1}.", format, this->joinSupportedSurfaceFormats()); @@ -54,7 +47,6 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public ImplementgetDevice()->surface().handle(); auto graphicsQueue = m_parent->getDevice()->graphicsQueue().handle(); const auto& backend = m_parent->getDevice()->backend(); - auto samples = this->findSupportedMultiSamplingLevel(multiSampleLevel, format); // Create the swap chain. LITEFX_TRACE(DIRECTX12_LOG, "Creating swap chain for device {0} {{ Images: {1}, Extent: {2}x{3} Px }}...", fmt::ptr(m_parent->getDevice()), frameBuffers, frameBufferSize.width(), frameBufferSize.height()); @@ -70,7 +62,7 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; + swapChainDesc.SampleDesc = { 1, 0 }; ComPtr swapChainBase; ComPtr swapChain; @@ -91,7 +83,6 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(2, frameBuffers); - raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), (m_supportsVariableRefreshRates = supportsVariableRefreshRates(backend)) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); + raiseIfFailed(m_parent->handle()->ResizeBuffers(buffers, static_cast(frameBufferSize.width()), static_cast(frameBufferSize.height()), ::getFormat(format), m_supportsVariableRefreshRates ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0), "Unable to resize swap chain back buffers."); // Acquire the swap chain images. m_presentImages.resize(buffers); @@ -147,10 +138,10 @@ class DirectX12SwapChain::DirectX12SwapChainImpl : public Implement(this)), DirectX12RuntimeObject(device, &device), ComResource(nullptr) { - this->handle() = m_impl->initialize(format, frameBufferSize, multiSampleLevel, frameBuffers); + this->handle() = m_impl->initialize(format, frameBufferSize, frameBuffers); } DirectX12SwapChain::~DirectX12SwapChain() noexcept = default; @@ -180,11 +171,6 @@ Array DirectX12SwapChain::images() const noexcept return m_impl->m_presentImages | std::views::transform([](const UniquePtr& image) { return image.get(); }) | ranges::to>(); } -const MultiSamplingLevel& DirectX12SwapChain::multiSamplingLevel() const noexcept -{ - return m_impl->m_multiSampleLevel; -} - Array DirectX12SwapChain::getSurfaceFormats() const noexcept { // NOTE: Those formats are actually the only ones that are supported for flip-model swap chains, which is currently the only diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 3d4d0dbb5..0ededcd07 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1577,17 +1577,6 @@ namespace LiteFX::Rendering { /// Returns an array of the swap chain present images. virtual Array images() const noexcept = 0; - /// - /// Returns the current back buffer multi sampling level. - /// - /// - /// Note that before a swap chain is created or reset, it is checked, if the multi-sampling level is supported by the device for the target back buffer format. If it is not - /// supported, the number of samples will be lowered, until a fitting sample count is found. Thus, the result of this property may not equal the levels you've used to create - /// the swap chain. - /// - /// The current back buffer multi sampling level. - virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept = 0; - public: /// /// Returns an array of supported formats, that can be drawn to the surface. From f67c7017d66e1a05c15af613bbdbd21e139b090d Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 21:22:35 +0200 Subject: [PATCH 17/31] Add multi-sampling sample for DirectX 12. --- src/CMakeLists.txt | 1 + .../DirectX12/BasicRendering/src/sample.cpp | 4 +- .../DirectX12/DeferredShading/src/sample.cpp | 6 +- .../DirectX12/Multisampling/CMakeLists.txt | 83 ++++++ .../shaders/multisampling_ps.hlsl | 23 ++ .../shaders/multisampling_vs.hlsl | 38 +++ .../DirectX12/Multisampling/src/main.cpp | 99 +++++++ .../DirectX12/Multisampling/src/sample.cpp | 269 ++++++++++++++++++ .../DirectX12/Multisampling/src/sample.h | 141 +++++++++ src/Samples/DirectX12/Textures/src/sample.cpp | 2 +- 10 files changed, 660 insertions(+), 6 deletions(-) create mode 100644 src/Samples/DirectX12/Multisampling/CMakeLists.txt create mode 100644 src/Samples/DirectX12/Multisampling/shaders/multisampling_ps.hlsl create mode 100644 src/Samples/DirectX12/Multisampling/shaders/multisampling_vs.hlsl create mode 100644 src/Samples/DirectX12/Multisampling/src/main.cpp create mode 100644 src/Samples/DirectX12/Multisampling/src/sample.cpp create mode 100644 src/Samples/DirectX12/Multisampling/src/sample.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 485499db7..ed60a57ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ IF(BUILD_EXAMPLES AND BUILD_DIRECTX_12_BACKEND) ADD_SUBDIRECTORY(Samples/DirectX12/BasicRendering) ADD_SUBDIRECTORY(Samples/DirectX12/Textures) ADD_SUBDIRECTORY(Samples/DirectX12/DeferredShading) + ADD_SUBDIRECTORY(Samples/DirectX12/Multisampling) ENDIF(BUILD_EXAMPLES AND BUILD_DIRECTX_12_BACKEND) # Install license notice. diff --git a/src/Samples/DirectX12/BasicRendering/src/sample.cpp b/src/Samples/DirectX12/BasicRendering/src/sample.cpp index 4a7f2ad45..85f803fe7 100644 --- a/src/Samples/DirectX12/BasicRendering/src/sample.cpp +++ b/src/Samples/DirectX12/BasicRendering/src/sample.cpp @@ -39,8 +39,8 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_renderPass = m_device->buildRenderPass() - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) - .renderTarget(RenderTargetType::DepthStencil, Format::D32_SFLOAT, MultiSamplingLevel::x1, { 1.f, 0.f, 0.f, 0.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::DepthStencil, Format::D32_SFLOAT, { 1.f, 0.f, 0.f, 0.f }, true, false, false) .go(); } diff --git a/src/Samples/DirectX12/DeferredShading/src/sample.cpp b/src/Samples/DirectX12/DeferredShading/src/sample.cpp index 9e5258264..f159e4576 100644 --- a/src/Samples/DirectX12/DeferredShading/src/sample.cpp +++ b/src/Samples/DirectX12/DeferredShading/src/sample.cpp @@ -50,14 +50,14 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_geometryPass = m_device->buildRenderPass() - .renderTarget(0, RenderTargetType::Color, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) - .renderTarget(1, RenderTargetType::DepthStencil, Format::D32_SFLOAT, MultiSamplingLevel::x1, { 1.f, 0.f, 0.f, 0.f }, true, false, false) + .renderTarget(0, RenderTargetType::Color, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(1, RenderTargetType::DepthStencil, Format::D32_SFLOAT, { 1.f, 0.f, 0.f, 0.f }, true, false, false) .go(); m_lightingPass = m_device->buildRenderPass() .inputAttachment(0, *m_geometryPass, 0) // Color attachment. .inputAttachment(1, *m_geometryPass, 1) // Depth/Stencil attachment. - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 0.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, { 0.f, 0.f, 0.f, 0.f }, true, false, false) .go(); } diff --git a/src/Samples/DirectX12/Multisampling/CMakeLists.txt b/src/Samples/DirectX12/Multisampling/CMakeLists.txt new file mode 100644 index 000000000..2308b76bf --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/CMakeLists.txt @@ -0,0 +1,83 @@ +################################################################################################### +##### ##### +##### LiteFX.Samples.DX12.Multisampling - Contains the multi-sampling sample. ##### +##### ##### +################################################################################################### + +PROJECT(LiteFX.Samples.DX12.Multisampling VERSION ${LITEFX_VERSION} LANGUAGES CXX) +MESSAGE(STATUS "Initializing: ${PROJECT_NAME}...") + +IF(NOT BUILD_DIRECTX_12_BACKEND) + MESSAGE(FATAL_ERROR "This sample requires the DirectX 12 backend. Set the BUILD_DIRECTX_12_BACKEND option to ON and retry.") +ENDIF(NOT BUILD_DIRECTX_12_BACKEND) + +IF(NOT BUILD_WITH_GLM) + MESSAGE(FATAL_ERROR "This sample requires the glm converters for the math module. Set the BUILD_WITH_GLM option to ON and retry.") +ENDIF(NOT BUILD_WITH_GLM) + +# Resolve package dependencies. +FIND_PACKAGE(glfw3 CONFIG REQUIRED) +FIND_PACKAGE(cli11 CONFIG REQUIRED) + +CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") + +# Collect header & source files. +SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS + "src/sample.h" + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" +) + +SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES + "src/main.cpp" + "src/sample.cpp" +) + +# Add shared library project. +ADD_EXECUTABLE(${PROJECT_NAME} + ${SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS} + ${SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES} +) + +# Create source groups for better code organization. +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${DIRECTX_12_BACKEND_HEADERS} ${DIRECTX_12_BACKEND_SOURCES}) + +# Setup project properties. +SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES + FOLDER "Samples/DirectX 12" + VERSION ${LITEFX_VERSION} + SOVERSION ${LITEFX_YEAR} +) + +# Setup target include directories. +TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PRIVATE "src/" "${CMAKE_CURRENT_BINARY_DIR}/src/") + +# Link project dependencies. +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE LiteFX.Core LiteFX.Math LiteFX.AppModel LiteFX.Rendering LiteFX.Backends.DirectX12 glfw CLI11::CLI11) + +# Add shader sources. +ADD_SHADER_MODULE(${PROJECT_NAME}.Shaders.VS SOURCE "shaders/multisampling_vs.hlsl" LANGUAGE HLSL TYPE VERTEX COMPILE_AS DXIL SHADER_MODEL ${BUILD_HLSL_SHADER_MODEL} COMPILER DXC) +ADD_SHADER_MODULE(${PROJECT_NAME}.Shaders.PS SOURCE "shaders/multisampling_ps.hlsl" LANGUAGE HLSL TYPE PIXEL COMPILE_AS DXIL SHADER_MODEL ${BUILD_HLSL_SHADER_MODEL} COMPILER DXC) +SET_TARGET_PROPERTIES(${PROJECT_NAME}.Shaders.VS PROPERTIES FOLDER "Samples/DirectX 12/Shaders") +SET_TARGET_PROPERTIES(${PROJECT_NAME}.Shaders.PS PROPERTIES FOLDER "Samples/DirectX 12/Shaders") + +TARGET_LINK_SHADERS(${PROJECT_NAME} + INSTALL_DESTINATION "${CMAKE_INSTALL_BINARY_DIR}/${SHADER_DEFAULT_SUBDIR}" + SHADERS ${PROJECT_NAME}.Shaders.VS ${PROJECT_NAME}.Shaders.PS +) + +# Re-use pre-compiled core header. +IF(BUILD_PRECOMPILED_HEADERS) + TARGET_PRECOMPILE_HEADERS(${PROJECT_NAME} REUSE_FROM LiteFX.Core) +ENDIF(BUILD_PRECOMPILED_HEADERS) + +# Setup installer. +INSTALL(TARGETS ${PROJECT_NAME} EXPORT LiteFXSamples + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBRARY_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBRARY_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINARY_DIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDE_DIR} +) + +# Export config. +INSTALL(EXPORT LiteFXSamples DESTINATION ${CMAKE_INSTALL_EXPORT_DIR}) +EXPORT(TARGETS ${PROJECT_NAME} FILE LiteFXSamplesDXBasicRenderingConfig.cmake) \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/shaders/multisampling_ps.hlsl b/src/Samples/DirectX12/Multisampling/shaders/multisampling_ps.hlsl new file mode 100644 index 000000000..5040528e3 --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/shaders/multisampling_ps.hlsl @@ -0,0 +1,23 @@ +#pragma pack_matrix(row_major) + +struct VertexData +{ + float4 Position : SV_POSITION; + float4 Color : COLOR; +}; + +struct FragmentData +{ + float4 Color : SV_TARGET; + float Depth : SV_DEPTH; +}; + +FragmentData main(VertexData input) +{ + FragmentData fragment; + + fragment.Depth = input.Position.z; + fragment.Color = input.Color; + + return fragment; +} \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/shaders/multisampling_vs.hlsl b/src/Samples/DirectX12/Multisampling/shaders/multisampling_vs.hlsl new file mode 100644 index 000000000..7bfb3d456 --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/shaders/multisampling_vs.hlsl @@ -0,0 +1,38 @@ +#pragma pack_matrix(row_major) + +struct VertexData +{ + float4 Position : SV_POSITION; + float4 Color : COLOR; +}; + +struct VertexInput +{ + float3 Position : POSITION; + float4 Color : COLOR; +}; + +struct CameraData +{ + float4x4 ViewProjection; +}; + +struct TransformData +{ + float4x4 Model; +}; + +ConstantBuffer camera : register(b0, space0); +ConstantBuffer transform : register(b0, space1); + +VertexData main(in VertexInput input) +{ + VertexData vertex; + + float4 position = mul(float4(input.Position, 1.0), transform.Model); + vertex.Position = mul(position, camera.ViewProjection); + + vertex.Color = input.Color; + + return vertex; +} \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/src/main.cpp b/src/Samples/DirectX12/Multisampling/src/main.cpp new file mode 100644 index 000000000..d017cf857 --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/src/main.cpp @@ -0,0 +1,99 @@ +#include "sample.h" +#include "config.h" + +// CLI11 parses optional values as double by default, which yields an implicit-cast warning. +#pragma warning(disable: 4244) + +#include +#include +#include +#include + +#ifdef BUILD_EXAMPLES_DX12_PIX_LOADER +bool loadPixCapturer() +{ + // Check if Pix has already been loaded. + if (::GetModuleHandleW(L"WinPixGpuCapturer.dll") != 0) + return true; + + // Search for latest version of Pix. + LPWSTR programFilesPath = nullptr; + ::SHGetKnownFolderPath(FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &programFilesPath); + + std::filesystem::path pixInstallationPath = programFilesPath; + pixInstallationPath /= "Microsoft PIX"; + + std::wstring newestVersionFound; + + for (auto const& directory_entry : std::filesystem::directory_iterator(pixInstallationPath)) + if (directory_entry.is_directory()) + if (newestVersionFound.empty() || newestVersionFound < directory_entry.path().filename().c_str()) + newestVersionFound = directory_entry.path().filename().c_str(); + + if (newestVersionFound.empty()) + return false; + + auto pixPath = pixInstallationPath / newestVersionFound / L"WinPixGpuCapturer.dll"; + std::wcout << "Found PIX: " << pixPath.c_str() << std::endl; + ::LoadLibraryW(pixPath.c_str()); + + return true; +} +#endif + +int main(const int argc, const char** argv) +{ + // Parse the command line parameters. + const String appName = SampleApp::name(); + + CLI::App app{ "Demonstrates MSAA anti-aliasing techniques.", appName }; + + Optional adapterId; + app.add_option("-a,--adapter", adapterId)->take_first(); + +#ifdef BUILD_EXAMPLES_DX12_PIX_LOADER + bool loadPix{ false }; + app.add_option("--load-pix", loadPix)->take_first(); +#endif + + try + { + app.parse(argc, argv); + } + catch (const CLI::ParseError& ex) + { + return app.exit(ex); + } + +#ifdef BUILD_EXAMPLES_DX12_PIX_LOADER + if (loadPix && !loadPixCapturer()) + std::cout << "No PIX distribution found. Make sure you have installed PIX for Windows." << std::endl; +#endif + + // Create glfw window. + if (!::glfwInit()) + throw std::runtime_error("Unable to initialize glfw."); + + ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + ::glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + auto window = GlfwWindowPtr(::glfwCreateWindow(800, 600, appName.c_str(), nullptr, nullptr)); + + // Create the app. + try + { + App::build(std::move(window), adapterId) + .logTo(LogLevel::Trace) + .logTo("sample.log", LogLevel::Debug) + .useBackend() + .go(); + } + catch (const LiteFX::Exception& ex) + { + std::cerr << "\033[3;41;37mUnhandled exception: " << ex.what() << "\033[0m" << std::endl; + + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/src/sample.cpp b/src/Samples/DirectX12/Multisampling/src/sample.cpp new file mode 100644 index 000000000..b40e954b6 --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/src/sample.cpp @@ -0,0 +1,269 @@ +#include "sample.h" +#include + +enum DescriptorSets : UInt32 +{ + Constant = 0, // All buffers that are immutable. + PerFrame = 1, // All buffers that are updated each frame. +}; + +enum Pipelines : UInt32 +{ + Basic = 0 // Default render pipeline. +}; + +const Array vertices = +{ + { { -0.5f, -0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } }, + { { -0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 0.5f, -0.5f, -0.5f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } } +}; + +const Array indices = { 0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3 }; + +struct CameraBuffer { + glm::mat4 ViewProjection; +} camera; + +struct TransformBuffer { + glm::mat4 World; +} transform; + +static void onResize(GLFWwindow* window, int width, int height) +{ + auto app = reinterpret_cast(::glfwGetWindowUserPointer(window)); + app->resize(width, height); +} + +void SampleApp::initRenderGraph() +{ + m_renderPass = m_device->buildRenderPass(MultiSamplingLevel::x4) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::DepthStencil, Format::D32_SFLOAT, { 1.f, 0.f, 0.f, 0.f }, true, false, false) + .go(); +} + +void SampleApp::initPipelines() +{ + m_pipeline = m_renderPass->makePipeline(Pipelines::Basic, "Basic") + .withViewport(m_viewport) + .withScissor(m_scissor) + .layout() + .shaderProgram() + .addVertexShaderModule("shaders/basic_vs.dxi") + .addFragmentShaderModule("shaders/basic_ps.dxi") + .go() + .addDescriptorSet(DescriptorSets::Constant, ShaderStage::Vertex | ShaderStage::Fragment) + .addUniform(0, sizeof(CameraBuffer)) + .go() + .addDescriptorSet(DescriptorSets::PerFrame, ShaderStage::Vertex) + .addUniform(0, sizeof(TransformBuffer)) + .go() + .go() + .rasterizer() + .withPolygonMode(PolygonMode::Solid) + .withCullMode(CullMode::BackFaces) + .withCullOrder(CullOrder::ClockWise) + .withLineWidth(1.f) + .go() + .inputAssembler() + .withTopology(PrimitiveTopology::TriangleList) + .withIndexType(IndexType::UInt16) + .addVertexBuffer(sizeof(Vertex), 0) + .addAttribute(BufferFormat::XYZ32F, offsetof(Vertex, Position), AttributeSemantic::Position) + .addAttribute(BufferFormat::XYZW32F, offsetof(Vertex, Color), AttributeSemantic::Color) + .go() + .go() + .go(); + + m_inputAssembler = m_pipeline->inputAssembler(); +} + +void SampleApp::initBuffers() +{ + // Get the pipeline instance. + auto commandBuffer = m_device->bufferQueue().createCommandBuffer(true); + + // Create the staging buffer. + // NOTE: The mapping works, because vertex and index buffers have an alignment of 0, so we can treat the whole buffer as a single element the size of the + // whole buffer. + auto stagedVertices = m_device->factory().createVertexBuffer(m_inputAssembler->vertexBufferLayout(0), BufferUsage::Staging, vertices.size()); + stagedVertices->map(vertices.data(), vertices.size() * sizeof(::Vertex), 0); + + // Create the actual vertex buffer and transfer the staging buffer into it. + m_vertexBuffer = m_device->factory().createVertexBuffer(m_inputAssembler->vertexBufferLayout(0), BufferUsage::Resource, vertices.size()); + m_vertexBuffer->transferFrom(*commandBuffer, *stagedVertices, 0, 0, vertices.size()); + + // Create the staging buffer for the indices. For infos about the mapping see the note about the vertex buffer mapping above. + auto stagedIndices = m_device->factory().createIndexBuffer(m_inputAssembler->indexBufferLayout(), BufferUsage::Staging, indices.size()); + stagedIndices->map(indices.data(), indices.size() * m_inputAssembler->indexBufferLayout().elementSize(), 0); + + // Create the actual index buffer and transfer the staging buffer into it. + m_indexBuffer = m_device->factory().createIndexBuffer(m_inputAssembler->indexBufferLayout(), BufferUsage::Resource, indices.size()); + m_indexBuffer->transferFrom(*commandBuffer, *stagedIndices, 0, 0, indices.size()); + + // Initialize the camera buffer. The camera buffer is constant, so we only need to create one buffer, that can be read from all frames. Since this is a + // write-once/read-multiple scenario, we also transfer the buffer to the more efficient memory heap on the GPU. + auto& cameraBindingLayout = m_pipeline->layout().layout(DescriptorSets::Constant); + m_cameraStagingBuffer = m_device->factory().createConstantBuffer(cameraBindingLayout.layout(0), BufferUsage::Staging, 1); + m_cameraBuffer = m_device->factory().createConstantBuffer(cameraBindingLayout.layout(0), BufferUsage::Resource, 1); + + // Allocate the descriptor set and bind the camera buffer to it. + m_cameraBindings = cameraBindingLayout.allocate(); + m_cameraBindings->update(*m_cameraBuffer, 0); + + // Update the camera. Since the descriptor set already points to the proper buffer, all changes are implicitly visible. + this->updateCamera(*commandBuffer); + + // Next, we create the descriptor sets for the transform buffer. The transform changes with every frame. Since we have three frames in flight, we + // create a buffer with three elements and bind the appropriate element to the descriptor set for every frame. + auto& transformBindingLayout = m_pipeline->layout().layout(DescriptorSets::PerFrame); + m_perFrameBindings = transformBindingLayout.allocate(3); + m_transformBuffer = m_device->factory().createConstantBuffer(transformBindingLayout.layout(0), BufferUsage::Dynamic, 3); + std::ranges::for_each(m_perFrameBindings, [this, i = 0](const UniquePtr& descriptorSet) mutable { descriptorSet->update(*m_transformBuffer, i++); }); + + // End and submit the command buffer. + commandBuffer->end(true, true); +} + +void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) +{ + // Calculate the camera view/projection matrix. + auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); + glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); + camera.ViewProjection = projection * view; + projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. + m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); + m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); +} + +void SampleApp::run() +{ + // Start by creating the surface and selecting the adapter. + auto backend = this->findBackend(BackendType::Rendering); + auto adapter = backend->findAdapter(m_adapterId); + + if (adapter == nullptr) + adapter = backend->findAdapter(std::nullopt); + + auto surface = makeUnique(::glfwGetWin32Window(m_window.get())); + + // Get the proper frame buffer size. + int width, height; + ::glfwGetFramebufferSize(m_window.get(), &width, &height); + + // Create viewport and scissors. + m_viewport = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + m_scissor = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + + // Create the device with the initial frame buffer size and triple buffering. + m_device = backend->createDevice(*adapter, *surface, *backend, Format::B8G8R8A8_UNORM, Size2d(width, height), 3); + + // Initialize resources. + this->initRenderGraph(); + this->initPipelines(); + this->initBuffers(); + + // Run application loop until the window is closed. + while (!::glfwWindowShouldClose(m_window.get())) + { + this->handleEvents(); + this->drawFrame(); + } + + // Shut down the device. + m_device->wait(); + + // Destroy all resources. + m_cameraBindings = nullptr; + m_perFrameBindings.clear(); + m_cameraBuffer = nullptr; + m_cameraStagingBuffer = nullptr; + m_transformBuffer = nullptr; + m_vertexBuffer = nullptr; + m_indexBuffer = nullptr; + + // Destroy the pipeline, render pass and the device. + m_pipeline = nullptr; + m_renderPass = nullptr; + m_device = nullptr; + + // Destroy the window. + ::glfwDestroyWindow(m_window.get()); + ::glfwTerminate(); +} + +void SampleApp::initialize() +{ + ::glfwSetWindowUserPointer(m_window.get(), this); + ::glfwSetFramebufferSizeCallback(m_window.get(), ::onResize); +} + +void SampleApp::resize(int width, int height) +{ + App::resize(width, height); + + if (m_device == nullptr) + return; + + // In order to re-create the swap chain, we need to wait for all frames in flight to finish. + m_device->wait(); + + // Resize the frame buffer and recreate the swap chain. + auto surfaceFormat = m_device->swapChain().surfaceFormat(); + auto renderArea = Size2d(width, height); + m_device->swapChain().reset(surfaceFormat, renderArea, 3); + // NOTE: Important to do this in order, since dependencies (i.e. input attachments) are re-created and might be mapped to images that do no longer exist when a dependency + // gets re-created. This is hard to detect, since some frame buffers can have a constant size, that does not change with the render area and do not need to be + // re-created. We should either think of a clever implicit dependency management for this, or at least document this behavior! + m_renderPass->resizeFrameBuffers(renderArea); + + // Also resize viewport and scissor. + m_viewport->setRectangle(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + m_scissor->setRectangle(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + + // Also update the camera. + auto commandBuffer = m_device->bufferQueue().createCommandBuffer(true); + this->updateCamera(*commandBuffer); + commandBuffer->end(true, true); +} + +void SampleApp::handleEvents() +{ + ::glfwPollEvents(); +} + +void SampleApp::drawFrame() +{ + // Store the initial time this method has been called first. + static auto start = std::chrono::high_resolution_clock::now(); + + // Swap the back buffers for the next frame. + auto backBuffer = m_device->swapChain().swapBackBuffer(); + + // Begin rendering on the render pass and use the only pipeline we've created for it. + m_renderPass->begin(backBuffer); + m_pipeline->use(); + + // Get the amount of time that has passed since the first frame. + auto now = std::chrono::high_resolution_clock::now(); + auto time = std::chrono::duration(now - start).count(); + + // Compute world transform and update the transform buffer. + transform.World = glm::rotate(glm::mat4(1.0f), time * glm::radians(42.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + m_transformBuffer->map(reinterpret_cast(&transform), sizeof(transform), backBuffer); + + // Bind both descriptor sets to the pipeline. + m_pipeline->bind(*m_cameraBindings); + m_pipeline->bind(*m_perFrameBindings[backBuffer]); + + // Bind the vertex and index buffers. + m_pipeline->bind(*m_vertexBuffer); + m_pipeline->bind(*m_indexBuffer); + + // Draw the object and present the frame by ending the render pass. + m_pipeline->drawIndexed(m_indexBuffer->elements()); + m_renderPass->end(); +} \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/src/sample.h b/src/Samples/DirectX12/Multisampling/src/sample.h new file mode 100644 index 000000000..c88c9fa74 --- /dev/null +++ b/src/Samples/DirectX12/Multisampling/src/sample.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include + +#if (defined _WIN32 || defined WINCE) +# define GLFW_EXPOSE_NATIVE_WIN32 +#else +# pragma message ("Basic Rendering Sample: No supported surface platform detected.") +#endif + +#include +#include +#include + +using namespace LiteFX; +using namespace LiteFX::Rendering; +using namespace LiteFX::Rendering::Backends; + +struct GlfwWindowDeleter { + void operator()(GLFWwindow* ptr) noexcept { + ::glfwDestroyWindow(ptr); + } +}; + +typedef UniquePtr GlfwWindowPtr; + +class SampleApp : public LiteFX::App { +public: + static String name() noexcept { return "LiteFX Sample: DirectX 12 Multisampling"; } + String getName() const noexcept override { return name(); } + + static AppVersion version() noexcept { return AppVersion(1, 0, 0, 0); } + AppVersion getVersion() const noexcept override { return version(); } + +private: + /// + /// Stores the GLFW window pointer. + /// + GlfwWindowPtr m_window; + + /// + /// Stores the preferred adapter ID (std::nullopt, if the default adapter is used). + /// + Optional m_adapterId; + + /// + /// Stores the main device instance. + /// + UniquePtr m_device; + + /// + /// Stores the only render pass used in this sample. + /// + UniquePtr m_renderPass; + + /// + /// Stores the only render pipeline used in this sample. + /// + UniquePtr m_pipeline; + + /// + /// Stores a reference of the input assembler state. + /// + SharedPtr m_inputAssembler; + + /// + /// Stores the viewport. + /// + SharedPtr m_viewport; + + /// + /// Stores the scissor. + /// + SharedPtr m_scissor; + + /// + /// Stores the vertex buffer for the quad rendered in this sample. + /// + UniquePtr m_vertexBuffer; + + /// + /// Stores the index buffer for the quad rendered in this sample. + /// + UniquePtr m_indexBuffer; + + /// + /// Stores the buffer that contains the camera information. Since the camera is static, we only need one (immutable) buffer for it, so the buffer will only contain one element. + /// + UniquePtr m_cameraBuffer, m_cameraStagingBuffer; + + /// + /// Stores the buffer that holds the object transform. The buffer will contain three elements, since we have three frames in flight. + /// + UniquePtr m_transformBuffer; + + /// + /// Stores the bindings to the transform buffer. + /// + Array> m_perFrameBindings; + + /// + /// Stores the binding for the camera buffer. + /// + UniquePtr m_cameraBindings; + +public: + SampleApp(GlfwWindowPtr&& window, Optional adapterId) : + App(), m_window(std::move(window)), m_adapterId(adapterId) + { + this->initialize(); + } + +private: + /// + /// Initializes the render pass. + /// + void initRenderGraph(); + + /// + /// Initializes the render pipelines. + /// + void initPipelines(); + + /// + /// Initializes the buffers. + /// + void initBuffers(); + + /// + /// Updates the camera buffer. This needs to be done whenever the frame buffer changes, since we need to pass changes in the aspect ratio to the view/projection matrix. + /// + void updateCamera(const DirectX12CommandBuffer& commandBuffer); + +public: + virtual void run() override; + virtual void initialize() override; + virtual void resize(int width, int height) override; + void handleEvents(); + void drawFrame(); +}; \ No newline at end of file diff --git a/src/Samples/DirectX12/Textures/src/sample.cpp b/src/Samples/DirectX12/Textures/src/sample.cpp index a0a5655a8..c286aaeba 100644 --- a/src/Samples/DirectX12/Textures/src/sample.cpp +++ b/src/Samples/DirectX12/Textures/src/sample.cpp @@ -43,7 +43,7 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_renderPass = m_device->buildRenderPass() - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_SRGB, { 0.f, 0.f, 0.f, 1.f }, true, false, false) .go(); } From 3bf4b80db3db53443112fb44038aaae068aca960 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 21:43:48 +0200 Subject: [PATCH 18/31] Allow multi-sample level changes on a per-render pass basis. --- .../DirectX12/include/litefx/backends/dx12.hpp | 5 ++++- src/Backends/DirectX12/src/device.cpp | 2 +- src/Backends/DirectX12/src/frame_buffer.cpp | 16 ++++++++++++---- src/Backends/DirectX12/src/render_pass.cpp | 9 +++++++++ src/Rendering/include/litefx/rendering.hpp | 14 +++++++++++++- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp index 8e8d45482..cf20c7ae6 100644 --- a/src/Backends/DirectX12/include/litefx/backends/dx12.hpp +++ b/src/Backends/DirectX12/include/litefx/backends/dx12.hpp @@ -1153,6 +1153,9 @@ namespace LiteFX::Rendering::Backends { /// virtual void resizeFrameBuffers(const Size2d& renderArea) override; + /// + virtual void changeMultiSamplingLevel(const MultiSamplingLevel& samples) override; + /// virtual void updateAttachments(const DirectX12DescriptorSet& descriptorSet) const override; @@ -1552,7 +1555,7 @@ namespace LiteFX::Rendering::Backends { /// /// - virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept override; + virtual MultiSamplingLevel maximumMultiSamplingLevel(const Format& format) const noexcept override; public: /// diff --git a/src/Backends/DirectX12/src/device.cpp b/src/Backends/DirectX12/src/device.cpp index 275d38475..e33b37f0e 100644 --- a/src/Backends/DirectX12/src/device.cpp +++ b/src/Backends/DirectX12/src/device.cpp @@ -325,7 +325,7 @@ const DirectX12Queue& DirectX12Device::bufferQueue() const noexcept return *m_impl->m_bufferQueue; } -MultiSamplingLevel DirectX12Device::maximumMultisamplingLevel(const Format& format) const noexcept +MultiSamplingLevel DirectX12Device::maximumMultiSamplingLevel(const Format& format) const noexcept { constexpr std::array allLevels = { MultiSamplingLevel::x64, MultiSamplingLevel::x32, MultiSamplingLevel::x16, MultiSamplingLevel::x8, MultiSamplingLevel::x4, MultiSamplingLevel::x2, MultiSamplingLevel::x1 }; D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS levels{ .Format = ::getFormat(format) }; diff --git a/src/Backends/DirectX12/src/frame_buffer.cpp b/src/Backends/DirectX12/src/frame_buffer.cpp index 0f1d9f757..2abb65344 100644 --- a/src/Backends/DirectX12/src/frame_buffer.cpp +++ b/src/Backends/DirectX12/src/frame_buffer.cpp @@ -61,19 +61,27 @@ class DirectX12FrameBuffer::DirectX12FrameBufferImpl : public Implementparent().renderTargets(), [&, i = 0](const RenderTarget& renderTarget) mutable { if (renderTarget.location() != i++) [[unlikely]] LITEFX_WARNING(DIRECTX12_LOG, "Remapped render target from location {0} to location {1}. Please make sure that the render targets are sorted within the render pass and do not have any gaps in their location mappings.", renderTarget.location(), i - 1); - + + // Check if the device supports the multi sampling level for the render target. + auto samples = m_parent->parent().multiSamplingLevel(); + + if (m_parent->getDevice()->maximumMultiSamplingLevel(renderTarget.format()) < samples) + throw InvalidArgumentException("Render target {0} with format {1} does not support {2} samples.", i, renderTarget.format(), static_cast(samples)); + const IDirectX12Image* renderTargetView; - if (renderTarget.type() == RenderTargetType::Present) + if (renderTarget.type() == RenderTargetType::Present && samples == MultiSamplingLevel::x1) { - // If the render target is a present target, acquire an image view from the swap chain. + // If the render target is a present target and should not be multi-sampled, acquire an image view directly from the swap chain. + // NOTE: Multi-sampling back-buffers directly is not supported (see https://docs.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_effect#remarks). auto swapChainImages = m_parent->getDevice()->swapChain().images(); renderTargetView = swapChainImages[m_bufferIndex]; } else { // Create an image view for the render target. - // TODO: Pass the optimized clear value from the render target to the attachment. (May need to refactor `CreateAttachment` to accept the render target and a size). + // TODO: Pass the optimized clear value from the render target to the attachment. (May need to refactor `CreateAttachment` to accept the render target and a size). Then + // remove the warning from the info queue. auto image = m_parent->getDevice()->factory().createAttachment(renderTarget.format(), m_size, m_parent->parent().multiSamplingLevel()); renderTargetView = image.get(); m_outputAttachments.push_back(std::move(image)); diff --git a/src/Backends/DirectX12/src/render_pass.cpp b/src/Backends/DirectX12/src/render_pass.cpp index eecd555fb..dc64396d5 100644 --- a/src/Backends/DirectX12/src/render_pass.cpp +++ b/src/Backends/DirectX12/src/render_pass.cpp @@ -279,6 +279,15 @@ void DirectX12RenderPass::resizeFrameBuffers(const Size2d& renderArea) std::ranges::for_each(m_impl->m_frameBuffers, [&](UniquePtr& frameBuffer) { frameBuffer->resize(renderArea); }); } +void DirectX12RenderPass::changeMultiSamplingLevel(const const MultiSamplingLevel& samples) +{ + // Check if we're currently running. + if (m_impl->m_activeFrameBuffer != nullptr) + throw RuntimeException("Unable to reset the frame buffers while the render pass is running. End the render pass first."); + + std::ranges::for_each(m_impl->m_frameBuffers, [&](UniquePtr& frameBuffer) { frameBuffer->resize(frameBuffer->size()); }); +} + void DirectX12RenderPass::updateAttachments(const DirectX12DescriptorSet& descriptorSet) const { const auto backBuffer = m_impl->m_backBuffer; diff --git a/src/Rendering/include/litefx/rendering.hpp b/src/Rendering/include/litefx/rendering.hpp index 0ededcd07..60dde00b7 100644 --- a/src/Rendering/include/litefx/rendering.hpp +++ b/src/Rendering/include/litefx/rendering.hpp @@ -1508,6 +1508,18 @@ namespace LiteFX::Rendering { /// The size of the render area, the frame buffers will be resized to. virtual void resizeFrameBuffers(const Size2d& renderArea) = 0; + /// + /// Changes the multi sampling level of the render pass. + /// + /// + /// The method causes the frame buffers to be re-created. It checks, if the are supported by the device for each render target + /// format. If not, an exception will be thrown. To prevent this, call for each render target format on + /// your own, in order to request the maximum number of samples supported. + /// + /// The number of samples per edge pixel. + /// Thrown, if one or more of the render targets have a format, that does not support the provided multi-sampling level. + virtual void changeMultiSamplingLevel(const MultiSamplingLevel& samples) = 0; + /// /// Resolves the input attachments mapped to the render pass and updates them on the descriptor set provided with . /// @@ -1902,7 +1914,7 @@ namespace LiteFX::Rendering { /// /// The target (i.e. back-buffer) format. /// The maximum multi-sampling level. - virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept = 0; + virtual MultiSamplingLevel maximumMultiSamplingLevel(const Format& format) const noexcept = 0; public: /// From d2cb3f5874ee5d842f896a4a719d9ff2c61e9fdb Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 22:05:18 +0200 Subject: [PATCH 19/31] Add support for multi-sampled image resources. --- src/Backends/DirectX12/src/factory.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Backends/DirectX12/src/factory.cpp b/src/Backends/DirectX12/src/factory.cpp index 6f865292e..aee6b1f7b 100644 --- a/src/Backends/DirectX12/src/factory.cpp +++ b/src/Backends/DirectX12/src/factory.cpp @@ -55,8 +55,7 @@ UniquePtr DirectX12GraphicsFactory::createImage(const Format& f resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = levels; resourceDesc.Format = ::getFormat(format); - resourceDesc.SampleDesc.Count = static_cast(samples); - resourceDesc.SampleDesc.Quality = 0; + resourceDesc.SampleDesc = samples == MultiSamplingLevel::x1 ? DXGI_SAMPLE_DESC{ 1, 0 } : DXGI_SAMPLE_DESC{ static_cast(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; // TODO: Check me! @@ -76,8 +75,7 @@ UniquePtr DirectX12GraphicsFactory::createAttachment(const Form resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = 1; resourceDesc.Format = ::getFormat(format); - resourceDesc.SampleDesc.Count = static_cast(samples); - resourceDesc.SampleDesc.Quality = 0; + resourceDesc.SampleDesc = samples == MultiSamplingLevel::x1 ? DXGI_SAMPLE_DESC{ 1, 0 } : DXGI_SAMPLE_DESC{ static_cast(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; D3D12MA::ALLOCATION_DESC allocationDesc = {}; @@ -242,8 +240,7 @@ UniquePtr DirectX12GraphicsFactory::createTexture(const Direc resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = levels; resourceDesc.Format = ::getFormat(format); - resourceDesc.SampleDesc.Count = static_cast(samples); - resourceDesc.SampleDesc.Quality = 0; + resourceDesc.SampleDesc = samples == MultiSamplingLevel::x1 ? DXGI_SAMPLE_DESC{ 1, 0 } : DXGI_SAMPLE_DESC{ static_cast(samples), DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN }; resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; From 096522bb156142bb9a0d251e7d2214dec6aa9294 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Thu, 24 Jun 2021 22:05:50 +0200 Subject: [PATCH 20/31] Properly initialize the DSV/RTV for multi-sampling. --- src/Backends/DirectX12/src/frame_buffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Backends/DirectX12/src/frame_buffer.cpp b/src/Backends/DirectX12/src/frame_buffer.cpp index 2abb65344..d78d08856 100644 --- a/src/Backends/DirectX12/src/frame_buffer.cpp +++ b/src/Backends/DirectX12/src/frame_buffer.cpp @@ -93,7 +93,7 @@ class DirectX12FrameBuffer::DirectX12FrameBufferImpl : public ImplementgetDevice()->handle()->CreateDepthStencilView(renderTargetView->handle().Get(), &depthStencilViewDesc, depthStencilViewDescriptor); depthStencilViewDescriptor = depthStencilViewDescriptor.Offset(m_depthStencilDescriptorSize); @@ -102,7 +102,7 @@ class DirectX12FrameBuffer::DirectX12FrameBufferImpl : public Implement Date: Thu, 24 Jun 2021 22:07:03 +0200 Subject: [PATCH 21/31] Implemented present target resolution. This properly implements multi-sampling for the DirectX 12 backend, described in issue #20. --- src/Backends/DirectX12/src/render_pass.cpp | 43 ++++++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Backends/DirectX12/src/render_pass.cpp b/src/Backends/DirectX12/src/render_pass.cpp index dc64396d5..05fb8be6b 100644 --- a/src/Backends/DirectX12/src/render_pass.cpp +++ b/src/Backends/DirectX12/src/render_pass.cpp @@ -163,7 +163,7 @@ void DirectX12RenderPass::begin(const UInt32& buffer) frameBuffer->commandBuffer().begin(); // Bind the global descriptor heaps. - // TODO: This is done once per render pass, since a frame buffer owns a command list. This may be ineffective and we may want to re-use command lists over multiple render passes. + // TODO: This is done once per render pass, since a frame buffer owns a command list. This may be ineffective and we may want to re-use command lists over multiple render passes (See issue #33). this->getDevice()->bindGlobalDescriptorHeaps(frameBuffer->commandBuffer()); // Declare render pass input and output access and transition barriers. @@ -244,22 +244,41 @@ void DirectX12RenderPass::end() const // End the render pass and the command buffer recording. frameBuffer->commandBuffer().handle()->EndRenderPass(); + // If the present target is multi-sampled, we need to resolve it to the back buffer. + bool requiresResolve = this->hasPresentTarget() && m_impl->m_multiSamplingLevel > MultiSamplingLevel::x1; + // Transition the present and depth/stencil views. Array transitionBarriers = m_impl->m_renderTargets | - std::views::transform([&frameBuffer](const RenderTarget& renderTarget) { - const auto& renderTargetImage = frameBuffer->image(renderTarget.location()); - - switch (renderTarget.type()) - { - default: - case RenderTargetType::Color: return renderTargetImage.transitionTo(D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE); - case RenderTargetType::Present: return renderTargetImage.transitionTo(D3D12_RESOURCE_STATE_PRESENT); - case RenderTargetType::DepthStencil: return renderTargetImage.transitionTo(D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_DEPTH_READ); - } - }) | ranges::to>(); + std::views::transform([&frameBuffer, &requiresResolve](const RenderTarget& renderTarget) { + const auto& renderTargetImage = frameBuffer->image(renderTarget.location()); + + switch (renderTarget.type()) + { + default: + case RenderTargetType::Color: return renderTargetImage.transitionTo(D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE); + case RenderTargetType::DepthStencil: return renderTargetImage.transitionTo(D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_DEPTH_READ); + case RenderTargetType::Present: return renderTargetImage.transitionTo(requiresResolve ? D3D12_RESOURCE_STATE_RESOLVE_SOURCE : D3D12_RESOURCE_STATE_PRESENT); + } + }) | ranges::to>(); + + // Add another barrier for the back buffer image, if required. + const IDirectX12Image* backBufferImage = this->getDevice()->swapChain().images()[m_impl->m_backBuffer]; + + if (requiresResolve) + transitionBarriers.push_back(backBufferImage->transitionTo(D3D12_RESOURCE_STATE_RESOLVE_DEST)); frameBuffer->commandBuffer().handle()->ResourceBarrier(transitionBarriers.size(), transitionBarriers.data()); + // If required, we need to resolve the present target. + if (requiresResolve) + { + const IDirectX12Image* multiSampledImage = &frameBuffer->image(m_impl->m_presentTarget->location()); + frameBuffer->commandBuffer().handle()->ResolveSubresource(backBufferImage->handle().Get(), 0, multiSampledImage->handle().Get(), 0, ::getFormat(m_impl->m_presentTarget->format())); + + // Transition the present target back to the present state. + backBufferImage->transitionTo(frameBuffer->commandBuffer(), D3D12_RESOURCE_STATE_PRESENT); + } + // End the command buffer recording and submit it. frameBuffer->commandBuffer().end(true); From c1cf18dbc367a1643d6613db46ea44ddfe009ecd Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:33:59 +0200 Subject: [PATCH 22/31] Fix several variable names and strings. --- src/Samples/DirectX12/BasicRendering/CMakeLists.txt | 2 +- src/Samples/DirectX12/DeferredShading/CMakeLists.txt | 2 +- src/Samples/DirectX12/Multisampling/CMakeLists.txt | 12 ++++++------ src/Samples/DirectX12/Multisampling/src/sample.h | 2 +- src/Samples/DirectX12/Textures/CMakeLists.txt | 3 +-- src/Samples/Vulkan/BasicRendering/CMakeLists.txt | 2 +- src/Samples/Vulkan/DeferredShading/CMakeLists.txt | 2 +- src/Samples/Vulkan/Textures/CMakeLists.txt | 10 +++++----- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Samples/DirectX12/BasicRendering/CMakeLists.txt b/src/Samples/DirectX12/BasicRendering/CMakeLists.txt index ffb1ae69a..df36a8620 100644 --- a/src/Samples/DirectX12/BasicRendering/CMakeLists.txt +++ b/src/Samples/DirectX12/BasicRendering/CMakeLists.txt @@ -39,7 +39,7 @@ ADD_EXECUTABLE(${PROJECT_NAME} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${DIRECTX_12_BACKEND_HEADERS} ${DIRECTX_12_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS} ${SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES diff --git a/src/Samples/DirectX12/DeferredShading/CMakeLists.txt b/src/Samples/DirectX12/DeferredShading/CMakeLists.txt index e36e9561c..7fbbeb7d6 100644 --- a/src/Samples/DirectX12/DeferredShading/CMakeLists.txt +++ b/src/Samples/DirectX12/DeferredShading/CMakeLists.txt @@ -39,7 +39,7 @@ ADD_EXECUTABLE(${PROJECT_NAME} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${DIRECTX_12_BACKEND_HEADERS} ${DIRECTX_12_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_DIRECTX_12_DEFERRED_SHADING_HEADERS} ${SAMPLE_DIRECTX_12_DEFERRED_SHADING_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES diff --git a/src/Samples/DirectX12/Multisampling/CMakeLists.txt b/src/Samples/DirectX12/Multisampling/CMakeLists.txt index 2308b76bf..e6fd6baa3 100644 --- a/src/Samples/DirectX12/Multisampling/CMakeLists.txt +++ b/src/Samples/DirectX12/Multisampling/CMakeLists.txt @@ -22,24 +22,24 @@ FIND_PACKAGE(cli11 CONFIG REQUIRED) CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") # Collect header & source files. -SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS +SET(SAMPLE_DIRECTX_12_MULTISAMPLING_HEADERS "src/sample.h" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) -SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES +SET(SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES "src/main.cpp" "src/sample.cpp" ) # Add shared library project. ADD_EXECUTABLE(${PROJECT_NAME} - ${SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS} - ${SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES} + ${SAMPLE_DIRECTX_12_MULTISAMPLING_HEADERS} + ${SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${DIRECTX_12_BACKEND_HEADERS} ${DIRECTX_12_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_DIRECTX_12_MULTISAMPLING_HEADERS} ${SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES @@ -80,4 +80,4 @@ INSTALL(TARGETS ${PROJECT_NAME} EXPORT LiteFXSamples # Export config. INSTALL(EXPORT LiteFXSamples DESTINATION ${CMAKE_INSTALL_EXPORT_DIR}) -EXPORT(TARGETS ${PROJECT_NAME} FILE LiteFXSamplesDXBasicRenderingConfig.cmake) \ No newline at end of file +EXPORT(TARGETS ${PROJECT_NAME} FILE LiteFXSamplesDXMultiSamplingConfig.cmake) \ No newline at end of file diff --git a/src/Samples/DirectX12/Multisampling/src/sample.h b/src/Samples/DirectX12/Multisampling/src/sample.h index c88c9fa74..fd3c48029 100644 --- a/src/Samples/DirectX12/Multisampling/src/sample.h +++ b/src/Samples/DirectX12/Multisampling/src/sample.h @@ -6,7 +6,7 @@ #if (defined _WIN32 || defined WINCE) # define GLFW_EXPOSE_NATIVE_WIN32 #else -# pragma message ("Basic Rendering Sample: No supported surface platform detected.") +# pragma message ("Multisampling Sample: No supported surface platform detected.") #endif #include diff --git a/src/Samples/DirectX12/Textures/CMakeLists.txt b/src/Samples/DirectX12/Textures/CMakeLists.txt index a6f7e628d..27b0636a3 100644 --- a/src/Samples/DirectX12/Textures/CMakeLists.txt +++ b/src/Samples/DirectX12/Textures/CMakeLists.txt @@ -36,11 +36,10 @@ SET(SAMPLE_DIRECTX_12_TEXTURES_SOURCES ADD_EXECUTABLE(${PROJECT_NAME} ${SAMPLE_DIRECTX_12_TEXTURES_HEADERS} ${SAMPLE_DIRECTX_12_TEXTURES_SOURCES} - ${SAMPLE_DIRECTX_12_TEXTURES_SHADERS} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${DIRECTX_12_BACKEND_HEADERS} ${DIRECTX_12_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_DIRECTX_12_TEXTURES_HEADERS} ${SAMPLE_DIRECTX_12_TEXTURES_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES diff --git a/src/Samples/Vulkan/BasicRendering/CMakeLists.txt b/src/Samples/Vulkan/BasicRendering/CMakeLists.txt index 6008a3161..34ae1b636 100644 --- a/src/Samples/Vulkan/BasicRendering/CMakeLists.txt +++ b/src/Samples/Vulkan/BasicRendering/CMakeLists.txt @@ -36,7 +36,7 @@ ADD_EXECUTABLE(${PROJECT_NAME} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${VULKAN_BACKEND_HEADERS} ${VULKAN_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_VULKAN_BASIC_RENDERING_HEADERS} ${SAMPLE_VULKAN_BASIC_RENDERING_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES diff --git a/src/Samples/Vulkan/DeferredShading/CMakeLists.txt b/src/Samples/Vulkan/DeferredShading/CMakeLists.txt index 1e6db9977..31589bcd8 100644 --- a/src/Samples/Vulkan/DeferredShading/CMakeLists.txt +++ b/src/Samples/Vulkan/DeferredShading/CMakeLists.txt @@ -36,7 +36,7 @@ ADD_EXECUTABLE(${PROJECT_NAME} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${VULKAN_BACKEND_HEADERS} ${VULKAN_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_DEFERRED_SHADING_HEADERS} ${SAMPLE_DEFERRED_SHADING_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES diff --git a/src/Samples/Vulkan/Textures/CMakeLists.txt b/src/Samples/Vulkan/Textures/CMakeLists.txt index e0b2d1c70..1f76495e9 100644 --- a/src/Samples/Vulkan/Textures/CMakeLists.txt +++ b/src/Samples/Vulkan/Textures/CMakeLists.txt @@ -21,23 +21,23 @@ FIND_PACKAGE(cli11 CONFIG REQUIRED) FIND_PACKAGE(Stb REQUIRED) # Collect header & source files. -SET(SAMPLE_TEXTURES_HEADERS +SET(SAMPLE_VULKAN_TEXTURES_HEADERS "src/sample.h" ) -SET(SAMPLE_TEXTURES_SOURCES +SET(SAMPLE_VULKAN_TEXTURES_SOURCES "src/main.cpp" "src/sample.cpp" ) # Add shared library project. ADD_EXECUTABLE(${PROJECT_NAME} - ${SAMPLE_TEXTURES_HEADERS} - ${SAMPLE_TEXTURES_SOURCES} + ${SAMPLE_VULKAN_TEXTURES_HEADERS} + ${SAMPLE_VULKAN_TEXTURES_SOURCES} ) # Create source groups for better code organization. -SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${VULKAN_BACKEND_HEADERS} ${VULKAN_BACKEND_SOURCES}) +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_VULKAN_TEXTURES_HEADERS} ${SAMPLE_VULKAN_TEXTURES_SOURCES}) # Setup project properties. SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES From eec868e715d2753ce37225cd4631bc1fcd1bb145 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:34:23 +0200 Subject: [PATCH 23/31] Add Vulkan multisampling sample --- src/CMakeLists.txt | 1 + .../Vulkan/Multisampling/CMakeLists.txt | 83 ++++++ .../shaders/multisampling_ps.hlsl | 23 ++ .../shaders/multisampling_vs.hlsl | 38 +++ src/Samples/Vulkan/Multisampling/src/main.cpp | 74 +++++ .../Vulkan/Multisampling/src/sample.cpp | 269 ++++++++++++++++++ src/Samples/Vulkan/Multisampling/src/sample.h | 141 +++++++++ 7 files changed, 629 insertions(+) create mode 100644 src/Samples/Vulkan/Multisampling/CMakeLists.txt create mode 100644 src/Samples/Vulkan/Multisampling/shaders/multisampling_ps.hlsl create mode 100644 src/Samples/Vulkan/Multisampling/shaders/multisampling_vs.hlsl create mode 100644 src/Samples/Vulkan/Multisampling/src/main.cpp create mode 100644 src/Samples/Vulkan/Multisampling/src/sample.cpp create mode 100644 src/Samples/Vulkan/Multisampling/src/sample.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed60a57ba..7a627c7f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,6 +47,7 @@ IF(BUILD_EXAMPLES AND BUILD_VULKAN_BACKEND) ADD_SUBDIRECTORY(Samples/Vulkan/BasicRendering) ADD_SUBDIRECTORY(Samples/Vulkan/Textures) ADD_SUBDIRECTORY(Samples/Vulkan/DeferredShading) + ADD_SUBDIRECTORY(Samples/Vulkan/Multisampling) ENDIF(BUILD_EXAMPLES AND BUILD_VULKAN_BACKEND) IF(BUILD_EXAMPLES AND BUILD_DIRECTX_12_BACKEND) diff --git a/src/Samples/Vulkan/Multisampling/CMakeLists.txt b/src/Samples/Vulkan/Multisampling/CMakeLists.txt new file mode 100644 index 000000000..0242b1a46 --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/CMakeLists.txt @@ -0,0 +1,83 @@ +################################################################################################### +##### ##### +##### LiteFX.Samples.Vulkan.Multisampling - Contains the multi-sampling sample. ##### +##### ##### +################################################################################################### + +PROJECT(LiteFX.Samples.Vulkan.Multisampling VERSION ${LITEFX_VERSION} LANGUAGES CXX) +MESSAGE(STATUS "Initializing: ${PROJECT_NAME}...") + +IF(NOT BUILD_VULKAN_BACKEND) + MESSAGE(FATAL_ERROR "This sample requires the Vulkan backend. Set the BUILD_VULKAN_BACKEND option to ON and retry.") +ENDIF(NOT BUILD_VULKAN_BACKEND) + +IF(NOT BUILD_WITH_GLM) + MESSAGE(FATAL_ERROR "This sample requires the glm converters for the math module. Set the BUILD_WITH_GLM option to ON and retry.") +ENDIF(NOT BUILD_WITH_GLM) + +# Resolve package dependencies. +FIND_PACKAGE(glfw3 CONFIG REQUIRED) +FIND_PACKAGE(cli11 CONFIG REQUIRED) + +CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") + +# Collect header & source files. +SET(SAMPLE_VULKAN_MULTISAMPLING_HEADERS + "src/sample.h" + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" +) + +SET(SAMPLE_VULKAN_MULTISAMPLING_SOURCES + "src/main.cpp" + "src/sample.cpp" +) + +# Add shared library project. +ADD_EXECUTABLE(${PROJECT_NAME} + ${SAMPLE_VULKAN_MULTISAMPLING_HEADERS} + ${SAMPLE_VULKAN_MULTISAMPLING_SOURCES} +) + +# Create source groups for better code organization. +SOURCE_GROUP(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SAMPLE_VULKAN_MULTISAMPLING_HEADERS} ${SAMPLE_VULKAN_MULTISAMPLING_SOURCES}) + +# Setup project properties. +SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES + FOLDER "Samples/Vulkan" + VERSION ${LITEFX_VERSION} + SOVERSION ${LITEFX_YEAR} +) + +# Setup target include directories. +TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PRIVATE "src/" "${CMAKE_CURRENT_BINARY_DIR}/src/") + +# Link project dependencies. +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE LiteFX.Core LiteFX.Math LiteFX.AppModel LiteFX.Rendering LiteFX.Backends.Vulkan glfw CLI11::CLI11) + +# Add shader sources. +ADD_SHADER_MODULE(${PROJECT_NAME}.Shaders.VS SOURCE "shaders/multisampling_vs.hlsl" LANGUAGE HLSL TYPE VERTEX COMPILE_AS SPIRV SHADER_MODEL ${BUILD_HLSL_SHADER_MODEL} COMPILER DXC) +ADD_SHADER_MODULE(${PROJECT_NAME}.Shaders.PS SOURCE "shaders/multisampling_ps.hlsl" LANGUAGE HLSL TYPE PIXEL COMPILE_AS SPIRV SHADER_MODEL ${BUILD_HLSL_SHADER_MODEL} COMPILER DXC) +SET_TARGET_PROPERTIES(${PROJECT_NAME}.Shaders.VS PROPERTIES FOLDER "Samples/Vulkan/Shaders") +SET_TARGET_PROPERTIES(${PROJECT_NAME}.Shaders.PS PROPERTIES FOLDER "Samples/Vulkan/Shaders") + +TARGET_LINK_SHADERS(${PROJECT_NAME} + INSTALL_DESTINATION "${CMAKE_INSTALL_BINARY_DIR}/${SHADER_DEFAULT_SUBDIR}" + SHADERS ${PROJECT_NAME}.Shaders.VS ${PROJECT_NAME}.Shaders.PS +) + +# Re-use pre-compiled core header. +IF(BUILD_PRECOMPILED_HEADERS) + TARGET_PRECOMPILE_HEADERS(${PROJECT_NAME} REUSE_FROM LiteFX.Core) +ENDIF(BUILD_PRECOMPILED_HEADERS) + +# Setup installer. +INSTALL(TARGETS ${PROJECT_NAME} EXPORT LiteFXSamples + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBRARY_DIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBRARY_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINARY_DIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDE_DIR} +) + +# Export config. +INSTALL(EXPORT LiteFXSamples DESTINATION ${CMAKE_INSTALL_EXPORT_DIR}) +EXPORT(TARGETS ${PROJECT_NAME} FILE LiteFXSamplesVulkanMultiSamplingConfig.cmake) \ No newline at end of file diff --git a/src/Samples/Vulkan/Multisampling/shaders/multisampling_ps.hlsl b/src/Samples/Vulkan/Multisampling/shaders/multisampling_ps.hlsl new file mode 100644 index 000000000..5040528e3 --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/shaders/multisampling_ps.hlsl @@ -0,0 +1,23 @@ +#pragma pack_matrix(row_major) + +struct VertexData +{ + float4 Position : SV_POSITION; + float4 Color : COLOR; +}; + +struct FragmentData +{ + float4 Color : SV_TARGET; + float Depth : SV_DEPTH; +}; + +FragmentData main(VertexData input) +{ + FragmentData fragment; + + fragment.Depth = input.Position.z; + fragment.Color = input.Color; + + return fragment; +} \ No newline at end of file diff --git a/src/Samples/Vulkan/Multisampling/shaders/multisampling_vs.hlsl b/src/Samples/Vulkan/Multisampling/shaders/multisampling_vs.hlsl new file mode 100644 index 000000000..7bfb3d456 --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/shaders/multisampling_vs.hlsl @@ -0,0 +1,38 @@ +#pragma pack_matrix(row_major) + +struct VertexData +{ + float4 Position : SV_POSITION; + float4 Color : COLOR; +}; + +struct VertexInput +{ + float3 Position : POSITION; + float4 Color : COLOR; +}; + +struct CameraData +{ + float4x4 ViewProjection; +}; + +struct TransformData +{ + float4x4 Model; +}; + +ConstantBuffer camera : register(b0, space0); +ConstantBuffer transform : register(b0, space1); + +VertexData main(in VertexInput input) +{ + VertexData vertex; + + float4 position = mul(float4(input.Position, 1.0), transform.Model); + vertex.Position = mul(position, camera.ViewProjection); + + vertex.Color = input.Color; + + return vertex; +} \ No newline at end of file diff --git a/src/Samples/Vulkan/Multisampling/src/main.cpp b/src/Samples/Vulkan/Multisampling/src/main.cpp new file mode 100644 index 000000000..97682f9cc --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/src/main.cpp @@ -0,0 +1,74 @@ +#include "sample.h" + +// CLI11 parses optional values as double by default, which yields an implicit-cast warning. +#pragma warning(disable: 4244) + +#include +#include + +int main(const int argc, const char** argv) +{ + // Parse the command line parameters. + const String appName = SampleApp::name(); + + CLI::App app{ "Demonstrates MSAA anti-aliasing techniques.", appName }; + Optional adapterId; + + auto validationLayers = app.add_option("-l,--layers")->take_all(); + app.add_option("-a,--adapter", adapterId)->take_first(); + + try + { + app.parse(argc, argv); + } + catch (const CLI::ParseError& ex) + { + return app.exit(ex); + } + + // Turn the validation layers into a list. + Array enabledLayers; + + if (validationLayers->count() > 0) + for (const auto& result : validationLayers->results()) + enabledLayers.push_back(result); + + // Create glfw window. + if (!::glfwInit()) + throw std::runtime_error("Unable to initialize glfw."); + + ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + ::glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + auto window = GlfwWindowPtr(::glfwCreateWindow(800, 600, appName.c_str(), nullptr, nullptr)); + + // Get the required extensions from glfw. + uint32_t extensions = 0; + const char** extensionNames = ::glfwGetRequiredInstanceExtensions(&extensions); + Array requiredExtensions; + + for (uint32_t i(0); i < extensions; ++i) + requiredExtensions.push_back(String(extensionNames[i])); + +#ifndef NDEBUG + // Use debug output, if in debug mode. + requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +#endif + + // Create the app. + try + { + App::build(std::move(window), adapterId) + .logTo(LogLevel::Trace) + .logTo("sample.log", LogLevel::Debug) + .useBackend(requiredExtensions, enabledLayers) + .go(); + } + catch (const LiteFX::Exception& ex) + { + std::cerr << "\033[3;41;37mUnhandled exception: " << ex.what() << "\033[0m" << std::endl; + + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/Samples/Vulkan/Multisampling/src/sample.cpp b/src/Samples/Vulkan/Multisampling/src/sample.cpp new file mode 100644 index 000000000..b40e954b6 --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/src/sample.cpp @@ -0,0 +1,269 @@ +#include "sample.h" +#include + +enum DescriptorSets : UInt32 +{ + Constant = 0, // All buffers that are immutable. + PerFrame = 1, // All buffers that are updated each frame. +}; + +enum Pipelines : UInt32 +{ + Basic = 0 // Default render pipeline. +}; + +const Array vertices = +{ + { { -0.5f, -0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } }, + { { -0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 0.5f, -0.5f, -0.5f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } } +}; + +const Array indices = { 0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3 }; + +struct CameraBuffer { + glm::mat4 ViewProjection; +} camera; + +struct TransformBuffer { + glm::mat4 World; +} transform; + +static void onResize(GLFWwindow* window, int width, int height) +{ + auto app = reinterpret_cast(::glfwGetWindowUserPointer(window)); + app->resize(width, height); +} + +void SampleApp::initRenderGraph() +{ + m_renderPass = m_device->buildRenderPass(MultiSamplingLevel::x4) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::DepthStencil, Format::D32_SFLOAT, { 1.f, 0.f, 0.f, 0.f }, true, false, false) + .go(); +} + +void SampleApp::initPipelines() +{ + m_pipeline = m_renderPass->makePipeline(Pipelines::Basic, "Basic") + .withViewport(m_viewport) + .withScissor(m_scissor) + .layout() + .shaderProgram() + .addVertexShaderModule("shaders/basic_vs.dxi") + .addFragmentShaderModule("shaders/basic_ps.dxi") + .go() + .addDescriptorSet(DescriptorSets::Constant, ShaderStage::Vertex | ShaderStage::Fragment) + .addUniform(0, sizeof(CameraBuffer)) + .go() + .addDescriptorSet(DescriptorSets::PerFrame, ShaderStage::Vertex) + .addUniform(0, sizeof(TransformBuffer)) + .go() + .go() + .rasterizer() + .withPolygonMode(PolygonMode::Solid) + .withCullMode(CullMode::BackFaces) + .withCullOrder(CullOrder::ClockWise) + .withLineWidth(1.f) + .go() + .inputAssembler() + .withTopology(PrimitiveTopology::TriangleList) + .withIndexType(IndexType::UInt16) + .addVertexBuffer(sizeof(Vertex), 0) + .addAttribute(BufferFormat::XYZ32F, offsetof(Vertex, Position), AttributeSemantic::Position) + .addAttribute(BufferFormat::XYZW32F, offsetof(Vertex, Color), AttributeSemantic::Color) + .go() + .go() + .go(); + + m_inputAssembler = m_pipeline->inputAssembler(); +} + +void SampleApp::initBuffers() +{ + // Get the pipeline instance. + auto commandBuffer = m_device->bufferQueue().createCommandBuffer(true); + + // Create the staging buffer. + // NOTE: The mapping works, because vertex and index buffers have an alignment of 0, so we can treat the whole buffer as a single element the size of the + // whole buffer. + auto stagedVertices = m_device->factory().createVertexBuffer(m_inputAssembler->vertexBufferLayout(0), BufferUsage::Staging, vertices.size()); + stagedVertices->map(vertices.data(), vertices.size() * sizeof(::Vertex), 0); + + // Create the actual vertex buffer and transfer the staging buffer into it. + m_vertexBuffer = m_device->factory().createVertexBuffer(m_inputAssembler->vertexBufferLayout(0), BufferUsage::Resource, vertices.size()); + m_vertexBuffer->transferFrom(*commandBuffer, *stagedVertices, 0, 0, vertices.size()); + + // Create the staging buffer for the indices. For infos about the mapping see the note about the vertex buffer mapping above. + auto stagedIndices = m_device->factory().createIndexBuffer(m_inputAssembler->indexBufferLayout(), BufferUsage::Staging, indices.size()); + stagedIndices->map(indices.data(), indices.size() * m_inputAssembler->indexBufferLayout().elementSize(), 0); + + // Create the actual index buffer and transfer the staging buffer into it. + m_indexBuffer = m_device->factory().createIndexBuffer(m_inputAssembler->indexBufferLayout(), BufferUsage::Resource, indices.size()); + m_indexBuffer->transferFrom(*commandBuffer, *stagedIndices, 0, 0, indices.size()); + + // Initialize the camera buffer. The camera buffer is constant, so we only need to create one buffer, that can be read from all frames. Since this is a + // write-once/read-multiple scenario, we also transfer the buffer to the more efficient memory heap on the GPU. + auto& cameraBindingLayout = m_pipeline->layout().layout(DescriptorSets::Constant); + m_cameraStagingBuffer = m_device->factory().createConstantBuffer(cameraBindingLayout.layout(0), BufferUsage::Staging, 1); + m_cameraBuffer = m_device->factory().createConstantBuffer(cameraBindingLayout.layout(0), BufferUsage::Resource, 1); + + // Allocate the descriptor set and bind the camera buffer to it. + m_cameraBindings = cameraBindingLayout.allocate(); + m_cameraBindings->update(*m_cameraBuffer, 0); + + // Update the camera. Since the descriptor set already points to the proper buffer, all changes are implicitly visible. + this->updateCamera(*commandBuffer); + + // Next, we create the descriptor sets for the transform buffer. The transform changes with every frame. Since we have three frames in flight, we + // create a buffer with three elements and bind the appropriate element to the descriptor set for every frame. + auto& transformBindingLayout = m_pipeline->layout().layout(DescriptorSets::PerFrame); + m_perFrameBindings = transformBindingLayout.allocate(3); + m_transformBuffer = m_device->factory().createConstantBuffer(transformBindingLayout.layout(0), BufferUsage::Dynamic, 3); + std::ranges::for_each(m_perFrameBindings, [this, i = 0](const UniquePtr& descriptorSet) mutable { descriptorSet->update(*m_transformBuffer, i++); }); + + // End and submit the command buffer. + commandBuffer->end(true, true); +} + +void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) +{ + // Calculate the camera view/projection matrix. + auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); + glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); + camera.ViewProjection = projection * view; + projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. + m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); + m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); +} + +void SampleApp::run() +{ + // Start by creating the surface and selecting the adapter. + auto backend = this->findBackend(BackendType::Rendering); + auto adapter = backend->findAdapter(m_adapterId); + + if (adapter == nullptr) + adapter = backend->findAdapter(std::nullopt); + + auto surface = makeUnique(::glfwGetWin32Window(m_window.get())); + + // Get the proper frame buffer size. + int width, height; + ::glfwGetFramebufferSize(m_window.get(), &width, &height); + + // Create viewport and scissors. + m_viewport = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + m_scissor = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + + // Create the device with the initial frame buffer size and triple buffering. + m_device = backend->createDevice(*adapter, *surface, *backend, Format::B8G8R8A8_UNORM, Size2d(width, height), 3); + + // Initialize resources. + this->initRenderGraph(); + this->initPipelines(); + this->initBuffers(); + + // Run application loop until the window is closed. + while (!::glfwWindowShouldClose(m_window.get())) + { + this->handleEvents(); + this->drawFrame(); + } + + // Shut down the device. + m_device->wait(); + + // Destroy all resources. + m_cameraBindings = nullptr; + m_perFrameBindings.clear(); + m_cameraBuffer = nullptr; + m_cameraStagingBuffer = nullptr; + m_transformBuffer = nullptr; + m_vertexBuffer = nullptr; + m_indexBuffer = nullptr; + + // Destroy the pipeline, render pass and the device. + m_pipeline = nullptr; + m_renderPass = nullptr; + m_device = nullptr; + + // Destroy the window. + ::glfwDestroyWindow(m_window.get()); + ::glfwTerminate(); +} + +void SampleApp::initialize() +{ + ::glfwSetWindowUserPointer(m_window.get(), this); + ::glfwSetFramebufferSizeCallback(m_window.get(), ::onResize); +} + +void SampleApp::resize(int width, int height) +{ + App::resize(width, height); + + if (m_device == nullptr) + return; + + // In order to re-create the swap chain, we need to wait for all frames in flight to finish. + m_device->wait(); + + // Resize the frame buffer and recreate the swap chain. + auto surfaceFormat = m_device->swapChain().surfaceFormat(); + auto renderArea = Size2d(width, height); + m_device->swapChain().reset(surfaceFormat, renderArea, 3); + // NOTE: Important to do this in order, since dependencies (i.e. input attachments) are re-created and might be mapped to images that do no longer exist when a dependency + // gets re-created. This is hard to detect, since some frame buffers can have a constant size, that does not change with the render area and do not need to be + // re-created. We should either think of a clever implicit dependency management for this, or at least document this behavior! + m_renderPass->resizeFrameBuffers(renderArea); + + // Also resize viewport and scissor. + m_viewport->setRectangle(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + m_scissor->setRectangle(RectF(0.f, 0.f, static_cast(width), static_cast(height))); + + // Also update the camera. + auto commandBuffer = m_device->bufferQueue().createCommandBuffer(true); + this->updateCamera(*commandBuffer); + commandBuffer->end(true, true); +} + +void SampleApp::handleEvents() +{ + ::glfwPollEvents(); +} + +void SampleApp::drawFrame() +{ + // Store the initial time this method has been called first. + static auto start = std::chrono::high_resolution_clock::now(); + + // Swap the back buffers for the next frame. + auto backBuffer = m_device->swapChain().swapBackBuffer(); + + // Begin rendering on the render pass and use the only pipeline we've created for it. + m_renderPass->begin(backBuffer); + m_pipeline->use(); + + // Get the amount of time that has passed since the first frame. + auto now = std::chrono::high_resolution_clock::now(); + auto time = std::chrono::duration(now - start).count(); + + // Compute world transform and update the transform buffer. + transform.World = glm::rotate(glm::mat4(1.0f), time * glm::radians(42.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + m_transformBuffer->map(reinterpret_cast(&transform), sizeof(transform), backBuffer); + + // Bind both descriptor sets to the pipeline. + m_pipeline->bind(*m_cameraBindings); + m_pipeline->bind(*m_perFrameBindings[backBuffer]); + + // Bind the vertex and index buffers. + m_pipeline->bind(*m_vertexBuffer); + m_pipeline->bind(*m_indexBuffer); + + // Draw the object and present the frame by ending the render pass. + m_pipeline->drawIndexed(m_indexBuffer->elements()); + m_renderPass->end(); +} \ No newline at end of file diff --git a/src/Samples/Vulkan/Multisampling/src/sample.h b/src/Samples/Vulkan/Multisampling/src/sample.h new file mode 100644 index 000000000..e1cde5261 --- /dev/null +++ b/src/Samples/Vulkan/Multisampling/src/sample.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include + +#if (defined _WIN32 || defined WINCE) +# define GLFW_EXPOSE_NATIVE_WIN32 +#else +# pragma message ("Multisampling Sample: No supported surface platform detected.") +#endif + +#include +#include +#include + +using namespace LiteFX; +using namespace LiteFX::Rendering; +using namespace LiteFX::Rendering::Backends; + +struct GlfwWindowDeleter { + void operator()(GLFWwindow* ptr) noexcept { + ::glfwDestroyWindow(ptr); + } +}; + +typedef UniquePtr GlfwWindowPtr; + +class SampleApp : public LiteFX::App { +public: + static String name() noexcept { return "LiteFX Sample: Vulkan Multisampling"; } + String getName() const noexcept override { return name(); } + + static AppVersion version() noexcept { return AppVersion(1, 0, 0, 0); } + AppVersion getVersion() const noexcept override { return version(); } + +private: + /// + /// Stores the GLFW window pointer. + /// + GlfwWindowPtr m_window; + + /// + /// Stores the preferred adapter ID (std::nullopt, if the default adapter is used). + /// + Optional m_adapterId; + + /// + /// Stores the main device instance. + /// + UniquePtr m_device; + + /// + /// Stores the only render pass used in this sample. + /// + UniquePtr m_renderPass; + + /// + /// Stores the only render pipeline used in this sample. + /// + UniquePtr m_pipeline; + + /// + /// Stores a reference of the input assembler state. + /// + SharedPtr m_inputAssembler; + + /// + /// Stores the viewport. + /// + SharedPtr m_viewport; + + /// + /// Stores the scissor. + /// + SharedPtr m_scissor; + + /// + /// Stores the vertex buffer for the quad rendered in this sample. + /// + UniquePtr m_vertexBuffer; + + /// + /// Stores the index buffer for the quad rendered in this sample. + /// + UniquePtr m_indexBuffer; + + /// + /// Stores the buffer that contains the camera information. Since the camera is static, we only need one (immutable) buffer for it, so the buffer will only contain one element. + /// + UniquePtr m_cameraBuffer, m_cameraStagingBuffer; + + /// + /// Stores the buffer that holds the object transform. The buffer will contain three elements, since we have three frames in flight. + /// + UniquePtr m_transformBuffer; + + /// + /// Stores the bindings to the transform buffer. + /// + Array> m_perFrameBindings; + + /// + /// Stores the binding for the camera buffer. + /// + UniquePtr m_cameraBindings; + +public: + SampleApp(GlfwWindowPtr&& window, Optional adapterId) : + App(), m_window(std::move(window)), m_adapterId(adapterId) + { + this->initialize(); + } + +private: + /// + /// Initializes the render pass. + /// + void initRenderGraph(); + + /// + /// Initializes the render pipelines. + /// + void initPipelines(); + + /// + /// Initializes the buffers. + /// + void initBuffers(); + + /// + /// Updates the camera buffer. This needs to be done whenever the frame buffer changes, since we need to pass changes in the aspect ratio to the view/projection matrix. + /// + void updateCamera(const VulkanCommandBuffer& commandBuffer); + +public: + virtual void run() override; + virtual void initialize() override; + virtual void resize(int width, int height) override; + void handleEvents(); + void drawFrame(); +}; \ No newline at end of file From 89091c6ef522f71a0425d682a06104c275368da1 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:53:02 +0200 Subject: [PATCH 24/31] Fix MSAA interface. --- .../Vulkan/BasicRendering/src/sample.cpp | 2 +- .../Vulkan/DeferredShading/src/sample.cpp | 6 +++--- .../Vulkan/Multisampling/CMakeLists.txt | 3 --- .../Vulkan/Multisampling/src/sample.cpp | 18 +++++++++++++----- src/Samples/Vulkan/Textures/src/sample.cpp | 2 +- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Samples/Vulkan/BasicRendering/src/sample.cpp b/src/Samples/Vulkan/BasicRendering/src/sample.cpp index 3513e3cfe..5360822a1 100644 --- a/src/Samples/Vulkan/BasicRendering/src/sample.cpp +++ b/src/Samples/Vulkan/BasicRendering/src/sample.cpp @@ -39,7 +39,7 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_renderPass = m_device->buildRenderPass() - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) .go(); } diff --git a/src/Samples/Vulkan/DeferredShading/src/sample.cpp b/src/Samples/Vulkan/DeferredShading/src/sample.cpp index fce631b86..3e572314f 100644 --- a/src/Samples/Vulkan/DeferredShading/src/sample.cpp +++ b/src/Samples/Vulkan/DeferredShading/src/sample.cpp @@ -50,14 +50,14 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_geometryPass = m_device->buildRenderPass() - .renderTarget(0, RenderTargetType::Color, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) - .renderTarget(1, RenderTargetType::DepthStencil, Format::D24_UNORM_S8_UINT, MultiSamplingLevel::x1, { 1.f, 0.f, 0.f, 0.f }, true, true, false) + .renderTarget(0, RenderTargetType::Color, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(1, RenderTargetType::DepthStencil, Format::D24_UNORM_S8_UINT, { 1.f, 0.f, 0.f, 0.f }, true, true, false) .go(); m_lightingPass = m_device->buildRenderPass() .inputAttachment(0, *m_geometryPass, 0) // Color attachment. .inputAttachment(1, *m_geometryPass, 1) // Depth/Stencil attachment. - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) .go(); } diff --git a/src/Samples/Vulkan/Multisampling/CMakeLists.txt b/src/Samples/Vulkan/Multisampling/CMakeLists.txt index 0242b1a46..56239d4ba 100644 --- a/src/Samples/Vulkan/Multisampling/CMakeLists.txt +++ b/src/Samples/Vulkan/Multisampling/CMakeLists.txt @@ -19,12 +19,9 @@ ENDIF(NOT BUILD_WITH_GLM) FIND_PACKAGE(glfw3 CONFIG REQUIRED) FIND_PACKAGE(cli11 CONFIG REQUIRED) -CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") - # Collect header & source files. SET(SAMPLE_VULKAN_MULTISAMPLING_HEADERS "src/sample.h" - "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) SET(SAMPLE_VULKAN_MULTISAMPLING_SOURCES diff --git a/src/Samples/Vulkan/Multisampling/src/sample.cpp b/src/Samples/Vulkan/Multisampling/src/sample.cpp index b40e954b6..3b7bb7d9f 100644 --- a/src/Samples/Vulkan/Multisampling/src/sample.cpp +++ b/src/Samples/Vulkan/Multisampling/src/sample.cpp @@ -121,13 +121,13 @@ void SampleApp::initBuffers() auto& transformBindingLayout = m_pipeline->layout().layout(DescriptorSets::PerFrame); m_perFrameBindings = transformBindingLayout.allocate(3); m_transformBuffer = m_device->factory().createConstantBuffer(transformBindingLayout.layout(0), BufferUsage::Dynamic, 3); - std::ranges::for_each(m_perFrameBindings, [this, i = 0](const UniquePtr& descriptorSet) mutable { descriptorSet->update(*m_transformBuffer, i++); }); + std::ranges::for_each(m_perFrameBindings, [this, i = 0](const UniquePtr& descriptorSet) mutable { descriptorSet->update(*m_transformBuffer, i++); }); // End and submit the command buffer. commandBuffer->end(true, true); } -void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) +void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) { // Calculate the camera view/projection matrix. auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); @@ -141,14 +141,22 @@ void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) void SampleApp::run() { + // Store the window handle. + auto window = m_window.get(); + // Start by creating the surface and selecting the adapter. - auto backend = this->findBackend(BackendType::Rendering); + auto backend = this->findBackend(BackendType::Rendering); auto adapter = backend->findAdapter(m_adapterId); if (adapter == nullptr) adapter = backend->findAdapter(std::nullopt); - auto surface = makeUnique(::glfwGetWin32Window(m_window.get())); + auto surface = backend->createSurface([&window](const VkInstance& instance) { + VkSurfaceKHR surface; + raiseIfFailed(::glfwCreateWindowSurface(instance, window, nullptr, &surface), "Unable to create GLFW window surface."); + + return surface; + }); // Get the proper frame buffer size. int width, height; @@ -159,7 +167,7 @@ void SampleApp::run() m_scissor = makeShared(RectF(0.f, 0.f, static_cast(width), static_cast(height))); // Create the device with the initial frame buffer size and triple buffering. - m_device = backend->createDevice(*adapter, *surface, *backend, Format::B8G8R8A8_UNORM, Size2d(width, height), 3); + m_device = backend->createDevice(*adapter, *surface, Format::B8G8R8A8_UNORM, Size2d(width, height), 3); // Initialize resources. this->initRenderGraph(); diff --git a/src/Samples/Vulkan/Textures/src/sample.cpp b/src/Samples/Vulkan/Textures/src/sample.cpp index e2671aa16..0c4e69131 100644 --- a/src/Samples/Vulkan/Textures/src/sample.cpp +++ b/src/Samples/Vulkan/Textures/src/sample.cpp @@ -42,7 +42,7 @@ static void onResize(GLFWwindow* window, int width, int height) void SampleApp::initRenderGraph() { m_renderPass = m_device->buildRenderPass() - .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, MultiSamplingLevel::x1, { 0.f, 0.f, 0.f, 1.f }, true, false, false) + .renderTarget(RenderTargetType::Present, Format::B8G8R8A8_UNORM, { 0.f, 0.f, 0.f, 1.f }, true, false, false) .go(); } From be62beec3a2d76c39eda6744599b33cc620aff09 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:53:18 +0200 Subject: [PATCH 25/31] Properly store samples. --- src/Backends/DirectX12/src/render_pass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Backends/DirectX12/src/render_pass.cpp b/src/Backends/DirectX12/src/render_pass.cpp index 05fb8be6b..32da5f253 100644 --- a/src/Backends/DirectX12/src/render_pass.cpp +++ b/src/Backends/DirectX12/src/render_pass.cpp @@ -304,6 +304,7 @@ void DirectX12RenderPass::changeMultiSamplingLevel(const const MultiSamplingLeve if (m_impl->m_activeFrameBuffer != nullptr) throw RuntimeException("Unable to reset the frame buffers while the render pass is running. End the render pass first."); + m_impl->m_multiSamplingLevel = samples; std::ranges::for_each(m_impl->m_frameBuffers, [&](UniquePtr& frameBuffer) { frameBuffer->resize(frameBuffer->size()); }); } From d146748887940306f9ff98ea89d9b2d9dfe0bf13 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:53:33 +0200 Subject: [PATCH 26/31] Implement multi sampling properties. --- .../Vulkan/include/litefx/backends/vulkan.hpp | 31 ++++++---- src/Backends/Vulkan/src/device.cpp | 6 +- src/Backends/Vulkan/src/frame_buffer.cpp | 6 +- src/Backends/Vulkan/src/render_pass.cpp | 60 +++++++++++++------ 4 files changed, 69 insertions(+), 34 deletions(-) diff --git a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp index 55571e258..74fa59aa9 100644 --- a/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp +++ b/src/Backends/Vulkan/include/litefx/backends/vulkan.hpp @@ -1087,10 +1087,11 @@ namespace LiteFX::Rendering::Backends { /// /// Creates and initializes a new Vulkan render pass instance. /// - /// - /// - /// - explicit VulkanRenderPass(const VulkanDevice& device, Span renderTargets, Span inputAttachments = { }); + /// The parent device instance. + /// The render targets that are output by the render pass. + /// The number of samples for the render targets in this render pass. + /// The input attachments that are read by the render pass. + explicit VulkanRenderPass(const VulkanDevice& device, Span renderTargets, const MultiSamplingLevel& samples = MultiSamplingLevel::x1, Span inputAttachments = { }); VulkanRenderPass(const VulkanRenderPass&) = delete; VulkanRenderPass(VulkanRenderPass&&) = delete; virtual ~VulkanRenderPass() noexcept; @@ -1137,6 +1138,9 @@ namespace LiteFX::Rendering::Backends { /// virtual Span inputAttachments() const noexcept override; + /// + virtual const MultiSamplingLevel& multiSamplingLevel() const noexcept override; + public: /// virtual void begin(const UInt32& buffer) override; @@ -1147,6 +1151,9 @@ namespace LiteFX::Rendering::Backends { /// virtual void resizeFrameBuffers(const Size2d& renderArea) override; + /// + virtual void changeMultiSamplingLevel(const MultiSamplingLevel& samples) override; + /// virtual void updateAttachments(const VulkanDescriptorSet& descriptorSet) const override; @@ -1170,7 +1177,7 @@ namespace LiteFX::Rendering::Backends { LITEFX_IMPLEMENTATION(VulkanRenderPassBuilderImpl) public: - explicit VulkanRenderPassBuilder(const VulkanDevice& device) noexcept; + explicit VulkanRenderPassBuilder(const VulkanDevice& device, const MultiSamplingLevel& samples = MultiSamplingLevel::x1) noexcept; VulkanRenderPassBuilder(const VulkanRenderPassBuilder&) noexcept = delete; VulkanRenderPassBuilder(VulkanRenderPassBuilder&&) noexcept = delete; virtual ~VulkanRenderPassBuilder() noexcept; @@ -1179,10 +1186,11 @@ namespace LiteFX::Rendering::Backends { virtual void use(VulkanInputAttachmentMapping&& inputAttachment) override; public: - virtual VulkanRenderPassBuilder& renderTarget(const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual VulkanRenderPassBuilder& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual VulkanRenderPassBuilder& renderTarget(VulkanInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; - virtual VulkanRenderPassBuilder& renderTarget(VulkanInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual VulkanRenderPassBuilder& renderTarget(const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual VulkanRenderPassBuilder& renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual VulkanRenderPassBuilder& renderTarget(VulkanInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual VulkanRenderPassBuilder& renderTarget(VulkanInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues = { 0.0f, 0.0f, 0.0f, 0.0f }, bool clearColor = true, bool clearStencil = true, bool isVolatile = false) override; + virtual VulkanRenderPassBuilder& setMultiSamplingLevel(const MultiSamplingLevel& samples = MultiSamplingLevel::x4) override; virtual VulkanRenderPassBuilder& inputAttachment(const VulkanInputAttachmentMapping& inputAttachment) override; virtual VulkanRenderPassBuilder& inputAttachment(const UInt32& inputLocation, const VulkanRenderPass& renderPass, const UInt32& outputLocation) override; virtual VulkanRenderPassBuilder& inputAttachment(const UInt32& inputLocation, const VulkanRenderPass& renderPass, const RenderTarget& renderTarget) override; @@ -1446,9 +1454,10 @@ namespace LiteFX::Rendering::Backends { /// /// Returns a builder for a . /// + /// The number of samples, the render targets of the render pass should be sampled with. /// An instance of a builder that is used to create a new render pass. /// - VulkanRenderPassBuilder buildRenderPass() const; + VulkanRenderPassBuilder buildRenderPass(const MultiSamplingLevel& samples = MultiSamplingLevel::x1) const; // IGraphicsDevice interface. public: @@ -1474,7 +1483,7 @@ namespace LiteFX::Rendering::Backends { virtual const VulkanQueue& bufferQueue() const noexcept override; /// - virtual MultiSamplingLevel maximumMultisamplingLevel(const Format& format) const noexcept override; + virtual MultiSamplingLevel maximumMultiSamplingLevel(const Format& format) const noexcept override; public: /// diff --git a/src/Backends/Vulkan/src/device.cpp b/src/Backends/Vulkan/src/device.cpp index 7a90e4b14..ae94f53b0 100644 --- a/src/Backends/Vulkan/src/device.cpp +++ b/src/Backends/Vulkan/src/device.cpp @@ -278,9 +278,9 @@ VulkanSwapChain& VulkanDevice::swapChain() noexcept return *m_impl->m_swapChain; } -VulkanRenderPassBuilder VulkanDevice::buildRenderPass() const +VulkanRenderPassBuilder VulkanDevice::buildRenderPass(const MultiSamplingLevel& samples) const { - return VulkanRenderPassBuilder(*this); + return VulkanRenderPassBuilder(*this, samples); } const VulkanSwapChain& VulkanDevice::swapChain() const noexcept @@ -318,7 +318,7 @@ const VulkanQueue& VulkanDevice::bufferQueue() const noexcept return *m_impl->m_bufferQueue; } -MultiSamplingLevel VulkanDevice::maximumMultisamplingLevel(const Format& format) const noexcept +MultiSamplingLevel VulkanDevice::maximumMultiSamplingLevel(const Format& format) const noexcept { auto limits = m_impl->m_adapter.getLimits(); VkSampleCountFlags sampleCounts = limits.framebufferColorSampleCounts; diff --git a/src/Backends/Vulkan/src/frame_buffer.cpp b/src/Backends/Vulkan/src/frame_buffer.cpp index edf0e147d..bab3f36d3 100644 --- a/src/Backends/Vulkan/src/frame_buffer.cpp +++ b/src/Backends/Vulkan/src/frame_buffer.cpp @@ -64,7 +64,9 @@ class VulkanFrameBuffer::VulkanFrameBufferImpl : public Implementparent().multiSamplingLevel(); + + if (renderTarget.type() == RenderTargetType::Present && samples == MultiSamplingLevel::x1) { // If the render target is a present target, acquire an image view from the swap chain. auto swapChainImages = m_parent->getDevice()->swapChain().images(); @@ -75,7 +77,7 @@ class VulkanFrameBuffer::VulkanFrameBufferImpl : public ImplementgetDevice()->factory().createAttachment(renderTarget.format(), m_size, renderTarget.samples()); + auto image = m_parent->getDevice()->factory().createAttachment(renderTarget.format(), m_size, samples); attachmentViews.push_back(image->imageView()); m_renderTargetViews.push_back(image.get()); m_outputAttachments.push_back(std::move(image)); diff --git a/src/Backends/Vulkan/src/render_pass.cpp b/src/Backends/Vulkan/src/render_pass.cpp index dbcbca013..733b7dd15 100644 --- a/src/Backends/Vulkan/src/render_pass.cpp +++ b/src/Backends/Vulkan/src/render_pass.cpp @@ -19,10 +19,11 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement m_clearValues; UInt32 m_backBuffer{ 0 }; + MultiSamplingLevel m_samples; public: - VulkanRenderPassImpl(VulkanRenderPass* parent, Span renderTargets, Span inputAttachments) : - base(parent) + VulkanRenderPassImpl(VulkanRenderPass* parent, Span renderTargets, const MultiSamplingLevel& samples, Span inputAttachments) : + base(parent), m_samples(samples) { this->mapRenderTargets(renderTargets); this->mapInputAttachments(inputAttachments); @@ -66,7 +67,7 @@ class VulkanRenderPass::VulkanRenderPassImpl : public ImplementmultiSamplingLevel()); attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -118,7 +119,7 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement renderTargets, Span inputAttachments) : - m_impl(makePimpl(this, renderTargets, inputAttachments)), VulkanRuntimeObject(device, &device), Resource(VK_NULL_HANDLE) +VulkanRenderPass::VulkanRenderPass(const VulkanDevice& device, Span renderTargets, const MultiSamplingLevel& samples, Span inputAttachments) : + m_impl(makePimpl(this, renderTargets, samples, inputAttachments)), VulkanRuntimeObject(device, &device), Resource(VK_NULL_HANDLE) { this->handle() = m_impl->initialize(); @@ -292,6 +293,11 @@ Span VulkanRenderPass::inputAttachments() co return m_impl->m_inputAttachments; } +const MultiSamplingLevel& VulkanRenderPass::multiSamplingLevel() const noexcept +{ + return m_impl->m_samples; +} + void VulkanRenderPass::begin(const UInt32& buffer) { // Only begin, if we are currently not running. @@ -372,6 +378,16 @@ void VulkanRenderPass::resizeFrameBuffers(const Size2d& renderArea) std::ranges::for_each(m_impl->m_frameBuffers, [&](UniquePtr& frameBuffer) { frameBuffer->resize(renderArea); }); } +void VulkanRenderPass::changeMultiSamplingLevel(const MultiSamplingLevel& samples) +{ + // Check if we're currently running. + if (m_impl->m_activeFrameBuffer != nullptr) + throw RuntimeException("Unable to reset the frame buffers while the render pass is running. End the render pass first."); + + m_impl->m_samples = samples; + std::ranges::for_each(m_impl->m_frameBuffers, [&](UniquePtr& frameBuffer) { frameBuffer->resize(frameBuffer->size()); }); +} + void VulkanRenderPass::updateAttachments(const VulkanDescriptorSet& descriptorSet) const { const auto backBuffer = m_impl->m_backBuffer; @@ -404,10 +420,11 @@ class VulkanRenderPassBuilder::VulkanRenderPassBuilderImpl : public Implement> m_pipelines; Array m_inputAttachments; Array m_renderTargets; + MultiSamplingLevel m_samples; public: - VulkanRenderPassBuilderImpl(VulkanRenderPassBuilder* parent) : - base(parent) + VulkanRenderPassBuilderImpl(VulkanRenderPassBuilder* parent, const MultiSamplingLevel& samples) : + base(parent), m_samples(samples) { } }; @@ -416,8 +433,8 @@ class VulkanRenderPassBuilder::VulkanRenderPassBuilderImpl : public Implement(this)), RenderPassBuilder(UniquePtr(new VulkanRenderPass(device))) +VulkanRenderPassBuilder::VulkanRenderPassBuilder(const VulkanDevice& device, const MultiSamplingLevel& samples) noexcept : + m_impl(makePimpl(this, samples)), RenderPassBuilder(UniquePtr(new VulkanRenderPass(device))) { } @@ -428,6 +445,7 @@ UniquePtr VulkanRenderPassBuilder::go() auto instance = this->instance(); instance->m_impl->mapRenderTargets(m_impl->m_renderTargets); instance->m_impl->mapInputAttachments(m_impl->m_inputAttachments); + instance->m_impl->m_samples = std::move(m_impl->m_samples); instance->handle() = instance->m_impl->initialize(); // Initialize the frame buffers. @@ -447,34 +465,40 @@ void VulkanRenderPassBuilder::use(VulkanInputAttachmentMapping&& attachment) m_impl->m_inputAttachments.push_back(std::move(attachment)); } -VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { // TODO: This might be invalid, if another target is already defined with a custom location, however in this case we have no guarantee that the location range will be contiguous // until the render pass is initialized, so we silently ignore this for now. - return this->renderTarget(static_cast(m_impl->m_renderTargets.size()), type, format, samples, clearValues, clear, clearStencil, isVolatile); + return this->renderTarget(static_cast(m_impl->m_renderTargets.size()), type, format, clearValues, clear, clearStencil, isVolatile); } -VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { - m_impl->m_renderTargets.push_back(RenderTarget(location, type, format, clear, clearValues, clearStencil, samples, isVolatile)); + m_impl->m_renderTargets.push_back(RenderTarget(location, type, format, clear, clearValues, clearStencil, isVolatile)); return *this; } -VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(VulkanInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(VulkanInputAttachmentMapping& output, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { // TODO: This might be invalid, if another target is already defined with a custom location, however in this case we have no guarantee that the location range will be contiguous // until the render pass is initialized, so we silently ignore this for now. - return this->renderTarget(output, static_cast(m_impl->m_renderTargets.size()), type, format, samples, clearValues, clear, clearStencil, isVolatile); + return this->renderTarget(output, static_cast(m_impl->m_renderTargets.size()), type, format, clearValues, clear, clearStencil, isVolatile); } -VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(VulkanInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const MultiSamplingLevel& samples, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) +VulkanRenderPassBuilder& VulkanRenderPassBuilder::renderTarget(VulkanInputAttachmentMapping& output, const UInt32& location, const RenderTargetType& type, const Format& format, const Vector4f& clearValues, bool clear, bool clearStencil, bool isVolatile) { - auto renderTarget = RenderTarget(location, type, format, clear, clearValues, clearStencil, samples, isVolatile); + auto renderTarget = RenderTarget(location, type, format, clear, clearValues, clearStencil, isVolatile); output = std::move(VulkanInputAttachmentMapping(*this->instance(), renderTarget, location)); m_impl->m_renderTargets.push_back(renderTarget); return *this; } +VulkanRenderPassBuilder& VulkanRenderPassBuilder::setMultiSamplingLevel(const MultiSamplingLevel& samples) +{ + m_impl->m_samples = samples; + return *this; +} + VulkanRenderPassBuilder& VulkanRenderPassBuilder::inputAttachment(const VulkanInputAttachmentMapping& inputAttachment) { m_impl->m_inputAttachments.push_back(inputAttachment); From 09be5b254cf1b86e9de619c063f7e0dc7ed310d1 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Sat, 26 Jun 2021 13:07:04 +0200 Subject: [PATCH 27/31] Implemented multi sampling for Vulkan. Fixes issue #20. --- docs/release-logs/0.2.1.md | 3 +- src/Backends/Vulkan/src/frame_buffer.cpp | 13 +++++-- src/Backends/Vulkan/src/pipeline.cpp | 3 +- src/Backends/Vulkan/src/render_pass.cpp | 35 +++++++++++++++++-- .../Vulkan/Multisampling/src/sample.cpp | 6 ++-- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/docs/release-logs/0.2.1.md b/docs/release-logs/0.2.1.md index cd3b28a71..f168a2053 100644 --- a/docs/release-logs/0.2.1.md +++ b/docs/release-logs/0.2.1.md @@ -4,4 +4,5 @@ - Introduces rasterizer depth/stencil state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) - Adds support for multisampling. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) - Introduces render target blend states ([See PR #36](https://github.com/crud89/LiteFX/pull/36)). -- Vulkan 🌋: line width is part of the dynamic rasterizer state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) \ No newline at end of file +- Vulkan 🌋: line width is part of the dynamic rasterizer state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) +- Vulkan 🌋: Fixed validation errors about invalid attachment layout for depth/stencil only render targets. \ No newline at end of file diff --git a/src/Backends/Vulkan/src/frame_buffer.cpp b/src/Backends/Vulkan/src/frame_buffer.cpp index bab3f36d3..66ec65463 100644 --- a/src/Backends/Vulkan/src/frame_buffer.cpp +++ b/src/Backends/Vulkan/src/frame_buffer.cpp @@ -60,12 +60,12 @@ class VulkanFrameBuffer::VulkanFrameBufferImpl : public Implementparent().multiSamplingLevel(); + std::ranges::for_each(m_parent->parent().renderTargets(), [&, i = 0](const RenderTarget& renderTarget) mutable { if (renderTarget.location() != i++) [[unlikely]] LITEFX_WARNING(VULKAN_LOG, "Remapped render target from location {0} to location {1}. Please make sure that the render targets are sorted within the render pass and do not have any gaps in their location mappings.", renderTarget.location(), i - 1); - auto samples = m_parent->parent().multiSamplingLevel(); - if (renderTarget.type() == RenderTargetType::Present && samples == MultiSamplingLevel::x1) { // If the render target is a present target, acquire an image view from the swap chain. @@ -84,6 +84,15 @@ class VulkanFrameBuffer::VulkanFrameBufferImpl : public Implement MultiSamplingLevel::x1 && std::ranges::any_of(m_parent->parent().renderTargets(), [](const RenderTarget& renderTarget) { return renderTarget.type() == RenderTargetType::Present; })) + { + auto swapChainImages = m_parent->getDevice()->swapChain().images(); + auto image = swapChainImages[m_bufferIndex]; + m_renderTargetViews.push_back(image); + attachmentViews.push_back(image->imageView()); + } + // Allocate the frame buffer. VkFramebufferCreateInfo frameBufferInfo{}; frameBufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; diff --git a/src/Backends/Vulkan/src/pipeline.cpp b/src/Backends/Vulkan/src/pipeline.cpp index 004f0a2d5..b289ada49 100644 --- a/src/Backends/Vulkan/src/pipeline.cpp +++ b/src/Backends/Vulkan/src/pipeline.cpp @@ -138,11 +138,10 @@ class VulkanRenderPipeline::VulkanRenderPipelineImpl : public Implement(dynamicStates.size()); // Setup multisampling state. - // TODO: Abstract me! VkPipelineMultisampleStateCreateInfo multisampling = {}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.rasterizationSamples = ::getSamples(m_parent->parent().multiSamplingLevel()); multisampling.minSampleShading = 1.0f; multisampling.pSampleMask = nullptr; multisampling.alphaToCoverageEnable = m_alphaToCoverage; diff --git a/src/Backends/Vulkan/src/render_pass.cpp b/src/Backends/Vulkan/src/render_pass.cpp index 733b7dd15..3ffc438be 100644 --- a/src/Backends/Vulkan/src/render_pass.cpp +++ b/src/Backends/Vulkan/src/render_pass.cpp @@ -57,6 +57,7 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement inputAttachments; Array outputAttachments; // Contains all output attachments, except the depth/stencil target. Optional depthTarget, presentTarget; + Optional presentResolveAttachment; // Map input attachments. std::ranges::for_each(m_inputAttachments, [&, i = 0](const VulkanInputAttachmentMapping& inputAttachment) mutable { @@ -139,7 +140,7 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement(currentIndex + inputAttachments.size()), attachment.finalLayout }); break; case RenderTargetType::DepthStencil: - if (::hasDepth(renderTarget.format()) && ::hasStencil(renderTarget.format())) [[likely]] + if (::hasDepth(renderTarget.format()) || ::hasStencil(renderTarget.format())) [[likely]] attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; else if (::hasDepth(renderTarget.format())) attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; @@ -152,11 +153,29 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement(currentIndex + inputAttachments.size()), attachment.finalLayout }; + depthTarget = VkAttachmentReference{ static_cast(currentIndex + inputAttachments.size()), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; break; case RenderTargetType::Present: attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + // If we have a multi-sampled present attachment, we also need to attach a resolve attachment for it. + if (m_samples == MultiSamplingLevel::x1) + attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + else + { + attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + presentResolveAttachment = VkAttachmentDescription{}; + presentResolveAttachment->format = attachment.format; + presentResolveAttachment->samples = VK_SAMPLE_COUNT_1_BIT; + presentResolveAttachment->loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + presentResolveAttachment->storeOp = VK_ATTACHMENT_STORE_OP_STORE; + presentResolveAttachment->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + presentResolveAttachment->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + presentResolveAttachment->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + presentResolveAttachment->finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + } + presentTarget = VkAttachmentReference { static_cast(currentIndex + inputAttachments.size()), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; outputAttachments.push_back(presentTarget.value()); break; @@ -174,6 +193,16 @@ class VulkanRenderPass::VulkanRenderPassImpl : public Implement(inputAttachments.size()); subPass.pInputAttachments = inputAttachments.data(); + subPass.pResolveAttachments = nullptr; + + // Add the resolve attachment. + VkAttachmentReference presentResolveReference = { static_cast(attachments.size()), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + + if (presentResolveAttachment.has_value()) + { + subPass.pResolveAttachments = &presentResolveReference; + attachments.push_back(presentResolveAttachment.value()); + } // Define an external sub-pass dependency, if there are input attachments to synchronize with. Array dependencies; diff --git a/src/Samples/Vulkan/Multisampling/src/sample.cpp b/src/Samples/Vulkan/Multisampling/src/sample.cpp index 3b7bb7d9f..3c43b6adf 100644 --- a/src/Samples/Vulkan/Multisampling/src/sample.cpp +++ b/src/Samples/Vulkan/Multisampling/src/sample.cpp @@ -51,8 +51,8 @@ void SampleApp::initPipelines() .withScissor(m_scissor) .layout() .shaderProgram() - .addVertexShaderModule("shaders/basic_vs.dxi") - .addFragmentShaderModule("shaders/basic_ps.dxi") + .addVertexShaderModule("shaders/multisampling_vs.spv") + .addFragmentShaderModule("shaders/multisampling_ps.spv") .go() .addDescriptorSet(DescriptorSets::Constant, ShaderStage::Vertex | ShaderStage::Fragment) .addUniform(0, sizeof(CameraBuffer)) @@ -133,8 +133,8 @@ void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); - camera.ViewProjection = projection * view; projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. + camera.ViewProjection = projection * view; m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); } From dc72b572fc9dc3555fd68bf1e89f90c9116944b9 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Sat, 26 Jun 2021 13:15:33 +0200 Subject: [PATCH 28/31] Fix coordinate clipping. --- src/Samples/DirectX12/Multisampling/src/sample.cpp | 1 - src/Samples/Vulkan/BasicRendering/src/sample.cpp | 1 - src/Samples/Vulkan/DeferredShading/src/sample.cpp | 11 +++++------ src/Samples/Vulkan/Multisampling/src/sample.cpp | 2 +- src/Samples/Vulkan/Textures/src/sample.cpp | 1 - src/cmake/Shaders.cmake | 4 ++++ 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Samples/DirectX12/Multisampling/src/sample.cpp b/src/Samples/DirectX12/Multisampling/src/sample.cpp index b40e954b6..5db365b2d 100644 --- a/src/Samples/DirectX12/Multisampling/src/sample.cpp +++ b/src/Samples/DirectX12/Multisampling/src/sample.cpp @@ -134,7 +134,6 @@ void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); camera.ViewProjection = projection * view; - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); } diff --git a/src/Samples/Vulkan/BasicRendering/src/sample.cpp b/src/Samples/Vulkan/BasicRendering/src/sample.cpp index 5360822a1..5a14a4e6b 100644 --- a/src/Samples/Vulkan/BasicRendering/src/sample.cpp +++ b/src/Samples/Vulkan/BasicRendering/src/sample.cpp @@ -132,7 +132,6 @@ void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. camera.ViewProjection = projection * view; m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); diff --git a/src/Samples/Vulkan/DeferredShading/src/sample.cpp b/src/Samples/Vulkan/DeferredShading/src/sample.cpp index 3e572314f..993b1cdec 100644 --- a/src/Samples/Vulkan/DeferredShading/src/sample.cpp +++ b/src/Samples/Vulkan/DeferredShading/src/sample.cpp @@ -25,13 +25,13 @@ const Array indices = { 0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3 }; const Array viewPlaneVertices = { - { { -1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } }, - { { -1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 1.0f } }, - { { 1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f } }, - { { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f } } + { { -1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 1.0f } }, + { { -1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f } }, + { { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f } } }; -const Array viewPlaneIndices = { 0, 2, 1, 1, 2, 3 }; +const Array viewPlaneIndices = { 0, 1, 2, 1, 3, 2 }; struct CameraBuffer { glm::mat4 ViewProjection; @@ -188,7 +188,6 @@ void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. camera.ViewProjection = projection * view; m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); diff --git a/src/Samples/Vulkan/Multisampling/src/sample.cpp b/src/Samples/Vulkan/Multisampling/src/sample.cpp index 3c43b6adf..c39aef410 100644 --- a/src/Samples/Vulkan/Multisampling/src/sample.cpp +++ b/src/Samples/Vulkan/Multisampling/src/sample.cpp @@ -133,7 +133,7 @@ void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. + //projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. camera.ViewProjection = projection * view; m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); diff --git a/src/Samples/Vulkan/Textures/src/sample.cpp b/src/Samples/Vulkan/Textures/src/sample.cpp index 0c4e69131..900426efb 100644 --- a/src/Samples/Vulkan/Textures/src/sample.cpp +++ b/src/Samples/Vulkan/Textures/src/sample.cpp @@ -178,7 +178,6 @@ void SampleApp::updateCamera(const VulkanCommandBuffer& commandBuffer) auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. camera.ViewProjection = projection * view; m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer.get()); diff --git a/src/cmake/Shaders.cmake b/src/cmake/Shaders.cmake index f1615968a..361ad8f83 100644 --- a/src/cmake/Shaders.cmake +++ b/src/cmake/Shaders.cmake @@ -163,6 +163,10 @@ FUNCTION(TARGET_HLSL_SHADERS target_name shader_source shader_model compile_as c IF(${compile_as} STREQUAL "SPIRV") LIST(APPEND compiler_options -D SPIRV) + + IF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") + LIST(APPEND compiler_options -fvk-invert-y) + ENDIF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") ADD_CUSTOM_TARGET(${target_name} COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR} From df7e6c01e1727bb785340721a1da603391d56a78 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Sat, 26 Jun 2021 13:25:47 +0200 Subject: [PATCH 29/31] Also apply axis inversion for glslc. --- src/cmake/Shaders.cmake | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cmake/Shaders.cmake b/src/cmake/Shaders.cmake index 361ad8f83..47d2444cb 100644 --- a/src/cmake/Shaders.cmake +++ b/src/cmake/Shaders.cmake @@ -109,6 +109,12 @@ FUNCTION(TARGET_HLSL_SHADERS target_name shader_source shader_model compile_as c ELSE() SET(OUTPUT_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${SHADER_DEFAULT_SUBDIR}) ENDIF(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) + + SET(compiler_options "") + + IF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") + LIST(APPEND compiler_options -finvert-y) + ENDIF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") # TODO: Check if we can use a generator expression to build the output directory and file names, so it is possible to set the target properties to control the result file name. ADD_CUSTOM_TARGET(${target_name} @@ -119,7 +125,7 @@ FUNCTION(TARGET_HLSL_SHADERS target_name shader_source shader_model compile_as c ADD_CUSTOM_COMMAND(TARGET ${target_name} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${BUILD_GLSLC_COMPILER} -mfmt=c -DSPIRV -x hlsl -fshader_stage=${SHADER_STAGE} -c ${shader_source} -o "${OUTPUT_DIR}/${out_name}${SPIRV_DEFAULT_SUFFIX}" -MD + COMMAND ${BUILD_GLSLC_COMPILER} -mfmt=c -DSPIRV -x hlsl -fshader_stage=${SHADER_STAGE} ${compiler_options} -c ${shader_source} -o "${OUTPUT_DIR}/${out_name}${SPIRV_DEFAULT_SUFFIX}" -MD ) SET_TARGET_PROPERTIES(${target_name} PROPERTIES @@ -258,6 +264,13 @@ FUNCTION(TARGET_GLSL_SHADERS target_name shader_source compile_as compile_with s SET(OUTPUT_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${SHADER_DEFAULT_SUBDIR}) ENDIF(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) + SET(compiler_options "") + + IF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") + LIST(APPEND compiler_options -finvert-y) + ENDIF(${shader_type} STREQUAL "VERTEX" OR ${shader_type} STREQUAL "GEOMETRY" OR ${shader_type} STREQUAL "DOMAIN" OR ${shader_type} STREQUAL "TESSELATION_EVALUATION") + + # TODO: Check if we can use a generator expression to build the output directory and file names, so it is possible to set the target properties to control the result file name. ADD_CUSTOM_TARGET(${target_name} COMMENT "glslc: compiling glsl shader '${shader_source}'..." @@ -267,7 +280,7 @@ FUNCTION(TARGET_GLSL_SHADERS target_name shader_source compile_as compile_with s ADD_CUSTOM_COMMAND(TARGET ${target_name} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${BUILD_GLSLC_COMPILER} -mfmt=c -DSPIRV -x glsl -fshader_stage=${SHADER_STAGE} -c ${shader_source} -o "${OUTPUT_DIR}/${out_name}${SPIRV_DEFAULT_SUFFIX}" -MD + COMMAND ${BUILD_GLSLC_COMPILER} -mfmt=c -DSPIRV -x glsl -fshader_stage=${SHADER_STAGE} ${compiler_options} -c ${shader_source} -o "${OUTPUT_DIR}/${out_name}${SPIRV_DEFAULT_SUFFIX}" -MD ) SET_TARGET_PROPERTIES(${target_name} PROPERTIES From 0369932d68f44844f4f7bf40112135f88c0e4455 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Sat, 26 Jun 2021 13:27:22 +0200 Subject: [PATCH 30/31] Update documentation on inversion fix. --- docs/release-logs/0.2.1.md | 3 ++- docs/tutorials/quick-start.markdown | 3 --- src/Samples/DirectX12/BasicRendering/src/sample.cpp | 1 - src/Samples/DirectX12/DeferredShading/src/sample.cpp | 1 - src/Samples/DirectX12/Textures/src/sample.cpp | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/release-logs/0.2.1.md b/docs/release-logs/0.2.1.md index f168a2053..64a811aa2 100644 --- a/docs/release-logs/0.2.1.md +++ b/docs/release-logs/0.2.1.md @@ -5,4 +5,5 @@ - Adds support for multisampling. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) - Introduces render target blend states ([See PR #36](https://github.com/crud89/LiteFX/pull/36)). - Vulkan 🌋: line width is part of the dynamic rasterizer state. ([See PR #36](https://github.com/crud89/LiteFX/pull/36)) -- Vulkan 🌋: Fixed validation errors about invalid attachment layout for depth/stencil only render targets. \ No newline at end of file +- Vulkan 🌋: Fixed validation errors about invalid attachment layout for depth/stencil only render targets. +- Vulkan 🌋: Apply the `-fvk-invert-y` (DXC)/`-finvert-y` (GLSLC) switch to vertex, geometry and tessellation evaluation shaders. This removes the requirement to manually add a flip transform to projection matrices. \ No newline at end of file diff --git a/docs/tutorials/quick-start.markdown b/docs/tutorials/quick-start.markdown index a9c6993fc..f5d959292 100644 --- a/docs/tutorials/quick-start.markdown +++ b/docs/tutorials/quick-start.markdown @@ -501,12 +501,9 @@ Let's move on and compute the view and projection matrix and pre-multiply them t auto aspectRatio = m_viewport->getRectangle().width() / m_viewport->getRectangle().height(); glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); -projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. camera.ViewProjection = projection * view; ``` -It is important to fix the GLM clip coordinate scaling in the projection matrix. Since GLM has originally been developed for OpenGL, the y-coordinate of the camera space is inverted. The easiest way to fix this, is to pre-apply a flip transform by inverting the Y-coordinate. If you leave out this line, you image might end up beeing rendered upside-down in Vulkan. - In the last line, we pre-multiply the view/projection matrix and store it in the camera buffer, which we can now transfer to the GPU: ```cxx diff --git a/src/Samples/DirectX12/BasicRendering/src/sample.cpp b/src/Samples/DirectX12/BasicRendering/src/sample.cpp index 85f803fe7..32732b4aa 100644 --- a/src/Samples/DirectX12/BasicRendering/src/sample.cpp +++ b/src/Samples/DirectX12/BasicRendering/src/sample.cpp @@ -134,7 +134,6 @@ void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); camera.ViewProjection = projection * view; - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); } diff --git a/src/Samples/DirectX12/DeferredShading/src/sample.cpp b/src/Samples/DirectX12/DeferredShading/src/sample.cpp index f159e4576..32d96134e 100644 --- a/src/Samples/DirectX12/DeferredShading/src/sample.cpp +++ b/src/Samples/DirectX12/DeferredShading/src/sample.cpp @@ -188,7 +188,6 @@ void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); camera.ViewProjection = projection * view; - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer); } diff --git a/src/Samples/DirectX12/Textures/src/sample.cpp b/src/Samples/DirectX12/Textures/src/sample.cpp index c286aaeba..4904fdd49 100644 --- a/src/Samples/DirectX12/Textures/src/sample.cpp +++ b/src/Samples/DirectX12/Textures/src/sample.cpp @@ -184,7 +184,6 @@ void SampleApp::updateCamera(const DirectX12CommandBuffer& commandBuffer) glm::mat4 view = glm::lookAt(glm::vec3(1.5f, 1.5f, 1.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 projection = glm::perspective(glm::radians(60.0f), aspectRatio, 0.0001f, 1000.0f); camera.ViewProjection = projection * view; - projection[1][1] *= -1.f; // Fix GLM clip coordinate scaling. m_cameraStagingBuffer->map(reinterpret_cast(&camera), sizeof(camera)); m_cameraBuffer->transferFrom(commandBuffer, *m_cameraStagingBuffer.get()); } From d4477c65a1510183cb5c7495e0b2b257a6d55e65 Mon Sep 17 00:00:00 2001 From: Carsten Rudolph <18394207+crud89@users.noreply.github.com> Date: Sat, 26 Jun 2021 13:44:15 +0200 Subject: [PATCH 31/31] Don't include auto-created headers in source groups. --- src/Samples/DirectX12/BasicRendering/CMakeLists.txt | 2 +- src/Samples/DirectX12/DeferredShading/CMakeLists.txt | 2 +- src/Samples/DirectX12/Multisampling/CMakeLists.txt | 2 +- src/Samples/DirectX12/Textures/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Samples/DirectX12/BasicRendering/CMakeLists.txt b/src/Samples/DirectX12/BasicRendering/CMakeLists.txt index df36a8620..53e83ba9b 100644 --- a/src/Samples/DirectX12/BasicRendering/CMakeLists.txt +++ b/src/Samples/DirectX12/BasicRendering/CMakeLists.txt @@ -24,7 +24,6 @@ CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") # Collect header & source files. SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS "src/sample.h" - "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES @@ -36,6 +35,7 @@ SET(SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES ADD_EXECUTABLE(${PROJECT_NAME} ${SAMPLE_DIRECTX_12_BASIC_RENDERING_HEADERS} ${SAMPLE_DIRECTX_12_BASIC_RENDERING_SOURCES} + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) # Create source groups for better code organization. diff --git a/src/Samples/DirectX12/DeferredShading/CMakeLists.txt b/src/Samples/DirectX12/DeferredShading/CMakeLists.txt index 7fbbeb7d6..0288fe029 100644 --- a/src/Samples/DirectX12/DeferredShading/CMakeLists.txt +++ b/src/Samples/DirectX12/DeferredShading/CMakeLists.txt @@ -24,7 +24,6 @@ CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") # Collect header & source files. SET(SAMPLE_DIRECTX_12_DEFERRED_SHADING_HEADERS "src/sample.h" - "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) SET(SAMPLE_DIRECTX_12_DEFERRED_SHADING_SOURCES @@ -36,6 +35,7 @@ SET(SAMPLE_DIRECTX_12_DEFERRED_SHADING_SOURCES ADD_EXECUTABLE(${PROJECT_NAME} ${SAMPLE_DIRECTX_12_DEFERRED_SHADING_HEADERS} ${SAMPLE_DIRECTX_12_DEFERRED_SHADING_SOURCES} + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) # Create source groups for better code organization. diff --git a/src/Samples/DirectX12/Multisampling/CMakeLists.txt b/src/Samples/DirectX12/Multisampling/CMakeLists.txt index e6fd6baa3..f07277467 100644 --- a/src/Samples/DirectX12/Multisampling/CMakeLists.txt +++ b/src/Samples/DirectX12/Multisampling/CMakeLists.txt @@ -24,7 +24,6 @@ CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") # Collect header & source files. SET(SAMPLE_DIRECTX_12_MULTISAMPLING_HEADERS "src/sample.h" - "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) SET(SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES @@ -36,6 +35,7 @@ SET(SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES ADD_EXECUTABLE(${PROJECT_NAME} ${SAMPLE_DIRECTX_12_MULTISAMPLING_HEADERS} ${SAMPLE_DIRECTX_12_MULTISAMPLING_SOURCES} + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) # Create source groups for better code organization. diff --git a/src/Samples/DirectX12/Textures/CMakeLists.txt b/src/Samples/DirectX12/Textures/CMakeLists.txt index 27b0636a3..3c1cba4d0 100644 --- a/src/Samples/DirectX12/Textures/CMakeLists.txt +++ b/src/Samples/DirectX12/Textures/CMakeLists.txt @@ -24,7 +24,6 @@ CONFIGURE_FILE("../config.tmpl" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") # Collect header & source files. SET(SAMPLE_DIRECTX_12_TEXTURES_HEADERS "src/sample.h" - "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) SET(SAMPLE_DIRECTX_12_TEXTURES_SOURCES @@ -36,6 +35,7 @@ SET(SAMPLE_DIRECTX_12_TEXTURES_SOURCES ADD_EXECUTABLE(${PROJECT_NAME} ${SAMPLE_DIRECTX_12_TEXTURES_HEADERS} ${SAMPLE_DIRECTX_12_TEXTURES_SOURCES} + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" ) # Create source groups for better code organization.