diff --git a/.github/workflows/TestCompilerSupport.yml b/.github/workflows/TestCompilerSupport.yml index 2fdf6391..5eaf8944 100644 --- a/.github/workflows/TestCompilerSupport.yml +++ b/.github/workflows/TestCompilerSupport.yml @@ -11,8 +11,8 @@ on: - "main" env: - MSVC_VERSION: 14.37 - VS_VERSION: 17.7 + MSVC_VERSION: 14.41 + VS_VERSION: 17.11 jobs: Test-Compiler-Support: diff --git a/docs/STF/Bindings.md b/docs/STF/Bindings.md index fab50beb..b1982ca3 100644 --- a/docs/STF/Bindings.md +++ b/docs/STF/Bindings.md @@ -10,7 +10,7 @@ All code snippets for this section will be taken from ([Ex8_ConstantBuffers](../../examples/Ex8_ConstantBuffers)) -Shader Test Framework provides a simple way of specifying constant buffer bindings. i.e. bindings which are not buffers, samples or textures. `stf::ShaderTestFixture::RuntimeTestDesc` contains a member called `Bindings` which can be populated with a `std::vector` of pairs of `std::string`s and constant buffer data. Bindings must refer to global names. Meaning that you can't bind individual members of global parameters or constant buffers. You must provide all of the data to bind to a global name in the shader. +Shader Test Framework provides a simple way of specifying constant buffer bindings. i.e. bindings which are not buffers, samplers or textures. `stf::ShaderTestFixture::RuntimeTestDesc` contains a member called `Bindings` which can be populated with a `std::vector` of pairs of `std::string`s and constant buffer data. Bindings must refer to global names. Meaning that you can't bind individual members of global parameters or constant buffers. You must provide all of the data to bind to a global name in the shader. Given the following HLSL test: diff --git a/examples/Ex8_ConstantBuffers/ConstantBuffers.cpp b/examples/Ex8_ConstantBuffers/ConstantBuffers.cpp index 48eccfa3..eb63a7a7 100644 --- a/examples/Ex8_ConstantBuffers/ConstantBuffers.cpp +++ b/examples/Ex8_ConstantBuffers/ConstantBuffers.cpp @@ -1,5 +1,7 @@ -#include + #include +#include + #include SCENARIO("Example8Tests") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cfd7f85..16fe7faa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ set_target_properties(WINPIX PROPERTIES asset_dependency_init(ShaderTestFramework) set(SOURCES + Public/Container/FreeList.h Public/Container/RingBuffer.h Public/D3D12/AgilityDefinitions.h Public/D3D12/BindlessFreeListAllocator.h @@ -32,28 +33,31 @@ set(SOURCES Public/D3D12/Descriptor.h Public/D3D12/DescriptorFreeListAllocator.h Public/D3D12/DescriptorHeap.h + Public/D3D12/DescriptorManager.h Public/D3D12/DescriptorRingAllocator.h Public/D3D12/Fence.h + Public/D3D12/FencedResourceFreeList.h + Public/D3D12/FencedResourcePool.h Public/D3D12/GPUDevice.h Public/D3D12/GPUResource.h + Public/D3D12/GPUResourceManager.h Public/D3D12/Shader/CompiledShaderData.h Public/D3D12/Shader/IncludeHandler.h Public/D3D12/Shader/PipelineState.h Public/D3D12/Shader/RootSignature.h + Public/D3D12/Shader/Shader.h Public/D3D12/Shader/ShaderBinding.h + Public/D3D12/Shader/ShaderBindingMap.h Public/D3D12/Shader/ShaderCompiler.h Public/D3D12/Shader/ShaderEnums.h Public/D3D12/Shader/ShaderHash.h Public/D3D12/Shader/ShaderReflectionUtils.h Public/D3D12/Shader/VirtualShaderDirectoryMapping.h Public/D3D12/Shader/VirtualShaderDirectoryMappingManager.h - Public/Framework/HLSLTypes.h Public/Framework/PIXCapturer.h Public/Framework/ShaderTestCommon.h - Public/Framework/ShaderTestDescriptorManager.h Public/Framework/ShaderTestDriver.h Public/Framework/ShaderTestFixture.h - Public/Framework/ShaderTestShader.h Public/Framework/TestDataBufferLayout.h Public/Framework/TestDataBufferProcessor.h Public/Framework/TestDataBufferStructs.h @@ -64,22 +68,26 @@ set(SOURCES Public/Utility/Algorithm.h Public/Utility/Concepts.h Public/Utility/EnumReflection.h + Public/Utility/Error.h Public/Utility/Exception.h Public/Utility/Expected.h Public/Utility/FixedString.h Public/Utility/Float.h Public/Utility/FunctionTraits.h + Public/Utility/HLSLTypes.h Public/Utility/Lambda.h Public/Utility/Math.h Public/Utility/MoveOnly.h Public/Utility/Object.h Public/Utility/OverloadSet.h Public/Utility/Pointer.h + Public/Utility/StringLiteral.h Public/Utility/Time.h Public/Utility/Tuple.h Public/Utility/Type.h Public/Utility/TypeList.h Public/Utility/TypeTraits.h + Public/Utility/VersionedIndex.h Private/D3D12/BindlessFreeListAllocator.cpp Private/D3D12/CommandAllocator.cpp Private/D3D12/CommandEngine.cpp @@ -87,24 +95,26 @@ set(SOURCES Private/D3D12/CommandQueue.cpp Private/D3D12/DescriptorFreeListAllocator.cpp Private/D3D12/DescriptorHeap.cpp + Private/D3D12/DescriptorManager.cpp Private/D3D12/DescriptorRingAllocator.cpp Private/D3D12/Fence.cpp Private/D3D12/GPUDevice.cpp Private/D3D12/GPUResource.cpp + Private/D3D12/GPUResourceManager.cpp Private/D3D12/Shader/CompiledShaderData.cpp Private/D3D12/Shader/IncludeHandler.cpp Private/D3D12/Shader/PipelineState.cpp Private/D3D12/Shader/RootSignature.cpp + Private/D3D12/Shader/Shader.cpp Private/D3D12/Shader/ShaderBinding.cpp + Private/D3D12/Shader/ShaderBindingMap.cpp Private/D3D12/Shader/ShaderCompiler.cpp Private/D3D12/Shader/ShaderReflectionUtils.cpp Private/D3D12/Shader/VirtualShaderDirectoryMappingManager.cpp Private/Framework/PIXCapturer.cpp Private/Framework/ShaderTestCommon.cpp - Private/Framework/ShaderTestDescriptorManager.cpp Private/Framework/ShaderTestDriver.cpp Private/Framework/ShaderTestFixture.cpp - Private/Framework/ShaderTestShader.cpp Private/Framework/TestDataBufferLayout.cpp Private/Framework/TestDataBufferProcessor.cpp Private/Framework/TestDataBufferStructs.cpp diff --git a/src/Private/D3D12/BindlessFreeListAllocator.cpp b/src/Private/D3D12/BindlessFreeListAllocator.cpp index 2b4d95a0..028a71c1 100644 --- a/src/Private/D3D12/BindlessFreeListAllocator.cpp +++ b/src/Private/D3D12/BindlessFreeListAllocator.cpp @@ -5,6 +5,29 @@ namespace stf { + namespace Errors::BindlessFreeListAllocator + { + ErrorFragment Empty() + { + return ErrorFragment::Make<"Bindless allocator is empty">(); + } + + ErrorFragment InvalidIndex(const u32 InIndex) + { + return ErrorFragment::Make<"Bindless index {} is invalid">(InIndex); + } + + ErrorFragment IndexAlreadyReleased(const u32 InIndex) + { + return ErrorFragment::Make<"Bindless index {} has already been released">(InIndex); + } + + ErrorFragment ShrinkAttempted(const u32 InCurrentSize, const u32 InRequestedSize) + { + return ErrorFragment::Make<"Attempted shrink which is unsupported. Current size: {}, Requested size: {}">(InCurrentSize, InRequestedSize); + } + } + BindlessFreeListAllocator::BindlessFreeListAllocator(CreationParams InParams) : m_FreeList(InParams.NumDescriptors) , m_FreeSet(InParams.NumDescriptors, true) @@ -13,7 +36,7 @@ namespace stf std::ranges::generate_n(std::back_inserter(m_FreeList), m_NumDescriptors, [index = 0]() mutable { return index++; }); } - BindlessFreeListAllocator::Expected BindlessFreeListAllocator::Allocate() + ExpectedError BindlessFreeListAllocator::Allocate() { return m_FreeList.pop_front() .transform( @@ -23,35 +46,24 @@ namespace stf return BindlessIndex{ Private{}, InIndex }; }) .transform_error( - [](const EBufferError InError) + [](const Error& InError) -> Error { - switch (InError) - { - case EBufferError::EmptyBuffer: - { - return EErrorType::EmptyError; - } - - default: - { - return EErrorType::UnknownError; - } - } + return InError + Errors::BindlessFreeListAllocator::Empty(); } ); } - BindlessFreeListAllocator::Expected BindlessFreeListAllocator::Release(const BindlessIndex InIndex) + ExpectedError BindlessFreeListAllocator::Release(const BindlessIndex InIndex) { const u32 index = InIndex; if (index >= m_NumDescriptors) { - return Unexpected(EErrorType::InvalidIndex); + return Unexpected{ Error{Errors::BindlessFreeListAllocator::InvalidIndex(index) } }; } if (m_FreeSet[index]) { - return Unexpected(EErrorType::IndexAlreadyReleased); + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::IndexAlreadyReleased(index) } }; } m_FreeList.push_back(index); @@ -60,11 +72,11 @@ namespace stf return {}; } - BindlessFreeListAllocator::Expected BindlessFreeListAllocator::Resize(const u32 InNewSize) + ExpectedError BindlessFreeListAllocator::Resize(const u32 InNewSize) { if (InNewSize < m_NumDescriptors) { - return Unexpected(EErrorType::ShrinkAttempted); + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::ShrinkAttempted(m_NumDescriptors, InNewSize) } }; } if (InNewSize == m_NumDescriptors) @@ -83,22 +95,6 @@ namespace stf std::ranges::generate_n(std::back_inserter(m_FreeSet), numAdded, []() { return true; }); m_NumDescriptors = InNewSize; } - ).transform_error( - [](const stf::RingBuffer::EErrorType InError) - { - using enum stf::RingBuffer::EErrorType; - switch (InError) - { - case AttemptedShrink: - { - return EErrorType::ShrinkAttempted; - } - default: - { - return EErrorType::UnknownError; - } - } - } ); } @@ -126,4 +122,15 @@ namespace stf { return m_Index; } + + ExpectedError BindlessFreeListAllocator::IsAllocated(const BindlessIndex InIndex) const + { + const u32 index = InIndex; + if (index >= m_NumDescriptors) + { + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::InvalidIndex(InIndex) } }; + } + + return !m_FreeSet[InIndex]; + } } \ No newline at end of file diff --git a/src/Private/D3D12/CommandAllocator.cpp b/src/Private/D3D12/CommandAllocator.cpp index fd36875a..eb479be5 100644 --- a/src/Private/D3D12/CommandAllocator.cpp +++ b/src/Private/D3D12/CommandAllocator.cpp @@ -4,8 +4,9 @@ namespace stf { - CommandAllocator::CommandAllocator(CreationParams InParams) - : m_Allocator(std::move(InParams.Allocator)) + CommandAllocator::CommandAllocator(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Allocator(std::move(InParams.Allocator)) , m_Type(InParams.Type) { } diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 79bd9924..c78bb8a7 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -2,8 +2,221 @@ namespace stf { - CommandEngine::CommandEngine(CreationParams InParams) - : m_Device(InParams.Device) + + ScopedGPUResourceManager::ScopedGPUResourceManager(const SharedPtr& InResourceManager) + : m_ResourceManager(InResourceManager) + { + } + + ScopedGPUResourceManager::~ScopedGPUResourceManager() noexcept + { + for (const auto& cb : m_ConstantBuffers) + { + ThrowIfUnexpected(m_ResourceManager->Release(cb)); + } + + for (const auto& cbv : m_CBVs) + { + ThrowIfUnexpected(m_ResourceManager->Release(cbv)); + } + + for (const auto& buffer : m_Buffers) + { + ThrowIfUnexpected(m_ResourceManager->Release(buffer)); + } + + for (const auto& bufferUAV : m_BufferUAVs) + { + ThrowIfUnexpected(m_ResourceManager->Release(bufferUAV)); + } + } + + GPUResourceManager::ConstantBufferViewHandle ScopedGPUResourceManager::CreateCBV(const std::span InData) + { + const auto buffer = m_ResourceManager->Acquire(GPUResourceManager::ConstantBufferDesc{ .RequestedSize = static_cast(InData.size_bytes()) }); + const auto cbv = m_ResourceManager->CreateCBV(buffer); + + ThrowIfUnexpected(m_ResourceManager->UploadData(InData, buffer)); + + m_ConstantBuffers.push_back(buffer); + m_CBVs.push_back(cbv); + + return cbv; + } + + GPUResourceManager::BufferHandle ScopedGPUResourceManager::CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) + { + const auto handle = m_ResourceManager->Acquire(InBufferDesc); + m_Buffers.push_back(handle); + return handle; + } + + GPUResourceManager::BufferUAVHandle ScopedGPUResourceManager::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + const auto handle = m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + m_BufferUAVs.push_back(handle); + return handle; + } + + ExpectedError ScopedGPUResourceManager::QueueReadback(CommandList& InList, const GPUResourceManager::BufferHandle InBufferHandle) + { + return m_ResourceManager->Acquire( + GPUResourceManager::ReadbackBufferDesc + { + .Source = InBufferHandle + }) + .and_then( + [&](const GPUResourceManager::ReadbackBufferHandle InReadbackHandle) + { + return m_ResourceManager->QueueReadback(InList, InReadbackHandle); + } + ); + } + + void ScopedGPUResourceManager::SetUAV(CommandList& InList, const GPUResourceManager::BufferUAVHandle InHandle) + { + ThrowIfUnexpected(m_ResourceManager->SetUAV(InList, InHandle)); + } + + void ScopedGPUResourceManager::SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) + { + ThrowIfUnexpected(m_ResourceManager->SetRootDescriptor(InList, InRootParamIndex, InHandle)); + } + + void ScopedGPUResourceManager::SetDescriptorHeap(CommandList& InList) + { + m_ResourceManager->SetDescriptorHeap(InList); + } + + ExpectedError ScopedGPUResourceManager::GetDescriptorIndex(const GPUResourceManager::DescriptorOpaqueHandle InHandle) const + { + return m_ResourceManager->GetDescriptorIndex(InHandle); + } + + ScopedCommandShader::ScopedCommandShader( + const SharedPtr& InShader, + const SharedPtr& InResourceManager, + const SharedPtr& InList + ) + : m_Shader(InShader) + , m_ResourceManager(InResourceManager) + , m_List(InList) + { + } + + ExpectedError ScopedCommandShader::StageBindingData(const ShaderBinding& InBinding) + { + return m_Shader->StageBindingData(InBinding); + } + + ExpectedError ScopedCommandShader::StageBindlessResource(std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle) + { + return m_ResourceManager->GetDescriptorIndex(InHandle.GetUAVHandle()) + .and_then( + [&](const u32 InIndex) + { + return m_Shader->StageBindingData(ShaderBinding{ InBindingName, InIndex }); + } + ) + .transform( + [&]() + { + m_ResourceManager->SetUAV(*m_List, InHandle); + }); + } + + ScopedCommandContext::ScopedCommandContext(CommandEngineToken, + const SharedPtr& InList, + const SharedPtr& InResourceManager + ) + : m_List(InList) + , m_ResourceManager(MakeShared(InResourceManager)) + { + } + + CommandList* ScopedCommandContext::operator->() const + { + return GetList(); + } + + CommandList& ScopedCommandContext::operator*() const + { + return *GetList(); + } + + CommandList* ScopedCommandContext::GetList() const + { + return m_List.get(); + } + + GPUResourceManager::BufferHandle ScopedCommandContext::CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) + { + return m_ResourceManager->CreateBuffer(InDesc); + } + + GPUResourceManager::BufferUAVHandle ScopedCommandContext::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + } + + ExpectedError ScopedCommandContext::QueueReadback(const GPUResourceManager::BufferHandle InBufferHandle) + { + return m_ResourceManager->QueueReadback(*m_List, InBufferHandle); + } + + void ScopedCommandContext::SetUAV(const GPUResourceManager::BufferUAVHandle InHandle) + { + m_ResourceManager->SetUAV(*m_List, InHandle); + } + + GPUResourceManager::ConstantBufferViewHandle ScopedCommandContext::CreateCBV(const std::span InData) + { + return m_ResourceManager->CreateCBV(InData); + } + + void ScopedCommandContext::SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) + { + m_ResourceManager->SetRootDescriptor(*m_List, InRootParamIndex, InHandle); + } + + void ScopedCommandContext::PreBindShader(const SharedPtr& InShader) + { + m_ResourceManager->SetDescriptorHeap(*m_List); + m_List->SetComputeRootSignature(InShader->GetRootSig()); + } + + void ScopedCommandContext::SetShaderStateAndDispatch(const SharedPtr& InShader, const uint3 InDispatchConfig) + { + InShader->ForEachStagingBuffer( + [&](const u32 InRootParamIndex, const ShaderBindingMap::StagingInfo& InStagingInfo) + { + switch (InStagingInfo.Type) + { + case ShaderBindingMap::EBindType::RootConstants: + { + m_List->SetComputeRoot32BitConstants(InRootParamIndex, std::span{ InStagingInfo.Buffer }, 0); + break; + } + case ShaderBindingMap::EBindType::RootDescriptor: + { + const auto cbv = CreateCBV(std::as_bytes(std::span{ InStagingInfo.Buffer })); + SetRootDescriptor(InRootParamIndex, cbv); + break; + } + default: + { + std::unreachable(); + } + } + } + ); + + m_List->Dispatch(InDispatchConfig.x, InDispatchConfig.y, InDispatchConfig.z); + } + + CommandEngine::CommandEngine(ObjectToken InToken, const CreationParams& InParams) + : Object(InToken) + , m_Device(InParams.Device) , m_Queue(InParams.Device->CreateCommandQueue( { .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, @@ -16,6 +229,15 @@ namespace stf D3D12_COMMAND_LIST_TYPE_DIRECT, "Command Engine Direct List" )) + , m_ResourceManager( + Object::New( + GPUResourceManager::CreationParams + { + .Device = InParams.Device, + .Queue = m_Queue + } + ) + ) , m_Allocators() { } diff --git a/src/Private/D3D12/CommandList.cpp b/src/Private/D3D12/CommandList.cpp index 5091d388..aac6da21 100644 --- a/src/Private/D3D12/CommandList.cpp +++ b/src/Private/D3D12/CommandList.cpp @@ -11,8 +11,9 @@ namespace stf { - CommandList::CommandList(CreationParams InParams) - : m_List(std::move(InParams.List)) + CommandList::CommandList(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_List(std::move(InParams.List)) { } @@ -81,6 +82,37 @@ namespace stf m_List->SetDescriptorHeaps(1, &rawHeap); } + void CommandList::SetComputeRootConstantBufferView(const u32 InRootParamIndex, GPUResource& InConstantBuffer) + { + const auto prevBarrier = InConstantBuffer.GetBarrier(); + GPUEnhancedBarrier newBarrier + { + .Sync = D3D12_BARRIER_SYNC_COMPUTE_SHADING, + .Access = D3D12_BARRIER_ACCESS_CONSTANT_BUFFER, + .Layout = D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_GENERIC_READ + }; + + InConstantBuffer.SetBarrier(newBarrier); + D3D12_BUFFER_BARRIER bufferBarriers[] = + { + CD3DX12_BUFFER_BARRIER( + prevBarrier.Sync, + newBarrier.Sync, + prevBarrier.Access, + newBarrier.Access, + InConstantBuffer + ) + }; + + D3D12_BARRIER_GROUP BufBarrierGroups[] = + { + CD3DX12_BARRIER_GROUP(1, bufferBarriers) + }; + + m_List->Barrier(1, BufBarrierGroups); + m_List->SetComputeRootConstantBufferView(InRootParamIndex, InConstantBuffer.GetGPUAddress()); + } + void CommandList::SetGraphicsRootSignature(const RootSignature& InRootSig) { m_List->SetGraphicsRootSignature(InRootSig); diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index 7d2d4cdf..140c2d7c 100644 --- a/src/Private/D3D12/CommandQueue.cpp +++ b/src/Private/D3D12/CommandQueue.cpp @@ -6,8 +6,9 @@ namespace stf { - CommandQueue::CommandQueue(CreationParams InParams) - : m_Queue(std::move(InParams.Queue)) + CommandQueue::CommandQueue(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Queue(std::move(InParams.Queue)) , m_Fence(std::move(InParams.Fence)) { } @@ -24,22 +25,37 @@ namespace stf Fence::FencePoint CommandQueue::Signal() { - return m_Fence->Signal(m_Queue.Get()); + return m_Fence->Signal(*m_Queue.Get()); } - void CommandQueue::WaitOnFence(const Fence::FencePoint& InFencePoint) + Fence::FencePoint CommandQueue::NextSignal() { - m_Fence->WaitCPU(InFencePoint); + return m_Fence->NextSignal(); + } + + Fence::Expected CommandQueue::WaitOnFenceCPU(const Fence::FencePoint& InFencePoint) + { + return m_Fence->WaitCPU(InFencePoint); + } + + Fence::Expected CommandQueue::WaitOnFenceCPU(const Fence::FencePoint& InFencePoint, const Milliseconds InTimeout) + { + return m_Fence->WaitCPU(InFencePoint, InTimeout); + } + + void CommandQueue::WaitOnFenceGPU(const Fence::FencePoint& InFencePoint) + { + m_Fence->WaitOnQueue(*m_Queue.Get(), InFencePoint); } void CommandQueue::SyncWithQueue(CommandQueue& InQueue) { - InQueue.GetFence().WaitOnQueue(m_Queue.Get()); + WaitOnFenceGPU(InQueue.Signal()); } void CommandQueue::FlushQueue() { - WaitOnFence(Signal()); + ThrowIfUnexpected(WaitOnFenceCPU(Signal())); } void CommandQueue::ExecuteCommandList(CommandList& InList) diff --git a/src/Private/D3D12/DescriptorHeap.cpp b/src/Private/D3D12/DescriptorHeap.cpp index ec850490..4ca95f7a 100644 --- a/src/Private/D3D12/DescriptorHeap.cpp +++ b/src/Private/D3D12/DescriptorHeap.cpp @@ -4,8 +4,9 @@ namespace stf { - DescriptorHeap::DescriptorHeap(Desc InParams) noexcept - : m_Heap(std::move(InParams.Heap)) + DescriptorHeap::DescriptorHeap(ObjectToken InToken, Desc InParams) noexcept + : Object(InToken) + , m_Heap(std::move(InParams.Heap)) , m_DescriptorSize(InParams.DescriptorSize) { } diff --git a/src/Private/D3D12/DescriptorManager.cpp b/src/Private/D3D12/DescriptorManager.cpp new file mode 100644 index 00000000..58a866b9 --- /dev/null +++ b/src/Private/D3D12/DescriptorManager.cpp @@ -0,0 +1,178 @@ + +#include "D3D12/DescriptorManager.h" + +namespace stf +{ + namespace Errors + { + ErrorFragment DescriptorManagerIsFull() + { + return ErrorFragment::Make<"Descriptor manager is full">(); + } + + ErrorFragment UnknownDescriptorManagerError() + { + return ErrorFragment::Make<"Unknown Descriptor manager error">(); + } + + ErrorFragment InvalidDescriptorManagerDescriptor(const u32 InIndex) + { + return ErrorFragment::Make<"Descriptor with Index {} is an invalid descriptor">(InIndex); + } + + ErrorFragment DescriptorManagerDescriptorNotAllocated(const u32 InIndex) + { + return ErrorFragment::Make<"Descriptor with Index {} has not been allocated">(InIndex); + } + + ErrorFragment ShrinkAttemptedOnDescriptorManager(const u32 InCurrentSize, const u32 InRequestedSize) + { + return ErrorFragment::Make<"Attempted shrink in Descriptor manager which is unsupported. Current size: {}, Requested size: {}">(InCurrentSize, InRequestedSize); + } + } + + DescriptorManager::Descriptor::Descriptor(DescriptorManager::Token, const SharedPtr& InOwner, BindlessFreeListAllocator::BindlessIndex InIndex) + : m_Owner(InOwner) + , m_Index(InIndex) + { + } + + ExpectedError DescriptorManager::Descriptor::Resolve() const + { + return m_Owner->ResolveDescriptor(m_Index); + } + + DescriptorManager* DescriptorManager::Descriptor::GetOwner(Token) const + { + return m_Owner.get(); + } + + BindlessFreeListAllocator::BindlessIndex DescriptorManager::Descriptor::GetIndex(Token) const + { + return m_Index; + } + + DescriptorManager::DescriptorManager(ObjectToken InToken, const CreationParams& InParams) + : Object(InToken) + , m_Device(InParams.Device) + , m_GPUHeap(InParams.Device->CreateDescriptorHeap( + D3D12_DESCRIPTOR_HEAP_DESC + { + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + .NumDescriptors = InParams.InitialSize, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + .NodeMask = 0 + })) + , m_CPUHeap(InParams.Device->CreateDescriptorHeap( + D3D12_DESCRIPTOR_HEAP_DESC + { + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + .NumDescriptors = InParams.InitialSize, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + .NodeMask = 0 + })) + , m_Allocator({ .NumDescriptors = InParams.InitialSize }) + { + } + + ExpectedError DescriptorManager::Acquire() + { + return m_Allocator.Allocate() + .transform( + [this](const BindlessFreeListAllocator::BindlessIndex InHandle) mutable + { + return Descriptor{ Token{}, SharedFromThis(), InHandle }; + }) + .or_else( + [](Error&& InError) -> ExpectedError + { + return Unexpected{ InError + Errors::DescriptorManagerIsFull() }; + }); + } + + ExpectedError DescriptorManager::Release(const Descriptor& InDescriptor) + { + return m_Allocator.Release(InDescriptor.GetIndex(Token{})); + } + + ExpectedError> DescriptorManager::Resize(const u32 InNewSize) + { + if (m_Allocator.GetCapacity() >= InNewSize) + { + return Unexpected{ Error{ Errors::ShrinkAttemptedOnDescriptorManager(m_Allocator.GetCapacity(), InNewSize) } }; + } + + auto copyDescriptorsToNewHeap = + [this, InNewSize](const DescriptorRange InSrc, const D3D12_DESCRIPTOR_HEAP_FLAGS InFlags) + { + auto newHeap = m_Device->CreateDescriptorHeap( + D3D12_DESCRIPTOR_HEAP_DESC + { + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + .NumDescriptors = InNewSize, + .Flags = InFlags, + .NodeMask = 0 + }); + const auto destRange = newHeap->GetHeapRange(); + m_Device->CopyDescriptors(destRange, InSrc, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + return newHeap; + }; + + auto oldGPUHeap = m_GPUHeap; + + m_CPUHeap = copyDescriptorsToNewHeap(m_CPUHeap->GetHeapRange(), D3D12_DESCRIPTOR_HEAP_FLAG_NONE); + m_GPUHeap = copyDescriptorsToNewHeap(m_CPUHeap->GetHeapRange(), D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); + return m_Allocator.Resize(InNewSize) + .transform( + [oldHeap = std::move(oldGPUHeap)]() + { + return std::move(oldHeap); + } + ); + } + + u32 DescriptorManager::GetSize() const + { + return m_Allocator.GetSize(); + } + + u32 DescriptorManager::GetCapacity() const + { + return m_Allocator.GetCapacity(); + } + + void DescriptorManager::SetDescriptorHeap(CommandList& InCommandList) + { + m_Device->CopyDescriptors(m_GPUHeap->GetHeapRange(), m_CPUHeap->GetHeapRange(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + InCommandList.SetDescriptorHeaps(*m_GPUHeap); + } + + ExpectedError DescriptorManager::ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const + { + return m_Allocator + .IsAllocated(InIndex) + .and_then( + [this, InIndex](const bool InIsAllocated) -> ExpectedError + { + if (InIsAllocated) + { + const auto descriptorRange = m_CPUHeap->GetHeapRange(); + + return descriptorRange[InIndex.GetIndex()] + .transform_error( + [&](const DescriptorRange::EErrorType) + { + return Error{ Errors::InvalidDescriptorManagerDescriptor(InIndex.GetIndex()) }; + } + ); + } + else + { + return Unexpected{ Error{ Errors::DescriptorManagerDescriptorNotAllocated(InIndex.GetIndex()) } }; + } + } + ); + + } +} diff --git a/src/Private/D3D12/Fence.cpp b/src/Private/D3D12/Fence.cpp index 807ae9ce..9d3ce6e4 100644 --- a/src/Private/D3D12/Fence.cpp +++ b/src/Private/D3D12/Fence.cpp @@ -1,50 +1,51 @@ #include "D3D12/Fence.h" +#include "Utility/Exception.h" namespace stf { - Fence::Fence(CreationParams InParams) - : m_Fence(std::move(InParams.Fence)) + Fence::Fence(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Fence(std::move(InParams.Fence)) , m_NextValue(InParams.InitialValue + 1) { } - Fence::FencePoint Fence::Signal(ID3D12CommandQueue* InQueue) + Fence::FencePoint Fence::Signal(ID3D12CommandQueue& InQueue) { - InQueue->Signal(m_Fence.Get(), m_NextValue); + InQueue.Signal(m_Fence.Get(), m_NextValue); return { m_Fence.Get(), m_NextValue++ }; } - bool Fence::WaitCPU(const FencePoint& InFencePoint) const + Fence::FencePoint Fence::NextSignal() { - if (!Validate(InFencePoint)) - { - return false; - } - const u64 currentValue = m_Fence->GetCompletedValue(); - - if (currentValue < InFencePoint.SignalledValue) - { - HANDLE event = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS); + return FencePoint{ m_Fence.Get(), m_NextValue }; + } - m_Fence->SetEventOnCompletion(InFencePoint.SignalledValue, event); - WaitForSingleObject(event, INFINITE); - } + Fence::Expected Fence::WaitCPU(const FencePoint& InFencePoint) const + { + return WaitForFencePoint(InFencePoint, INFINITE); + } - return true; + Fence::Expected Fence::WaitCPU(const FencePoint& InFencePoint, const Milliseconds InTimeOut) const + { + return WaitForFencePoint(InFencePoint, InTimeOut.count()); } - void Fence::WaitOnQueue(ID3D12CommandQueue* InQueue) const + void Fence::WaitOnQueue(ID3D12CommandQueue& InQueue, const FencePoint& InFencePoint) const { - InQueue->Wait(m_Fence.Get(), m_NextValue - 1ull); + ThrowIfFailed(InQueue.Wait(InFencePoint.Fence, InFencePoint.SignalledValue)); } - Expected Fence::HasCompleted(const FencePoint& InFencePoint) const + Fence::Expected Fence::HasCompleted(const FencePoint& InFencePoint) const { - if (!Validate(InFencePoint)) - { - return Unexpected{ false }; - } - return m_Fence->GetCompletedValue() >= InFencePoint.SignalledValue; + return Validate(InFencePoint) + .transform( + [&, this]() + { + const auto fenceValue = m_Fence->GetCompletedValue(); + return fenceValue >= InFencePoint.SignalledValue; + } + ); } ID3D12Fence* Fence::GetRaw() const @@ -57,8 +58,56 @@ namespace stf return GetRaw(); } - bool Fence::Validate(const FencePoint& InFencePoint) const + Fence::Expected Fence::Validate(const FencePoint& InFencePoint) const { - return m_Fence.Get() == InFencePoint.Fence; + if (m_Fence.Get() != InFencePoint.Fence) + { + return Unexpected{ EErrorType::FencePointIsFromAnotherFence }; + } + + return {}; + } + + Fence::Expected Fence::WaitForFencePoint(const FencePoint& InFencePoint, const DWORD InTimeout) const + { + + return Validate(InFencePoint) + .and_then( + [&, this]() -> Expected + { + if (m_Fence->GetCompletedValue() > InFencePoint.SignalledValue) + { + return ECPUWaitResult::FenceAlreadyReached; + } + + HANDLE event = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS); + if (!event) + { + return Unexpected{ EErrorType::FencePointIsFromAnotherFence }; + } + + m_Fence->SetEventOnCompletion(InFencePoint.SignalledValue, event); + const auto waitResult = WaitForSingleObject(event, InTimeout); + + CloseHandle(event); + + switch (waitResult) + { + case WAIT_OBJECT_0: + { + return ECPUWaitResult::WaitFenceReached; + } + + case WAIT_TIMEOUT: + { + return ECPUWaitResult::WaitTimedOut; + } + + default: + { + return Unexpected{ EErrorType::WaitFailed }; + } + } + }); } } diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 48ccec9e..b005baa0 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -121,13 +123,10 @@ namespace stf } } - GPUDevice::GPUDevice(const CreationParams InDesc) - : m_Device(nullptr) + GPUDevice::GPUDevice(ObjectToken InToken, const CreationParams InDesc) + : Object(InToken) + , m_Device(nullptr) , m_PixHandle(ConditionalLoadPIX(InDesc.EnableGPUCapture)) - , m_CBVDescriptorSize(0) - , m_RTVDescriptorSize(0) - , m_DSVDescriptorSize(0) - , m_SamplerDescriptorSize(0) { ThrowIfUnexpected(SetupDebugLayer(InDesc.DebugLevel)); const u32 factoryCreateFlags = InDesc.DebugLevel != EDebugLevel::Off ? DXGI_CREATE_FACTORY_DEBUG : 0; @@ -149,11 +148,6 @@ namespace stf } } - m_CBVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - m_RTVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - m_DSVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); - m_SamplerDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); - CacheHardwareInfo(m_Device.Get(), adapterInfo.Adapter.Get()); } @@ -190,7 +184,7 @@ namespace stf ThrowIfFailed(m_Device->CreateCommandAllocator(InType, IID_PPV_ARGS(allocator.GetAddressOf()))); SetName(allocator.Get(), InName); - return MakeShared(CommandAllocator::CreationParams{ std::move(allocator), InType }); + return Object::New(CommandAllocator::CreationParams{ std::move(allocator), InType }); } SharedPtr GPUDevice::CreateCommandList(D3D12_COMMAND_LIST_TYPE InType, std::string_view InName) const @@ -199,7 +193,7 @@ namespace stf ThrowIfFailed(m_Device->CreateCommandList1(0, InType, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(list.GetAddressOf()))); SetName(list.Get(), InName); - return MakeShared(CommandList::CreationParams{ std::move(list) }); + return Object::New(CommandList::CreationParams{ std::move(list) }); } SharedPtr GPUDevice::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC& InDesc, const std::string_view InName) const @@ -207,27 +201,33 @@ namespace stf ComPtr raw = nullptr; ThrowIfFailed(m_Device->CreateCommandQueue(&InDesc, IID_PPV_ARGS(raw.GetAddressOf()))); SetName(raw.Get(), InName); - return MakeShared(CommandQueue::CreationParams{ std::move(raw), CreateFence(0ull) }); + return Object::New(CommandQueue::CreationParams{ std::move(raw), CreateFence(0ull) }); } - SharedPtr GPUDevice::CreateCommittedResource(const D3D12_HEAP_PROPERTIES& InHeapProps, const D3D12_HEAP_FLAGS InFlags, const D3D12_RESOURCE_DESC1& InResourceDesc, const D3D12_BARRIER_LAYOUT InInitialLayout, const D3D12_CLEAR_VALUE* InClearValue, const std::span InCastableFormats, const std::string_view InName) const + SharedPtr GPUDevice::CreateCommittedResource(const CommittedResourceDesc& InDesc) const { ComPtr raw{ nullptr }; ThrowIfFailed( m_Device->CreateCommittedResource3( - &InHeapProps, - InFlags, - &InResourceDesc, - InInitialLayout, - InClearValue, + &InDesc.HeapProps, + InDesc.HeapFlags, + &InDesc.ResourceDesc, + InDesc.BarrierLayout, + InDesc.ClearValue.has_value() ? &InDesc.ClearValue.value() : nullptr, nullptr, - static_cast(InCastableFormats.size()), - InCastableFormats.data(), + static_cast(InDesc.CastableFormats.size()), + InDesc.CastableFormats.data(), IID_PPV_ARGS(raw.GetAddressOf())) ); - SetName(raw.Get(), InName); - return MakeShared(GPUResource::CreationParams{ std::move(raw), InClearValue ? std::optional{*InClearValue} : std::nullopt, {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InInitialLayout} }); + SetName(raw.Get(), std::string_view{ InDesc.Name }); + return Object::New( + GPUResource::CreationParams{ + .Resource = std::move(raw), + .ClearValue = InDesc.ClearValue, + .InitialBarrier = {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InDesc.BarrierLayout}, + .Name = InDesc.Name + }); } SharedPtr GPUDevice::CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC& InDesc, const std::string_view InName) const @@ -237,7 +237,7 @@ namespace stf SetName(heap.Get(), InName); const u32 descriptorSize = GetDescriptorSize(InDesc.Type); - return MakeShared(DescriptorHeap::Desc{ std::move(heap), descriptorSize }); + return Object::New(DescriptorHeap::Desc{ std::move(heap), descriptorSize }); } SharedPtr GPUDevice::CreateFence(const u64 InInitialValue, const std::string_view InName) const @@ -246,7 +246,7 @@ namespace stf ThrowIfFailed(m_Device->CreateFence(InInitialValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.GetAddressOf()))); SetName(fence.Get(), InName); - return MakeShared(Fence::CreationParams{ std::move(fence), InInitialValue }); + return Object::New(Fence::CreationParams{ std::move(fence), InInitialValue }); } SharedPtr GPUDevice::CreateRootSignature(const D3D12_VERSIONED_ROOT_SIGNATURE_DESC& InDesc) const @@ -260,7 +260,7 @@ namespace stf ComPtr deserializer; ThrowIfFailed(D3D12CreateVersionedRootSignatureDeserializer(signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(deserializer.GetAddressOf()))); - return MakeShared(RootSignature::CreationParams{ std::move(rootSignatureObject), std::move(deserializer), std::move(signature) }); + return Object::New(RootSignature::CreationParams{ std::move(rootSignatureObject), std::move(deserializer), std::move(signature) }); } SharedPtr GPUDevice::CreateRootSignature(const CompiledShaderData& InShader) const @@ -288,6 +288,15 @@ namespace stf InType); } + void GPUDevice::CreateConstantBufferView(const GPUResource& InResource, const DescriptorHandle InHandle) const + { + D3D12_CONSTANT_BUFFER_VIEW_DESC viewDesc{}; + viewDesc.BufferLocation = InResource.GetGPUAddress(); + viewDesc.SizeInBytes = static_cast(InResource.GetDesc().Width); + + m_Device->CreateConstantBufferView(&viewDesc, InHandle.GetCPUHandle()); + } + void GPUDevice::CreateShaderResourceView(const GPUResource& InResource, const DescriptorHandle InHandle) const { m_Device->CreateShaderResourceView(InResource, nullptr, InHandle.GetCPUHandle()); @@ -374,8 +383,8 @@ namespace stf featureLevels.pFeatureLevelsRequested = levels.data(); ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featureLevels, static_cast(sizeof(featureLevels)))); - GPUVirtualAddressInfo virtualAddressInfo{}; - ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_GPU_VIRTUAL_ADDRESS_SUPPORT, &virtualAddressInfo, static_cast(sizeof(virtualAddressInfo)))); + D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORT virtualAddressSupport{}; + ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_GPU_VIRTUAL_ADDRESS_SUPPORT, &virtualAddressSupport, static_cast(sizeof(virtualAddressSupport)))); D3D12_FEATURE_DATA_SHADER_MODEL maxShaderModel{ .HighestShaderModel = D3D_HIGHEST_SHADER_MODEL }; ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &maxShaderModel, static_cast(sizeof(maxShaderModel)))); @@ -406,92 +415,112 @@ namespace stf D3D12_FEATURE_DATA_D3D12_OPTIONS12 options12{}; ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &options12, static_cast(sizeof(options12)))); - m_Info = MakeShared + D3D12_FEATURE_DATA_D3D12_OPTIONS19 options19{}; + ThrowIfFailed(InDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS19, &options19, static_cast(sizeof(options19)))); + + m_Info = MakeUnique ( - GPUAdapterInfo - { - .Name = adapterDesc.Description, - .DedicatedVRAM = adapterDesc.DedicatedVideoMemory, - .SystemRAM = adapterDesc.DedicatedSystemMemory, - .VendorId = adapterDesc.VendorId, - .DeviceId = adapterDesc.DeviceId, - .SubSysId = adapterDesc.SubSysId, - .Revision = adapterDesc.Revision - }, - D3D12FeatureInfo - { - .ShaderCacheSupportFlags = shaderCacheInfo.SupportFlags, - .TiledResourceTier = options1.TiledResourcesTier, - .ResourceBindingTier = options1.ResourceBindingTier, - .ConservativeRasterizationTier = options1.ConservativeRasterizationTier, - .ResourceHeapTier = options1.ResourceHeapTier, - .RenderPassTier = options5.RenderPassesTier, - .RayTracingTier = options5.RaytracingTier, - .MaxFeatureLevel = featureLevels.MaxSupportedFeatureLevel, - .MaxShaderModel = maxShaderModel.HighestShaderModel, - .RootSignatureVersion = rootSigInfo.HighestVersion, - .MeshShaderTier = options7.MeshShaderTier, - .SamplerFeedbackTier = options7.SamplerFeedbackTier, - .ProgrammableSamplePositionsTier = options2.ProgrammableSamplePositionsTier, - .DoublePrecisionSupport = !!options1.DoublePrecisionFloatShaderOps, - .Float10Support = Enum::EnumHasMask(options1.MinPrecisionSupport, D3D12_SHADER_MIN_PRECISION_SUPPORT_10_BIT), - .Float16Support = Enum::EnumHasMask(options1.MinPrecisionSupport, D3D12_SHADER_MIN_PRECISION_SUPPORT_16_BIT), - .DepthBoundsTestSupport = !!options2.DepthBoundsTestSupported, - .EnhancedBarriersSupport = !!options12.EnhancedBarriersSupported - }, - GPUWaveOperationInfo - { - .MinWaveLaneCount = waveOpInfo.WaveLaneCountMin, - .MaxWaveLaneCount = waveOpInfo.WaveLaneCountMax, - .TotalLaneCount = waveOpInfo.TotalLaneCount, - .IsSupported = !!waveOpInfo.WaveOps - }, - virtualAddressInfo, - GPUArchitectureInfo - { - .GPUIndex = architectureInfo.NodeIndex, - .SupportsTileBasedRendering = !!architectureInfo.TileBasedRenderer, - .UMA = !!architectureInfo.UMA, - .CacheCoherentUMA = !!architectureInfo.CacheCoherentUMA, - .IsolatedMMU = !!architectureInfo.IsolatedMMU - }, - VariableRateShadingInfo - { - .ImageTileSize = options6.ShadingRateImageTileSize, - .AdditionalShadingRates = !!options6.AdditionalShadingRatesSupported, - .PerPrimitiveShadingRateSupportedWithViewportIndexing = !!options6.PerPrimitiveShadingRateSupportedWithViewportIndexing, - .BackgroundProcessingSupported = !!options6.BackgroundProcessingSupported, - .Tier = options6.VariableShadingRateTier + GPUHardwareInfo{ + .AdapterInfo = GPUAdapterInfo + { + .Name = adapterDesc.Description, + .DedicatedVRAM = adapterDesc.DedicatedVideoMemory, + .SystemRAM = adapterDesc.DedicatedSystemMemory, + .VendorId = adapterDesc.VendorId, + .DeviceId = adapterDesc.DeviceId, + .SubSysId = adapterDesc.SubSysId, + .Revision = adapterDesc.Revision + }, + .FeatureInfo = D3D12FeatureInfo + { + .ShaderCacheSupportFlags = shaderCacheInfo.SupportFlags, + .TiledResourceTier = options1.TiledResourcesTier, + .ResourceBindingTier = options1.ResourceBindingTier, + .ConservativeRasterizationTier = options1.ConservativeRasterizationTier, + .ResourceHeapTier = options1.ResourceHeapTier, + .RenderPassTier = options5.RenderPassesTier, + .RayTracingTier = options5.RaytracingTier, + .MaxFeatureLevel = featureLevels.MaxSupportedFeatureLevel, + .MaxShaderModel = maxShaderModel.HighestShaderModel, + .RootSignatureVersion = rootSigInfo.HighestVersion, + .MeshShaderTier = options7.MeshShaderTier, + .SamplerFeedbackTier = options7.SamplerFeedbackTier, + .ProgrammableSamplePositionsTier = options2.ProgrammableSamplePositionsTier, + .DoublePrecisionSupport = !!options1.DoublePrecisionFloatShaderOps, + .Float10Support = Enum::EnumHasMask(options1.MinPrecisionSupport, D3D12_SHADER_MIN_PRECISION_SUPPORT_10_BIT), + .Float16Support = Enum::EnumHasMask(options1.MinPrecisionSupport, D3D12_SHADER_MIN_PRECISION_SUPPORT_16_BIT), + .DepthBoundsTestSupport = !!options2.DepthBoundsTestSupported, + .EnhancedBarriersSupport = !!options12.EnhancedBarriersSupported + }, + .WaveOperationInfo = GPUWaveOperationInfo + { + .MinWaveLaneCount = waveOpInfo.WaveLaneCountMin, + .MaxWaveLaneCount = waveOpInfo.WaveLaneCountMax, + .TotalLaneCount = waveOpInfo.TotalLaneCount, + .IsSupported = !!waveOpInfo.WaveOps + }, + .VirtualAddressInfo = GPUVirtualAddressInfo + { + .MaxBitsPerResource = virtualAddressSupport.MaxGPUVirtualAddressBitsPerResource, + .MaxBitsPerProcess = virtualAddressSupport.MaxGPUVirtualAddressBitsPerProcess + }, + .ArchitectureInfo = GPUArchitectureInfo + { + .GPUIndex = architectureInfo.NodeIndex, + .SupportsTileBasedRendering = !!architectureInfo.TileBasedRenderer, + .UMA = !!architectureInfo.UMA, + .CacheCoherentUMA = !!architectureInfo.CacheCoherentUMA, + .IsolatedMMU = !!architectureInfo.IsolatedMMU + }, + .VRSInfo = VariableRateShadingInfo + { + .ImageTileSize = options6.ShadingRateImageTileSize, + .AdditionalShadingRates = !!options6.AdditionalShadingRatesSupported, + .PerPrimitiveShadingRateSupportedWithViewportIndexing = !!options6.PerPrimitiveShadingRateSupportedWithViewportIndexing, + .BackgroundProcessingSupported = !!options6.BackgroundProcessingSupported, + .Tier = options6.VariableShadingRateTier + }, + .DescriptorHeapInfo = DescriptorHeapProperties + { + .MaxSamplers = options19.MaxSamplerDescriptorHeapSize, + .MaxStaticSamplers = options19.MaxSamplerDescriptorHeapSizeWithStaticSamplers, + .MaxViews = options19.MaxViewDescriptorHeapSize, + .ViewDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV), + .RTVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV), + .DSVDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV), + .SamplerDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER), + } } ); } u32 GPUDevice::GetDescriptorSize(const D3D12_DESCRIPTOR_HEAP_TYPE InType) const { + const auto& hardwareInfo = GetHardwareInfo(); switch (InType) { - case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: - { - return m_CBVDescriptorSize; - } - case D3D12_DESCRIPTOR_HEAP_TYPE_DSV: - { - return m_DSVDescriptorSize; - } - case D3D12_DESCRIPTOR_HEAP_TYPE_RTV: - { - return m_RTVDescriptorSize; - } - case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: - { - return m_SamplerDescriptorSize; - } - case D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES: - default: - { - ThrowIfFalse(false, "Unknown Descriptor heap type"); + case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: + { + return hardwareInfo.DescriptorHeapInfo.ViewDescriptorSize; + } + case D3D12_DESCRIPTOR_HEAP_TYPE_DSV: + { + return hardwareInfo.DescriptorHeapInfo.DSVDescriptorSize; + } + case D3D12_DESCRIPTOR_HEAP_TYPE_RTV: + { + return hardwareInfo.DescriptorHeapInfo.RTVDescriptorSize; + } + case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: + { + return hardwareInfo.DescriptorHeapInfo.SamplerDescriptorSize; + } + case D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES: + default: + { + ThrowIfFalse(false, "Unknown Descriptor heap type"); - } + } } return 0; diff --git a/src/Private/D3D12/GPUResource.cpp b/src/Private/D3D12/GPUResource.cpp index 07192730..3920edb1 100644 --- a/src/Private/D3D12/GPUResource.cpp +++ b/src/Private/D3D12/GPUResource.cpp @@ -4,8 +4,10 @@ namespace stf { - GPUResource::GPUResource(CreationParams InParams) noexcept - : m_Resource(std::move(InParams.Resource)) + GPUResource::GPUResource(ObjectToken InToken, const CreationParams& InParams) noexcept + : Object(InToken) + , m_Name{InParams.Name} + , m_Resource(InParams.Resource) , m_ClearValue(InParams.ClearValue) , m_CurrentBarrier(InParams.InitialBarrier) { @@ -41,6 +43,11 @@ namespace stf return m_ClearValue; } + std::string GPUResource::GetName() const + { + return m_Name; + } + u64 GPUResource::GetGPUAddress() const noexcept { return m_Resource->GetGPUVirtualAddress(); @@ -51,8 +58,8 @@ namespace stf return MappedResource(GPUResourceToken{}, m_Resource); } - MappedResource::MappedResource(GPUResourceToken, ComPtr InResource) - : m_Resource(std::move(InResource)) + MappedResource::MappedResource(GPUResourceToken, const ComPtr& InResource) + : m_Resource(InResource) , m_MappedData() { if (m_Resource) @@ -60,7 +67,7 @@ namespace stf D3D12_RANGE range{ 0, m_Resource->GetDesc().Width }; void* mappedData = nullptr; ThrowIfFailed(m_Resource->Map(0, &range, &mappedData)); - m_MappedData = std::span(static_cast(mappedData), range.End); + m_MappedData = std::span(static_cast(mappedData), range.End); } } diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp new file mode 100644 index 00000000..5b6abd6c --- /dev/null +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -0,0 +1,358 @@ + +#include "D3D12/GPUResourceManager.h" + +#include "Utility/Math.h" + +#include + +namespace stf +{ + namespace Errors::GPUResourceManager + { + ErrorFragment ReadbackHasNotBeenCompleted(const std::string_view InSourceName) + { + return ErrorFragment::Make<"Readback of {} has not completed yet.">(InSourceName); + } + } + + GPUResourceManager::GPUResourceManager(ObjectToken InToken, const CreationParams& InParams) + : Object(InToken) + , m_Device(InParams.Device) + , m_Queue(InParams.Queue) + , m_DescriptorManager( + Object::New + ( + DescriptorManager::CreationParams + { + .Device = InParams.Device, + .InitialSize = 1000 + } + ) + ) + , m_Resources{ ResourceManager::CreationParams{.Queue = InParams.Queue} } + , m_Descriptors{ DescriptorFreeList::CreationParams{ .Queue = InParams.Queue } } + , m_HeapReleaseManager{ DescriptorHeapReleaseManager::CreationParams { .Queue = InParams.Queue } } + { + } + + GPUResourceManager::ConstantBufferHandle::ConstantBufferHandle(Private, const ResourceHandle InHandle) + : m_Handle(InHandle) + { + } + + GPUResourceManager::ResourceHandle GPUResourceManager::ConstantBufferHandle::GetHandle() const + { + return m_Handle; + } + + GPUResourceManager::ConstantBufferViewHandle::ConstantBufferViewHandle(Private, const ResourceHandle InBufferHandle, const DescriptorOpaqueHandle InCBVHandle) + : m_BufferHandle(InBufferHandle) + , m_CBVHandle(InCBVHandle) + { + } + + GPUResourceManager::ResourceHandle GPUResourceManager::ConstantBufferViewHandle::GetBufferHandle() const + { + return m_BufferHandle; + } + + GPUResourceManager::DescriptorOpaqueHandle GPUResourceManager::ConstantBufferViewHandle::GetCBVHandle() const + { + return m_CBVHandle; + } + + GPUResourceManager::BufferHandle::BufferHandle(Private, const ResourceHandle InHandle) + : m_Handle(InHandle) + { + } + + GPUResourceManager::ResourceHandle GPUResourceManager::BufferHandle::GetHandle() const + { + return m_Handle; + } + + GPUResourceManager::BufferUAVHandle::BufferUAVHandle(Private, const ResourceHandle InBufferHandle, const DescriptorOpaqueHandle InUAVHandle) + : m_BufferHandle(InBufferHandle) + , m_UAVHandle(InUAVHandle) + { + } + + GPUResourceManager::ResourceHandle GPUResourceManager::BufferUAVHandle::GetBufferHandle() const + { + return m_BufferHandle; + } + + GPUResourceManager::DescriptorOpaqueHandle GPUResourceManager::BufferUAVHandle::GetUAVHandle() const + { + return m_UAVHandle; + } + + GPUResourceManager::ReadbackBufferHandle::ReadbackBufferHandle(Private, const ResourceHandle InReadbackHandle, const ResourceHandle InSourceHandle) + : m_ReadbackHandle(InReadbackHandle) + , m_SourceHandle(InSourceHandle) + { + } + + GPUResourceManager::ResourceHandle GPUResourceManager::ReadbackBufferHandle::GetReadbackHandle() const + { + return m_ReadbackHandle; + } + + GPUResourceManager::ResourceHandle GPUResourceManager::ReadbackBufferHandle::GetSourceHandle() const + { + return m_SourceHandle; + } + + GPUResourceManager::ReadbackResultHandle::ReadbackResultHandle(Private, const InFlightReadbackHandle InHandle) + : m_Handle(InHandle) + { + } + + GPUResourceManager::InFlightReadbackHandle GPUResourceManager::ReadbackResultHandle::GetReadbackHandle() const + { + return m_Handle; + } + + GPUResourceManager::BufferHandle GPUResourceManager::Acquire(const BufferDesc& InDesc) + { + const auto bufferHandle = m_Resources.Manage( + m_Device->CreateCommittedResource( + GPUDevice::CommittedResourceDesc + { + .HeapProps = CD3DX12_HEAP_PROPERTIES{ D3D12_HEAP_TYPE_DEFAULT }, + .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(InDesc.RequestedSize, InDesc.Flags), + .Name = InDesc.Name + } + ) + ); + + return BufferHandle{ Private{}, bufferHandle }; + } + + GPUResourceManager::ConstantBufferHandle GPUResourceManager::Acquire(const ConstantBufferDesc& InDesc) + { + const u64 bufferSize = AlignedOffset(InDesc.RequestedSize, 256ull); + const auto bufferHandle = m_Resources.Manage( + m_Device->CreateCommittedResource( + GPUDevice::CommittedResourceDesc + { + .HeapProps = CD3DX12_HEAP_PROPERTIES{ D3D12_HEAP_TYPE_UPLOAD }, + .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(bufferSize), + .Name = InDesc.Name + } + ) + ); + + return ConstantBufferHandle{ Private{}, bufferHandle }; + } + + ExpectedError GPUResourceManager::Acquire(const ReadbackBufferDesc& InDesc) + { + return m_Resources.Get(InDesc.Source.GetHandle()) + .and_then( + [&](const SharedPtr& InSourceBuffer) -> ExpectedError + { + const auto readbackHandle = m_Resources.Manage( + m_Device->CreateCommittedResource( + GPUDevice::CommittedResourceDesc + { + .HeapProps = CD3DX12_HEAP_PROPERTIES{ D3D12_HEAP_TYPE_READBACK }, + .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(InSourceBuffer->GetDesc().Width), + .Name = std::format("Readback buffer for -> {}", InSourceBuffer->GetName()) + } + ) + ); + + return ReadbackBufferHandle{ Private{}, readbackHandle, InDesc.Source.GetHandle()}; + } + ); + } + + GPUResourceManager::BufferUAVHandle GPUResourceManager::CreateUAV(const BufferHandle InHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() + .or_else( + [&](const Error& InErrorType) -> ExpectedError + { + if (InErrorType.HasFragment(Errors::DescriptorManagerIsFull())) + { + auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); + ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); + return m_DescriptorManager->Acquire(); + } + + return Unexpected{ InErrorType }; + } + )); + + ThrowIfUnexpected(m_Resources.Get(InHandle.GetHandle()) + .and_then + ( + [&](const SharedPtr& InResource) -> ExpectedError + { + m_Device->CreateUnorderedAccessView(*InResource, InDesc, ThrowIfUnexpected(descriptor.Resolve())); + return {}; + } + )); + + const auto managedHandle = m_Descriptors.Manage(std::move(descriptor)); + + return BufferUAVHandle{ Private{}, InHandle.GetHandle(), managedHandle }; + } + + GPUResourceManager::ConstantBufferViewHandle GPUResourceManager::CreateCBV(const ConstantBufferHandle InBufferHandle) + { + auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() + .or_else( + [&](const Error& InErrorType) -> ExpectedError + { + if (InErrorType.HasFragment(Errors::DescriptorManagerIsFull())) + { + auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); + ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); + return m_DescriptorManager->Acquire(); + } + + return Unexpected{ InErrorType }; + } + )); + + ThrowIfUnexpected(m_Resources.Get(InBufferHandle.GetHandle()) + .and_then + ( + [&](const SharedPtr& InResource) -> ExpectedError + { + m_Device->CreateConstantBufferView(*InResource, ThrowIfUnexpected(descriptor.Resolve())); + return {}; + } + )); + + const auto managedHandle = m_Descriptors.Manage(std::move(descriptor)); + + return ConstantBufferViewHandle{ Private{}, InBufferHandle.GetHandle(), managedHandle}; + } + + ExpectedError GPUResourceManager::GetDescriptorIndex(const DescriptorOpaqueHandle InHandle) const + { + return m_Descriptors.Get(InHandle) + .and_then( + [&](const DescriptorManager::Descriptor& InDescriptor) + { + return InDescriptor.Resolve(); + } + ) + .and_then( + [](const DescriptorHandle InHandle) -> ExpectedError + { + return InHandle.GetHeapIndex(); + } + ); + } + + ExpectedError GPUResourceManager::UploadData(const std::span InData, const ConstantBufferHandle InHandle) + { + return m_Resources.Get(InHandle.GetHandle()) + .and_then + ( + [&](const SharedPtr& InResource) -> ExpectedError + { + const auto mappedResource = InResource->Map(); + const auto mappedData = mappedResource.Get(); + + if (InData.size_bytes() > mappedData.size_bytes()) + { + return Unexpected{ + Error + { + ErrorFragment::Make<"Provided data is too large for resource. Provided data: {} bytes, Resource Size {} bytes">( + InData.size_bytes(), + mappedData.size_bytes()) + } + }; + } + + std::memcpy(mappedData.data(), InData.data(), InData.size_bytes()); + return {}; + } + ); + } + + ExpectedError GPUResourceManager::Release(const BufferHandle InHandle) + { + return m_Resources.Release(InHandle.GetHandle()); + } + + ExpectedError GPUResourceManager::Release(const ConstantBufferHandle InHandle) + { + return m_Resources.Release(InHandle.GetHandle()); + } + + ExpectedError GPUResourceManager::Release(const BufferUAVHandle InHandle) + { + return m_Descriptors.Release(InHandle.GetUAVHandle()); + } + + ExpectedError GPUResourceManager::Release(const ConstantBufferViewHandle InHandle) + { + return m_Descriptors.Release(InHandle.GetCBVHandle()); + } + + ExpectedError GPUResourceManager::QueueReadback(CommandList& InCommandList, const ReadbackBufferHandle InHandle) + { + return m_Resources.Get(InHandle.GetReadbackHandle()) + .and_then( + [&](const SharedPtr& InReadback) + { + return m_Resources.Get(InHandle.GetSourceHandle()) + .and_then( + [&](const SharedPtr& InSource) -> ExpectedError + { + InCommandList.CopyBufferResource(*InReadback, *InSource); + + const auto handle = m_Readbacks.Manage( + InFlightReadback + { + .Handle = InHandle, + .FencePoint = m_Queue->Signal(), + .SourceBufferName = InSource->GetName() + } + ); + + return ReadbackResultHandle{ Private{}, handle }; + } + ); + } + ); + } + + ExpectedError GPUResourceManager::SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const ConstantBufferViewHandle InHandle) + { + return m_Resources.Get(InHandle.GetBufferHandle()) + .and_then + ( + [&](const SharedPtr& InResource) -> ExpectedError + { + InList.SetComputeRootConstantBufferView(InRootParamIndex, *InResource); + return {}; + } + ); + } + + ExpectedError GPUResourceManager::SetUAV(CommandList& InCommandList, const BufferUAVHandle InHandle) + { + return m_Resources.Get(InHandle.GetBufferHandle()) + .and_then + ( + [&](const SharedPtr& InResource) -> ExpectedError + { + InCommandList.SetBufferUAV(*InResource); + return {}; + } + ); + } + + void GPUResourceManager::SetDescriptorHeap(CommandList& InCommandList) + { + m_DescriptorManager->SetDescriptorHeap(InCommandList); + } +} diff --git a/src/Private/D3D12/Shader/PipelineState.cpp b/src/Private/D3D12/Shader/PipelineState.cpp index 2cd14c69..6adf9abc 100644 --- a/src/Private/D3D12/Shader/PipelineState.cpp +++ b/src/Private/D3D12/Shader/PipelineState.cpp @@ -4,8 +4,9 @@ namespace stf { - PipelineState::PipelineState(CreationParams InParams) noexcept - : m_Raw(std::move(InParams.Raw)) + PipelineState::PipelineState(ObjectToken InToken, CreationParams InParams) noexcept + : Object(InToken) + , m_Raw(std::move(InParams.Raw)) { } diff --git a/src/Private/D3D12/Shader/RootSignature.cpp b/src/Private/D3D12/Shader/RootSignature.cpp index b03689b0..3bfd703b 100644 --- a/src/Private/D3D12/Shader/RootSignature.cpp +++ b/src/Private/D3D12/Shader/RootSignature.cpp @@ -2,8 +2,9 @@ namespace stf { - RootSignature::RootSignature(CreationParams InParams) - : m_RootSig(std::move(InParams.RootSig)) + RootSignature::RootSignature(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_RootSig(std::move(InParams.RootSig)) , m_Deserializer(std::move(InParams.Deserializer)) , m_Blob(std::move(InParams.Blob)) { diff --git a/src/Private/D3D12/Shader/Shader.cpp b/src/Private/D3D12/Shader/Shader.cpp new file mode 100644 index 00000000..c59ff855 --- /dev/null +++ b/src/Private/D3D12/Shader/Shader.cpp @@ -0,0 +1,69 @@ + +#include "D3D12/Shader/Shader.h" + +namespace stf +{ + namespace Errors + { + Error NoShaderReflectionDataAvailable() + { + return Error::FromFragment<"No reflection data generated.Did you compile your shader as a lib ? Libs do not generate reflection data">(); + } + } + + Shader::Shader(ObjectToken InToken, const CreationParams& InParams) + : Object(InToken) + , m_ShaderData(InParams.ShaderData) + , m_BindingMap(InParams.BindingMap) + { + } + + ExpectedError> Shader::Make(const CompiledShaderData& InShaderData, GPUDevice& InDevice) + { + auto reflection = InShaderData.GetReflection(); + + if (!reflection) + { + return Unexpected{ Errors::NoShaderReflectionDataAvailable() }; + } + + return ShaderBindingMap::Make(*reflection, InDevice) + .and_then( + [&](ShaderBindingMap&& InShaderBindingMap) -> ExpectedError + { + return Shader::CreationParams + { + .ShaderData = InShaderData, + .BindingMap = std::move(InShaderBindingMap) + }; + }) + .and_then( + [](const Shader::CreationParams& InCreationParams) -> ExpectedError> + { + return Object::New(InCreationParams); + }); + } + + ExpectedError Shader::StageBindingData(const ShaderBinding& InBinding) + { + return m_BindingMap.StageBindingData(InBinding); + } + + uint3 Shader::GetThreadGroupSize() const + { + uint3 ret; + m_ShaderData.GetReflection()->GetThreadGroupSize(&ret.x, &ret.y, &ret.z); + + return ret; + } + + const RootSignature& Shader::GetRootSig() const + { + return m_BindingMap.GetRootSig(); + } + + IDxcBlob* Shader::GetCompiledShader() const + { + return m_ShaderData.GetCompiledShader(); + } +} \ No newline at end of file diff --git a/src/Private/D3D12/Shader/ShaderBindingMap.cpp b/src/Private/D3D12/Shader/ShaderBindingMap.cpp new file mode 100644 index 00000000..b0a71f97 --- /dev/null +++ b/src/Private/D3D12/Shader/ShaderBindingMap.cpp @@ -0,0 +1,217 @@ + +#include "D3D12/Shader/ShaderBindingMap.h" +#include "D3D12/Shader/ShaderReflectionUtils.h" + +namespace stf +{ + namespace Errors + { + Error OnlyConstantBuffersSupportedForBinding() + { + return Error::FromFragment<"Only constant buffers are supported for binding.">(); + } + + Error ConstantBufferCantBeInRootConstants(const std::string_view InBufferName) + { + return Error::FromFragment<"Constant Buffer: {} can not be stored in root constants">(InBufferName); + } + + Error RootSignatureDWORDLimitReached() + { + return Error::FromFragment<"Limit of 64 uints reached">(); + } + + Error ConstantBufferMustBeBoundToDecriptorTable(const std::string_view InBufferName) + { + return Error::FromFragment<"Constant Buffer: {} must be bound in a descriptor table, which is currently unsupported">(InBufferName); + } + + Error BindingIsSmallerThanBindingData(const std::string_view InBindingName, const u32 InConstantBufferSize, const u64 InBindingDataSize) + { + return Error::FromFragment<"Binding is smaller in size than provided data. Binding name: {}, Binding size: {}, Provided binding data size: {}"> + ( + InBindingName, + InConstantBufferSize, + InBindingDataSize + ); + } + + Error BindingDoesNotExist(const std::string_view InBindingName) + { + return Error::FromFragment<"No shader binding named {} found. If the binding exists in the shader, are you using it?">(InBindingName); + } + } + + ExpectedError ShaderBindingMap::Make(ID3D12ShaderReflection& InReflection, GPUDevice& InDevice) + { + D3D12_SHADER_DESC shaderDesc{}; + InReflection.GetDesc(&shaderDesc); + + std::vector parameters; + parameters.reserve(shaderDesc.BoundResources); + + BindingMapType nameToBindingMap; + StagingBufferMap stagingBuffers; + + u32 totalNumValues = 0; + for (u32 boundIndex = 0; boundIndex < shaderDesc.BoundResources; ++boundIndex) + { + D3D12_SHADER_INPUT_BIND_DESC bindDesc{}; + InReflection.GetResourceBindingDesc(boundIndex, &bindDesc); + + if (bindDesc.Type != D3D_SIT_CBUFFER) + { + return Unexpected{ Errors::OnlyConstantBuffersSupportedForBinding() }; + } + + const auto constantBuffer = InReflection.GetConstantBufferByName(bindDesc.Name); + D3D12_SHADER_BUFFER_DESC bufferDesc{}; + ThrowIfFailed(constantBuffer->GetDesc(&bufferDesc)); + + const EBindType bindingType = + [&]() + { + if (ConstantBufferCanBeBoundToRootConstants(*constantBuffer)) + { + const u32 numValues = bufferDesc.Size / sizeof(u32); + totalNumValues += numValues; + + if (totalNumValues <= 64) + { + return EBindType::RootConstants; + } + } + + if (ConstantBufferCanBeBoundToRootDescriptor(*constantBuffer)) + { + return EBindType::RootDescriptor; + } + + return EBindType::DescriptorTable; + }(); + + if (bindingType == EBindType::DescriptorTable) + { + return Unexpected{ Errors::ConstantBufferMustBeBoundToDecriptorTable(bufferDesc.Name) }; + } + + if (bufferDesc.Name != nullptr && std::string_view{ bufferDesc.Name } == std::string_view{ "$Globals" }) + { + for (u32 globalIndex = 0; globalIndex < bufferDesc.Variables; ++globalIndex) + { + auto var = constantBuffer->GetVariableByIndex(globalIndex); + D3D12_SHADER_VARIABLE_DESC varDesc{}; + var->GetDesc(&varDesc); + + nameToBindingMap.emplace( + std::string{ varDesc.Name }, + BindingInfo{ + .RootParamIndex = static_cast(parameters.size()), + .OffsetIntoBuffer = varDesc.StartOffset, + .BindingSize = varDesc.Size, + .Type = bindingType + }); + } + } + else + { + nameToBindingMap.emplace( + std::string{ bindDesc.Name }, + BindingInfo{ + .RootParamIndex = static_cast(parameters.size()), + .OffsetIntoBuffer = 0, + .BindingSize = bufferDesc.Size, + .Type = bindingType + }); + } + + auto& stagingBuffer = stagingBuffers[static_cast(parameters.size())]; + stagingBuffer.Buffer.resize(bufferDesc.Size); + stagingBuffer.Type = bindingType; + + auto& parameter = parameters.emplace_back(); + + switch (bindingType) + { + case EBindType::RootConstants: + { + const u32 numValues = bufferDesc.Size / sizeof(u32); + parameter.InitAsConstants(numValues, bindDesc.BindPoint, bindDesc.Space); + break; + } + case EBindType::RootDescriptor: + { + parameter.InitAsConstantBufferView(bindDesc.BindPoint, bindDesc.Space); + break; + } + default: + { + std::unreachable(); + } + } + } + + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSigDesc{}; + rootSigDesc.Init_1_1(static_cast(parameters.size()), parameters.data(), 0u, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED + ); + + return ShaderBindingMap + { + InDevice.CreateRootSignature(rootSigDesc), + std::move(nameToBindingMap), + std::move(stagingBuffers) + }; + } + + const RootSignature& ShaderBindingMap::GetRootSig() const + { + return *m_RootSignature; + } + + ExpectedError ShaderBindingMap::StageBindingData(const ShaderBinding& InBinding) + { + if (const auto bindingIter = m_NameToBindingInfo.find(InBinding.GetName()); bindingIter != m_NameToBindingInfo.cend()) + { + const auto bindingData = InBinding.GetBindingData(); + if (bindingIter->second.BindingSize < bindingData.size_bytes()) + { + return Unexpected{ Errors::BindingIsSmallerThanBindingData( + bindingIter->first, + bindingIter->second.BindingSize, + bindingData.size_bytes()) }; + } + + auto& bindingBuffer = m_RootParamBuffers[bindingIter->second.RootParamIndex]; + + ThrowIfFalse(bindingBuffer.Buffer.size() > 0, "Shader binding buffer has a size of zero. It should have been created and initialized when processing the reflection data"); + + const u32 offset = bindingIter->second.OffsetIntoBuffer; + std::memcpy(bindingBuffer.Buffer.data() + offset, bindingData.data(), bindingData.size_bytes()); + } + else + { + return Unexpected{ Errors::BindingDoesNotExist(InBinding.GetName()) }; + } + + return {}; + } + + ShaderBindingMap::ShaderBindingMap( + SharedPtr&& InRootSignature, + BindingMapType&& InNameToBindingsMap, + StagingBufferMap&& InStagingBufferMap + ) + : m_RootSignature(std::move(InRootSignature)) + , m_NameToBindingInfo(std::move(InNameToBindingsMap)) + , m_RootParamBuffers(std::move(InStagingBufferMap)) + { + } +} \ No newline at end of file diff --git a/src/Private/D3D12/Shader/ShaderCompiler.cpp b/src/Private/D3D12/Shader/ShaderCompiler.cpp index 807bd4b0..a635d7c6 100644 --- a/src/Private/D3D12/Shader/ShaderCompiler.cpp +++ b/src/Private/D3D12/Shader/ShaderCompiler.cpp @@ -41,43 +41,58 @@ namespace stf return ret; } - - } - std::ostream& operator<<(std::ostream& InStream, const CompilationResult& InResult) + namespace Errors { - if (InResult.has_value()) + Error EmptyShaderCodeSource() + { + return Error::FromFragment<"Empty shader code source. Please specify either a path or give raw HLSL code as a string">(); + } + + Error EmptyVirtualPath() + { + return Error::FromFragment<"Empty path provided. Please provide either a virtual path that can be mapped by this manager, or an absolute/relative path">(); + } + + Error UnknownVirtualShaderMappingError() { - InStream << "Successful shader compilation"; + return Error::FromFragment<"Unknown error encountered while mapping the shader path.">(); } - else + + Error ResolvedPathIsInvalid(const std::string_view InAbsolutePath, const std::string_view InPath) + { + return Error::FromFragment<"Could not open file with fully resolved path of {0} and a potentially virtual path of {1}">(InAbsolutePath, InPath); + } + + Error ReportShaderCompilationError(const std::string_view InError) { - InStream << InResult.error(); + return Error::FromFragment<"DXC shader compilation error\n------------------------\n{}">(InError); } - return InStream; } ShaderCodeSource::ShaderCodeSource(std::string InSourceCode) : m_Source(std::move(InSourceCode)) - {} + { + } ShaderCodeSource::ShaderCodeSource(fs::path InSourcePath) : m_Source(std::move(InSourcePath)) - {} + { + } - ShaderCodeSource::ToStringResult ShaderCodeSource::ToString(const VirtualShaderDirectoryMappingManager& InManager) const + ExpectedError ShaderCodeSource::ToString(const VirtualShaderDirectoryMappingManager& InManager) const { return std::visit(OverloadSet{ - [](std::monostate) -> ToStringResult + [](std::monostate) -> ExpectedError { - return Unexpected{"Empty shader code source. Please specify either a path or give raw HLSL code as a string"}; + return Unexpected{Errors::EmptyShaderCodeSource()}; }, - [](const std::string& InSource) -> ToStringResult + [](const std::string& InSource) -> ExpectedError { return InSource; }, - [&InManager](const fs::path& InPath) -> ToStringResult + [&InManager](const fs::path& InPath) -> ExpectedError { using EErrorType = VirtualShaderDirectoryMappingManager::EErrorType; @@ -95,22 +110,22 @@ namespace stf } ) .transform_error( - [](const EErrorType InError) -> std::string + [](const EErrorType InError) -> Error { switch (InError) { case EErrorType::VirtualPathEmpty: { - return "Empty path provided. Please provide either a virtual path that can be mapped by this manager, or an absolute/relative path"; + return Errors::EmptyVirtualPath(); } default: { - return "Unknown error encountered while mapping the shader path."; + return Errors::UnknownVirtualShaderMappingError(); } } }) .and_then( - [&InPath](const fs::path& InAbsolutePath) -> ToStringResult + [&InPath](const fs::path& InAbsolutePath) -> ExpectedError { std::ifstream file(InAbsolutePath); @@ -122,7 +137,7 @@ namespace stf } else { - return Unexpected{ std::format("Could not open file with fully resolved path of {0} and a potentially virtual path of {1}", InAbsolutePath.string(), InPath.string())}; + return Unexpected{Errors::ResolvedPathIsInvalid(InAbsolutePath.string(), InPath.string())}; } }); @@ -148,11 +163,11 @@ namespace stf Init(); } - CompilationResult ShaderCompiler::CompileShader(const ShaderCompilationJobDesc& InJob) const + ExpectedError ShaderCompiler::CompileShader(const ShaderCompilationJobDesc& InJob) const { return InJob.Source.ToString(m_DirectoryManager) .and_then( - [this, &InJob](const std::string InSource) -> CompilationResult + [this, &InJob](const std::string InSource) -> ExpectedError { DxcBuffer sourceBuffer; sourceBuffer.Encoding = DXC_CP_ACP; @@ -279,7 +294,7 @@ namespace stf if (errorBuffer && errorBuffer->GetStringLength() > 0) { - return Unexpected{ std::string{errorBuffer->GetStringPointer()} }; + return Unexpected{ Errors::ReportShaderCompilationError(errorBuffer->GetStringPointer())}; } } diff --git a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp index 9c030af6..993cc4b7 100644 --- a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp +++ b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp @@ -1,9 +1,19 @@ +#include "Platform.h" #include "D3D12/Shader/ShaderReflectionUtils.h" #include "Utility/Exception.h" + namespace stf { + bool IsArray(ID3D12ShaderReflectionType& InType) + { + D3D12_SHADER_TYPE_DESC typeDesc{}; + InType.GetDesc(&typeDesc); + + return typeDesc.Elements > 0u; + } + bool IsOrContainsArray(ID3D12ShaderReflectionType& InType) { D3D12_SHADER_TYPE_DESC typeDesc{}; @@ -52,4 +62,29 @@ namespace stf return true; } + + bool ConstantBufferCanBeBoundToRootDescriptor(ID3D12ShaderReflectionConstantBuffer& InBuffer) + { + D3D12_SHADER_BUFFER_DESC bufferDesc; + ThrowIfFailed(InBuffer.GetDesc(&bufferDesc)); + + if (std::string_view{ bufferDesc.Name } == std::string_view{ "$Globals" }) + { + return true; + } + + for (u32 varIndex = 0; varIndex < bufferDesc.Variables; ++varIndex) + { + const auto varParam = InBuffer.GetVariableByIndex(varIndex); + D3D12_SHADER_VARIABLE_DESC varDesc{}; + ThrowIfFailed(varParam->GetDesc(&varDesc)); + + if (IsArray(*varParam->GetType())) + { + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/src/Private/Framework/ShaderTestCommon.cpp b/src/Private/Framework/ShaderTestCommon.cpp index 85a810fa..9a99c9f9 100644 --- a/src/Private/Framework/ShaderTestCommon.cpp +++ b/src/Private/Framework/ShaderTestCommon.cpp @@ -39,7 +39,7 @@ namespace stf return ""; } - Results::Results(ErrorTypeAndDescription InError) + Results::Results(Error InError) : m_Result(std::move(InError)) { } @@ -60,7 +60,7 @@ namespace stf { return InTestResults.NumFailed == 0; }, - [](const ErrorTypeAndDescription&) + [](const Error&) { return false; } }, m_Result); @@ -71,9 +71,9 @@ namespace stf return std::get_if(&m_Result); } - const ErrorTypeAndDescription* Results::GetTestRunError() const + const Error* Results::GetTestRunError() const { - return std::get_if(&m_Result); + return std::get_if(&m_Result); } bool operator==(const FailedAssert& InA, const FailedAssert& InB) @@ -172,12 +172,6 @@ namespace stf return InOs; } - std::ostream& operator<<(std::ostream& InOs, const ErrorTypeAndDescription& In) - { - InOs << "Type: " << Enum::ScopedName(In.Type) << "\n Description: " << In.Error; - return InOs; - } - std::ostream& operator<<(std::ostream& InOs, const Results& In) { std::visit( @@ -191,7 +185,7 @@ namespace stf { InOs << InTestResults; }, - [&InOs](const ErrorTypeAndDescription& InCompilationError) + [&InOs](const Error& InCompilationError) { InOs << InCompilationError; } diff --git a/src/Private/Framework/ShaderTestDescriptorManager.cpp b/src/Private/Framework/ShaderTestDescriptorManager.cpp deleted file mode 100644 index 2a0eaa4d..00000000 --- a/src/Private/Framework/ShaderTestDescriptorManager.cpp +++ /dev/null @@ -1,139 +0,0 @@ - -#include "Framework/ShaderTestDescriptorManager.h" - -#include "Utility/Tuple.h" - -namespace stf -{ - ShaderTestDescriptorManager::ShaderTestDescriptorManager(CreationParams InParams) - : m_Device(InParams.Device) - , m_GPUHeap(InParams.Device->CreateDescriptorHeap( - D3D12_DESCRIPTOR_HEAP_DESC - { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - .NumDescriptors = InParams.InitialSize, - .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, - .NodeMask = 0 - })) - , m_CPUHeap(InParams.Device->CreateDescriptorHeap( - D3D12_DESCRIPTOR_HEAP_DESC - { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - .NumDescriptors = InParams.InitialSize, - .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, - .NodeMask = 0 - })) - , m_Allocator({.NumDescriptors = InParams.InitialSize}) - , m_CPUDescriptors(m_CPUHeap->GetHeapRange()) - , m_GPUDescriptors(m_GPUHeap->GetHeapRange()) - { - } - - ShaderTestDescriptorManager::Expected ShaderTestDescriptorManager::CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) - { - using ErrorType = BindlessFreeListAllocator::EErrorType; - return m_Allocator.Allocate() - .transform( - [this, resource = std::move(InResource), &InDesc](const BindlessFreeListAllocator::BindlessIndex InHandle) mutable - { - - m_Device->CreateUnorderedAccessView(*resource, InDesc, ThrowIfUnexpected(m_CPUDescriptors[InHandle.GetIndex()])); - return ShaderTestUAV - { - .Resource = std::move(resource), - .Handle = InHandle - }; - }) - .transform_error( - [](const ErrorType InError) - { - ThrowIfFalse(InError == ErrorType::EmptyError); - - return EErrorType::AllocatorFull; - }); - } - - ShaderTestDescriptorManager::Expected ShaderTestDescriptorManager::ReleaseUAV(const ShaderTestUAV& InUAV) - { - using ErrorType = BindlessFreeListAllocator::EErrorType; - return - m_Allocator.Release(InUAV.Handle) - .transform_error( - [](const ErrorType InError) - { - ThrowIfFalse(InError == ErrorType::IndexAlreadyReleased); - return EErrorType::DescriptorAlreadyFree; - } - ); - } - - ShaderTestDescriptorManager::Expected> ShaderTestDescriptorManager::Resize(const u32 InNewSize) - { - if (m_Allocator.GetCapacity() >= InNewSize) - { - return Unexpected(EErrorType::AttemptedShrink); - } - - auto copyDescriptorsToNewHeap = - [this, InNewSize](const DescriptorRange InSrc, const D3D12_DESCRIPTOR_HEAP_FLAGS InFlags) - { - auto newHeap = m_Device->CreateDescriptorHeap( - D3D12_DESCRIPTOR_HEAP_DESC - { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - .NumDescriptors = InNewSize, - .Flags = InFlags, - .NodeMask = 0 - }); - const auto destRange = newHeap->GetHeapRange(); - m_Device->CopyDescriptors(destRange, InSrc, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - - return Tuple>{ destRange, std::move(newHeap) }; - }; - - const auto oldGPUHeap = m_GPUHeap; - - stf::tie(m_CPUDescriptors, m_CPUHeap) = copyDescriptorsToNewHeap(m_CPUDescriptors, D3D12_DESCRIPTOR_HEAP_FLAG_NONE); - stf::tie(m_GPUDescriptors, m_GPUHeap) = copyDescriptorsToNewHeap(m_CPUDescriptors, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); - return m_Allocator.Resize(InNewSize) - .transform( - [oldGPUHeap]() - { - return oldGPUHeap; - } - ).transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) - { - using enum BindlessFreeListAllocator::EErrorType; - - switch (InError) - { - case ShrinkAttempted: - { - return EErrorType::AttemptedShrink; - } - default: - { - return EErrorType::Unknown; - } - } - } - ); - } - - u32 ShaderTestDescriptorManager::GetSize() const - { - return m_Allocator.GetSize(); - } - - u32 ShaderTestDescriptorManager::GetCapacity() const - { - return m_Allocator.GetCapacity(); - } - - void ShaderTestDescriptorManager::SetDescriptorHeap(CommandList& InCommandList) - { - m_Device->CopyDescriptors(m_GPUDescriptors, m_CPUDescriptors, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - InCommandList.SetDescriptorHeaps(*m_GPUHeap); - } -} \ No newline at end of file diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index f140703e..fd4caff3 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -25,51 +25,16 @@ namespace stf }; } - ShaderTestDriver::ShaderTestDriver(CreationParams InParams) - : m_Device(std::move(InParams.Device)) - , m_CommandEngine(MakeShared( + ShaderTestDriver::ShaderTestDriver(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Device(std::move(InParams.Device)) + , m_CommandEngine(Object::New( CommandEngine::CreationParams { .Device = m_Device } )) - , m_DescriptorManager(MakeShared( - ShaderTestDescriptorManager::CreationParams{ - .Device = m_Device, - .InitialSize = 16 - } - )) - { - } - - SharedPtr ShaderTestDriver::CreateBuffer(const D3D12_HEAP_TYPE InType, const D3D12_RESOURCE_DESC1& InDesc) { - const auto heapProps = CD3DX12_HEAP_PROPERTIES(InType); - - return m_Device->CreateCommittedResource(heapProps, D3D12_HEAP_FLAG_NONE, InDesc, D3D12_BARRIER_LAYOUT_UNDEFINED); - } - - ShaderTestUAV ShaderTestDriver::CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) - { - return ThrowIfUnexpected(m_DescriptorManager->CreateUAV(InResource, InDesc) - .or_else( - [&, this](const ShaderTestDescriptorManager::EErrorType InErrorType) -> Expected - { - switch (InErrorType) - { - case ShaderTestDescriptorManager::EErrorType::AllocatorFull: - { - m_DeferredDeletedDescriptorHeaps.push_back( - ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2))); - return m_DescriptorManager->CreateUAV(InResource, InDesc); - } - default: - { - return Unexpected{ InErrorType }; - } - } - } - )); } TypeReaderIndex ShaderTestDriver::RegisterByteReader(std::string, MultiTypeByteReader InByteReader) @@ -89,121 +54,148 @@ namespace stf } ); } - - Expected ShaderTestDriver::RunShaderTest(TestDesc InTestDesc) + + ExpectedError ShaderTestDriver::RunShaderTest(TestDesc&& InTestDesc) { - auto pipelineState = CreatePipelineState(*InTestDesc.Shader.GetRootSig(), InTestDesc.Shader.GetCompiledShader()); + auto pipelineState = CreatePipelineState(InTestDesc.Shader->GetRootSig(), InTestDesc.Shader->GetCompiledShader()); const u32 bufferSizeInBytes = std::max(InTestDesc.TestBufferLayout.GetSizeOfTestData(), 4u); static constexpr u32 allocationBufferSizeInBytes = sizeof(AllocationBufferData); - auto assertBuffer = CreateBuffer(D3D12_HEAP_TYPE_DEFAULT, CD3DX12_RESOURCE_DESC1::Buffer(bufferSizeInBytes, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)); - auto allocationBuffer = CreateBuffer(D3D12_HEAP_TYPE_DEFAULT, CD3DX12_RESOURCE_DESC1::Buffer(allocationBufferSizeInBytes, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)); - auto readBackBuffer = CreateBuffer(D3D12_HEAP_TYPE_READBACK, CD3DX12_RESOURCE_DESC1::Buffer(bufferSizeInBytes)); - auto readBackAllocationBuffer = CreateBuffer(D3D12_HEAP_TYPE_READBACK, CD3DX12_RESOURCE_DESC1::Buffer(allocationBufferSizeInBytes)); - - const auto assertUAV = CreateUAV(assertBuffer, CreateRawUAVDesc(bufferSizeInBytes)); - const auto allocationUAV = CreateUAV(allocationBuffer, CreateRawUAVDesc(allocationBufferSizeInBytes)); + const auto dispatchDimensions = InTestDesc.DispatchConfig * InTestDesc.Shader->GetThreadGroupSize(); - InTestDesc.Bindings.append_range( std::array - { - ShaderBinding{ "stf::detail::DispatchDimensions", InTestDesc.DispatchConfig * InTestDesc.Shader.GetThreadGroupSize()}, - ShaderBinding{ "stf::detail::AllocationBufferIndex", allocationUAV.Handle.GetIndex() }, - ShaderBinding{ "stf::detail::TestDataBufferIndex", assertUAV.Handle.GetIndex() }, - ShaderBinding{ "stf::detail::Asserts", InTestDesc.TestBufferLayout.GetAssertSection() }, - ShaderBinding{ "stf::detail::Strings", InTestDesc.TestBufferLayout.GetStringSection() }, - ShaderBinding{ "stf::detail::Sections", InTestDesc.TestBufferLayout.GetSectionInfoSection() } - }); - - return InTestDesc.Shader.BindConstantBufferData(InTestDesc.Bindings) - .and_then( - [&, this]() - { - m_CommandEngine->Execute(InTestDesc.TestName, - [this, - &pipelineState, - &assertBuffer, - &allocationBuffer, - &readBackBuffer, - &readBackAllocationBuffer, - &InTestDesc - ] - (ScopedCommandContext& InContext) - { - InContext.Section("Test Setup", - [&](ScopedCommandContext& InContext) - { - InContext->SetPipelineState(*pipelineState); - m_DescriptorManager->SetDescriptorHeap(*InContext); - InContext->SetComputeRootSignature(*InTestDesc.Shader.GetRootSig()); - InContext->SetBufferUAV(*assertBuffer); - InContext->SetBufferUAV(*allocationBuffer); - - InTestDesc.Shader.SetConstantBufferData(*InContext); - } - ); - - InContext.Section("Test Dispatch", - [&](ScopedCommandContext& InContext) - { - InContext->Dispatch(InTestDesc.DispatchConfig.x, InTestDesc.DispatchConfig.y, InTestDesc.DispatchConfig.z); - } - ); + struct Resources + { + GPUResourceManager::BufferHandle AssertBuffer; + GPUResourceManager::BufferHandle AllocationBuffer; + GPUResourceManager::BufferUAVHandle AssertUAV; + GPUResourceManager::BufferUAVHandle AllocationUAV; + }; - InContext.Section("Results readback", - [&](ScopedCommandContext& InContext) - { - InContext->CopyBufferResource(*readBackBuffer, *assertBuffer); - InContext->CopyBufferResource(*readBackAllocationBuffer, *allocationBuffer); - } - ); - } - ); + struct Readbacks + { + const GPUResourceManager::ReadbackResultHandle AssertReadback; + const GPUResourceManager::ReadbackResultHandle AllocationReadback; + }; - m_CommandEngine->Flush(); - m_DeferredDeletedDescriptorHeaps.clear(); + return m_CommandEngine->Execute(InTestDesc.TestName, + [&](ScopedCommandContext& InContext) -> ExpectedError + { + return InContext.Section("Test Setup", + [&](ScopedCommandContext& InContext) -> ExpectedError + { + InContext->SetPipelineState(*pipelineState); - return m_DescriptorManager->ReleaseUAV(assertUAV) - .and_then( - [this, &allocationUAV]() + const auto assertBuffer = InContext.CreateBuffer( + GPUResourceManager::BufferDesc { - return m_DescriptorManager->ReleaseUAV(allocationUAV); + .Name = "Assert data buffer", + .RequestedSize = bufferSizeInBytes, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS } - ) - .transform( - [this, &readBackAllocationBuffer, &readBackBuffer, &InTestDesc]() + ); + + const auto allocationBuffer = InContext.CreateBuffer( + GPUResourceManager::BufferDesc { - return ReadbackResults(*readBackAllocationBuffer, *readBackBuffer, InTestDesc.Shader.GetThreadGroupSize() * InTestDesc.DispatchConfig, InTestDesc.TestBufferLayout); + .Name = "Allocation data buffer", + .RequestedSize = allocationBufferSizeInBytes, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS } - ) - .transform_error( - [](const ShaderTestDescriptorManager::EErrorType InErrorType) - { - using enum ShaderTestDescriptorManager::EErrorType; + ); + + const auto assertBufferUAV = InContext.CreateUAV(assertBuffer, + CreateRawUAVDesc(bufferSizeInBytes)); - switch (InErrorType) + const auto allocationBufferUAV = InContext.CreateUAV(allocationBuffer, + CreateRawUAVDesc(allocationBufferSizeInBytes)); + + return Resources + { + .AssertBuffer = assertBuffer, + .AllocationBuffer = allocationBuffer, + .AssertUAV = assertBufferUAV, + .AllocationUAV = allocationBufferUAV + }; + }) + .and_then( + [&](Resources&& InBuffers) -> ExpectedError + { + return InContext.Section("Test Dispatch", + [&](ScopedCommandContext& InContext) { - case DescriptorAlreadyFree: - { - return ErrorTypeAndDescription + return InContext.Dispatch(InTestDesc.DispatchConfig, InTestDesc.Shader, + [&](ScopedCommandShader& InShader) -> ExpectedError { - .Type = ETestRunErrorType::DescriptorManagement, - .Error = "Attempted to free an already freed descriptor." - }; - } - default: + std::ignore = InShader.StageBindingData(ShaderBinding{ "stf::detail::DispatchDimensions", dispatchDimensions }); + std::ignore = InShader.StageBindingData(ShaderBinding{ "stf::detail::Asserts", InTestDesc.TestBufferLayout.GetAssertSection() }); + std::ignore = InShader.StageBindingData(ShaderBinding{ "stf::detail::Strings", InTestDesc.TestBufferLayout.GetStringSection() }); + std::ignore = InShader.StageBindingData(ShaderBinding{ "stf::detail::Sections", InTestDesc.TestBufferLayout.GetSectionInfoSection() }); + + std::ignore = InShader.StageBindlessResource("stf::detail::AllocationBufferIndex", InBuffers.AllocationUAV); + std::ignore = InShader.StageBindlessResource("stf::detail::TestDataBufferIndex", InBuffers.AssertUAV); + + for (const auto& binding : InTestDesc.Bindings) + { + if (auto result = InShader.StageBindingData(binding); !result) + { + return result; + } + } + + return {}; + }); + }) + .transform( + [&]() -> Resources { - return ErrorTypeAndDescription - { - .Type = ETestRunErrorType::Unknown, - .Error = "Unknown descriptor management error." - }; - } - } - } - ); - } - ); + return InBuffers; + }); + }) + .and_then( + [&](const Resources& InBuffers) + { + return InContext.Section("Results readback", + [&](ScopedCommandContext& InContext) + { + return InContext.QueueReadback(InBuffers.AssertBuffer) + .and_then( + [&](const GPUResourceManager::ReadbackResultHandle InAssertReadback) + { + return InContext.QueueReadback(InBuffers.AllocationBuffer) + .transform( + [&](const GPUResourceManager::ReadbackResultHandle InAllocationReadback) + { + return Readbacks + { + .AssertReadback = InAssertReadback, + .AllocationReadback = InAllocationReadback + }; + }); + }); + }); + }); + }) + .and_then( + [&](const Readbacks& InReadbacks) + { + m_CommandEngine->Flush(); + return m_CommandEngine->ExecuteReadback(InReadbacks.AssertReadback, + [&](const MappedResource& InAssertData) + { + return m_CommandEngine->ExecuteReadback(InReadbacks.AllocationReadback, + [&](const MappedResource& InAllocationData) -> ExpectedError + { + const auto allocationData = InAllocationData.Get(); + AllocationBufferData data; + std::memcpy(&data, allocationData.data(), sizeof(AllocationBufferData)); + const auto assertData = InAssertData.Get(); + + return ProcessTestDataBuffer(data, dispatchDimensions, InTestDesc.TestBufferLayout, assertData, m_ByteReaderMap); + }); + }); + + }); } - + SharedPtr ShaderTestDriver::CreatePipelineState(const RootSignature& InRootSig, IDxcBlob* InShader) const { return m_Device->CreatePipelineState( diff --git a/src/Private/Framework/ShaderTestFixture.cpp b/src/Private/Framework/ShaderTestFixture.cpp index ef761063..b8e7f909 100644 --- a/src/Private/Framework/ShaderTestFixture.cpp +++ b/src/Private/Framework/ShaderTestFixture.cpp @@ -1,8 +1,11 @@ #include "Framework/ShaderTestFixture.h" +#include "D3D12/GPUDevice.h" +#include "D3D12/Shader/Shader.h" + #include "Framework/PIXCapturer.h" +#include "Framework/ShaderTestCommon.h" #include "Utility/EnumReflection.h" -#include "D3D12/GPUDevice.h" #include #include @@ -28,8 +31,8 @@ namespace stf std::vector ShaderTestFixture::cachedStats; ShaderTestFixture::ShaderTestFixture(FixtureDesc InParams) - : m_Device(MakeShared(InParams.GPUDeviceParams)) - , m_TestDriver(MakeShared( + : m_Device(Object::New(InParams.GPUDeviceParams)) + , m_TestDriver(Object::New( ShaderTestDriver::CreationParams { .Device = m_Device @@ -85,12 +88,12 @@ namespace stf ScopedDuration scope(std::format("ShaderTestFixture::RunCompileTimeTest: {}", InTestDesc.TestName)); return CompileShader("", EShaderType::Lib, std::move(InTestDesc.CompilationEnv), false) .transform( - [](SharedPtr) + [](CompiledShaderData) { return Results{ TestRunResults{} }; }) .or_else( - [](ErrorTypeAndDescription InError) -> Expected + [](Error InError) -> Expected { return Results{ std::move(InError) }; } @@ -116,24 +119,17 @@ namespace stf return CompileShader(InTestDesc.TestName, EShaderType::Compute, std::move(InTestDesc.CompilationEnv), takeCapture) .and_then( - [](SharedPtr InShader) -> Expected, ErrorTypeAndDescription> + [&](const CompiledShaderData& InCompilationResult) { - auto res = InShader->Init(); - return std::move(res) - .transform( - [shader = std::move(InShader)]() - { - return std::move(shader); - } - ); + return Shader::Make(InCompilationResult, *m_Device); }) .and_then( - [this, &InTestDesc, takeCapture](SharedPtr InShader) + [&](const SharedPtr& InShader) { const auto capturer = PIXCapturer(InTestDesc.TestName, takeCapture); return m_TestDriver->RunShaderTest( { - .Shader = *InShader, + .Shader = InShader, .TestBufferLayout{ InTestDesc.TestDataLayout }, .Bindings = std::move(InTestDesc.Bindings), .TestName = InTestDesc.TestName, @@ -141,14 +137,14 @@ namespace stf }); }) .or_else( - [](ErrorTypeAndDescription InError) -> Expected + [](Error InError) -> Expected { return Results{ std::move(InError) }; } ).value(); } - Expected, ErrorTypeAndDescription> ShaderTestFixture::CompileShader(const std::string_view InName, const EShaderType InType, CompilationEnvDesc InCompileDesc, const bool InTakingCapture) const + ExpectedError ShaderTestFixture::CompileShader(const std::string_view InName, const EShaderType InType, CompilationEnvDesc InCompileDesc, const bool InTakingCapture) const { ScopedDuration scope(std::format("ShaderTestFixture::CompileShader: {}", InName)); ShaderCompilationJobDesc job; @@ -172,22 +168,7 @@ namespace stf job.Flags = Enum::MakeFlags(EShaderCompileFlags::SkipOptimization, EShaderCompileFlags::O0); } - return m_Compiler - .CompileShader(job) - .transform( - [this](CompiledShaderData InData) - { - return MakeShared(ShaderTestShader::CreationParams{ .ShaderData = std::move(InData), .Device = m_Device }); - }) - .transform_error( - [](std::string InError) - { - return ErrorTypeAndDescription - { - .Type = ETestRunErrorType::ShaderCompilation, - .Error = std::move(InError) - }; - }); + return m_Compiler.CompileShader(job); } void ShaderTestFixture::RegisterByteReader(std::string InTypeIDName, MultiTypeByteReader InByteReader) diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp deleted file mode 100644 index a871ff6c..00000000 --- a/src/Private/Framework/ShaderTestShader.cpp +++ /dev/null @@ -1,205 +0,0 @@ - -#include "Framework/ShaderTestShader.h" -#include "D3D12/Shader/ShaderReflectionUtils.h" - -namespace stf -{ - ShaderTestShader::ShaderTestShader(CreationParams InParams) - : m_ShaderData(std::move(InParams.ShaderData)) - , m_Device(std::move(InParams.Device)) - , m_RootSignature() - , m_NameToBindingInfo() - , m_RootParamBuffers() - { - } - - Expected ShaderTestShader::BindConstantBufferData(const std::span InBindings) - { - return - Init() - .and_then( - [this, InBindings]() -> Expected - { - for (const auto& binding : InBindings) - { - const auto bindingInfo = m_NameToBindingInfo.find(binding.GetName()); - if (bindingInfo == m_NameToBindingInfo.cend()) - { - if (binding.GetName().contains("stf::detail::")) - { - continue; - } - else - { - return Unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::Binding, - .Error = std::format("No shader binding named {} found in test shader", binding.GetName()) - }); - } - } - - auto& bindingBuffer = m_RootParamBuffers[bindingInfo->second.RootParamIndex]; - - ThrowIfFalse(bindingBuffer.size() > 0, "Shader binding buffer has a size of zero. It should have been created and initialized when processing the reflection data"); - - const auto bindingData = binding.GetBindingData(); - if (bindingData.size_bytes() > bindingInfo->second.BindingSize) - { - return - Unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::Binding, - .Error = std::format("Shader binding {0} provided is larger than the expected binding size", binding.GetName()) - }); - } - - const u32 uintIndex = bindingInfo->second.OffsetIntoBuffer / sizeof(u32); - std::memcpy(bindingBuffer.data() + uintIndex, bindingData.data(), bindingData.size_bytes()); - } - - return {}; - }); - } - - void ShaderTestShader::SetConstantBufferData(CommandList& InList) const - { - for (const auto& [paramIndex, buffer] : m_RootParamBuffers) - { - InList.SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); - } - } - - uint3 ShaderTestShader::GetThreadGroupSize() const - { - uint3 ret; - m_ShaderData.GetReflection()->GetThreadGroupSize(&ret.x, &ret.y, &ret.z); - - return ret; - } - - RootSignature* ShaderTestShader::GetRootSig() const - { - return m_RootSignature.get(); - } - - IDxcBlob* ShaderTestShader::GetCompiledShader() const - { - return m_ShaderData.GetCompiledShader(); - } - - Expected ShaderTestShader::Init() - { - if (m_RootSignature) - { - return {}; - } - - const auto refl = m_ShaderData.GetReflection(); - - if (!refl) - { - return std::unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::RootSignatureGeneration, - .Error = "No reflection data generated. Did you compile your shader as a lib? Libs do not generate reflection data" - }); - } - - D3D12_SHADER_DESC shaderDesc{}; - refl->GetDesc(&shaderDesc); - - std::vector parameters; - parameters.reserve(shaderDesc.BoundResources); - - u32 totalNumValues = 0; - for (u32 boundIndex = 0; boundIndex < shaderDesc.BoundResources; ++boundIndex) - { - D3D12_SHADER_INPUT_BIND_DESC bindDesc{}; - refl->GetResourceBindingDesc(boundIndex, &bindDesc); - - if (bindDesc.Type != D3D_SIT_CBUFFER) - { - return std::unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::RootSignatureGeneration, - .Error = "Only constant buffers are supported for binding." - }); - } - - const auto constantBuffer = refl->GetConstantBufferByName(bindDesc.Name); - D3D12_SHADER_BUFFER_DESC bufferDesc{}; - ThrowIfFailed(constantBuffer->GetDesc(&bufferDesc)); - - if (!ConstantBufferCanBeBoundToRootConstants(*constantBuffer)) - { - return std::unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::RootSignatureGeneration, - .Error = std::format("Constant Buffer: {} can not be stored in root constants", bufferDesc.Name) - }); - } - - const u32 numValues = bufferDesc.Size / sizeof(u32); - totalNumValues += numValues; - - if (totalNumValues > 64) - { - return std::unexpected(ErrorTypeAndDescription - { - .Type = ETestRunErrorType::RootSignatureGeneration, - .Error = "Limit of 64 uints reached" - }); - } - - if (bufferDesc.Name != nullptr && std::string_view{ bufferDesc.Name } == std::string_view{ "$Globals" }) - { - for (u32 globalIndex = 0; globalIndex < bufferDesc.Variables; ++globalIndex) - { - auto var = constantBuffer->GetVariableByIndex(globalIndex); - D3D12_SHADER_VARIABLE_DESC varDesc{}; - var->GetDesc(&varDesc); - - m_NameToBindingInfo.emplace( - std::string{ varDesc.Name }, - BindingInfo{ - .RootParamIndex = static_cast(parameters.size()), - .OffsetIntoBuffer = varDesc.StartOffset, - .BindingSize = varDesc.Size - }); - } - } - else - { - m_NameToBindingInfo.emplace( - std::string{ bindDesc.Name }, - BindingInfo{ - .RootParamIndex = static_cast(parameters.size()), - .OffsetIntoBuffer = 0, - .BindingSize = bufferDesc.Size - }); - } - - m_RootParamBuffers[static_cast(parameters.size())].resize(bufferDesc.Size / sizeof(u32)); - - auto& parameter = parameters.emplace_back(); - parameter.InitAsConstants(numValues, bindDesc.BindPoint, bindDesc.Space); - } - - CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSig{}; - rootSig.Init_1_1(static_cast(parameters.size()), parameters.data(), 0u, nullptr, - D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED - ); - - m_RootSignature = m_Device->CreateRootSignature(rootSig); - - return {}; - } -} \ No newline at end of file diff --git a/src/Public/Container/FreeList.h b/src/Public/Container/FreeList.h new file mode 100644 index 00000000..8726f1bf --- /dev/null +++ b/src/Public/Container/FreeList.h @@ -0,0 +1,155 @@ + +#pragma once + +#include "Platform.h" + +#include "Container/RingBuffer.h" +#include "Utility/Error.h" +#include "Utility/Exception.h" +#include "Utility/Expected.h" +#include "Utility/VersionedIndex.h" +#include + +namespace stf +{ + template + class FreeList; + + template + class FreeListToken + { + friend class FreeList; + FreeListToken() {} + }; + + namespace Errors::FreeList + { + inline ErrorFragment StaleHandle(const u32 InExpectedVersion, const u32 InActualVersion) + { + return ErrorFragment::Make<"Handle version mismatch (Expected: {}, Actual Handle: {}). Likely a stale resource handle">(InExpectedVersion, InActualVersion); + } + + inline ErrorFragment HandleOutOfRange(const u32 InIndex) + { + return ErrorFragment::Make<"Handle index ({}) out of range. Is this handle from another free list?">(InIndex); + } + } + + template + class FreeList + { + public: + + class Handle + { + public: + + Handle(FreeListToken, u32VersionedIndex InIndex) + : m_Index(InIndex) + { + } + + u32VersionedIndex GetIndex(FreeListToken) const + { + return m_Index; + } + + friend bool operator==(const Handle&, const Handle&) = default; + friend bool operator!=(const Handle&, const Handle&) = default; + + private: + + u32VersionedIndex m_Index; + }; + + [[nodiscard]] Handle Manage(T&& InResource) + { + return m_FreeList.pop_front() + .and_then( + [&](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const u32 index = InVersionedIndex.GetIndex(); + const u32 version = InVersionedIndex.GetVersion(); + auto& resource = m_Resources[index]; + + ThrowIfFalse(version == resource.Version); + resource.Resource = std::move(InResource); + + return Handle{ FreeListToken{}, InVersionedIndex }; + } + ) + .or_else( + [&](const Error&) -> ExpectedError + { + const u32VersionedIndex versionedIndex{ static_cast(m_Resources.size()) }; + + m_Resources.emplace_back(std::move(InResource), versionedIndex.GetVersion()); + + return Handle{ FreeListToken{}, versionedIndex }; + } + ).value(); + } + + ExpectedError Release(const Handle InHandle) + { + return InternalValidateHandle(InHandle) + .and_then( + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const auto nextVersion = InVersionedIndex.Next(); + const auto index = nextVersion.GetIndex(); + m_Resources[index].Version = nextVersion.GetVersion(); + return {}; + } + ); + } + + ExpectedError ValidateHandle(const Handle InHandle) const + { + return InternalValidateHandle(InHandle).transform([](const auto&) {}); + } + + ExpectedError Get(const Handle InHandle) const + { + return InternalValidateHandle(InHandle) + .and_then( + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const auto index = InVersionedIndex.GetIndex(); + + return m_Resources[index].Resource; + } + ); + } + + private: + + ExpectedError InternalValidateHandle(const Handle InHandle) const + { + const auto versionedIndex = InHandle.GetIndex(FreeListToken {}); + const u32 index = versionedIndex.GetIndex(); + const u32 version = versionedIndex.GetVersion(); + + if (index >= static_cast(m_Resources.size())) + { + return Unexpected{ Error{ Errors::FreeList::HandleOutOfRange(index) } }; + } + + if (m_Resources[index].Version != version) + { + return Unexpected{ Error{ Errors::FreeList::StaleHandle(m_Resources[index].Version, version) } }; + } + + return versionedIndex; + } + + struct VersionedResource + { + T Resource; + u32 Version{}; + }; + + std::vector m_Resources; + RingBuffer m_FreeList; + }; +} \ No newline at end of file diff --git a/src/Public/Container/RingBuffer.h b/src/Public/Container/RingBuffer.h index 197e8c82..2c7a8d50 100644 --- a/src/Public/Container/RingBuffer.h +++ b/src/Public/Container/RingBuffer.h @@ -1,7 +1,7 @@ #pragma once +#include "Utility/Error.h" #include "Utility/Exception.h" -#include "Utility/Expected.h" #include "Platform.h" #include #include @@ -9,6 +9,20 @@ namespace stf { + + namespace Errors::RingBuffer + { + inline ErrorFragment Empty() + { + return ErrorFragment::Make<"Ring buffer was empty">(); + } + + inline ErrorFragment AttemptedShrink(const u64 InCurrentCapacity, const u64 InRequestedCapacity) + { + return ErrorFragment::Make<"Attempted shrink of ring buffer which is unsupported. Current Capacity: {}, Requested Capacity: {}">(InCurrentCapacity, InRequestedCapacity); + } + } + template class RingBuffer { @@ -26,16 +40,6 @@ namespace stf Backwards }; - enum class EErrorType - { - Success, - EmptyBuffer, - AttemptedShrink - }; - - template - using Expected = Expected; - template class Iterator { @@ -49,7 +53,8 @@ namespace stf Iterator(buffer_type InBuffer, const u64 InIndex) : m_Buffer(InBuffer) , m_Index(InIndex) - {} + { + } Iterator& operator++() { @@ -142,7 +147,8 @@ namespace stf RingBuffer() : RingBuffer(0) - {} + { + } explicit RingBuffer(const u64 InSize) : m_Data(InSize + 1) @@ -156,7 +162,7 @@ namespace stf { if (m_Size == capacity()) { - ThrowIfUnexpected(resize(m_Size * 2)); + ThrowIfUnexpected(resize(m_Data.size() * 2)); } m_Data[m_TailIndex] = In; @@ -169,7 +175,7 @@ namespace stf { if (m_Size == capacity()) { - ThrowIfUnexpected(resize(m_Size * 2)); + ThrowIfUnexpected(resize(m_Data.size() * 2)); } m_Data[m_TailIndex] = std::move(In); @@ -178,11 +184,11 @@ namespace stf ++m_Size; } - Expected pop_front() + ExpectedError pop_front() { if (empty()) { - return Unexpected(EErrorType::EmptyBuffer); + return Unexpected(Error{ Errors::RingBuffer::Empty() }); } const auto prevHeadIndex = m_HeadIndex; @@ -277,12 +283,12 @@ namespace stf return m_Data[m_HeadIndex].value(); } - Expected resize(const u64 InNewSize) + ExpectedError resize(const u64 InNewSize) { const u64 newCapacity = InNewSize + 1; if (newCapacity < m_Data.size()) { - return Unexpected{ EErrorType::AttemptedShrink }; + return Unexpected{ Error {Errors::RingBuffer::AttemptedShrink(size(), InNewSize)}}; } if (newCapacity == m_Data.size()) diff --git a/src/Public/D3D12/BindlessFreeListAllocator.h b/src/Public/D3D12/BindlessFreeListAllocator.h index abaf3be2..85d02e2d 100644 --- a/src/Public/D3D12/BindlessFreeListAllocator.h +++ b/src/Public/D3D12/BindlessFreeListAllocator.h @@ -3,30 +3,26 @@ #include "Platform.h" #include "Container/RingBuffer.h" -#include "Utility/Expected.h" +#include "Utility/Error.h" #include #include namespace stf { + namespace Errors::BindlessFreeListAllocator + { + ErrorFragment Empty(); + ErrorFragment InvalidIndex(const u32 InIndex); + ErrorFragment IndexAlreadyReleased(const u32 InIndex); + ErrorFragment ShrinkAttempted(const u32 InCurrentSize, const u32 InRequestedSize); + } + class BindlessFreeListAllocator { struct Private { explicit Private() = default; }; public: - enum class EErrorType - { - UnknownError, - EmptyError, - InvalidIndex, - IndexAlreadyReleased, - ShrinkAttempted - }; - - template - using Expected = Expected; - class BindlessIndex { public: @@ -51,16 +47,16 @@ namespace stf BindlessFreeListAllocator() = default; BindlessFreeListAllocator(CreationParams InParams); - [[nodiscard]] Expected Allocate(); - Expected Release(const BindlessIndex InIndex); - Expected Resize(const u32 InNewSize); + [[nodiscard]] ExpectedError Allocate(); + ExpectedError Release(const BindlessIndex InIndex); + ExpectedError Resize(const u32 InNewSize); u32 GetSize() const; u32 GetCapacity() const; - private: + ExpectedError IsAllocated(const BindlessIndex InIndex) const; - using EBufferError = RingBuffer::EErrorType; + private: RingBuffer m_FreeList; std::vector m_FreeSet; diff --git a/src/Public/D3D12/CommandAllocator.h b/src/Public/D3D12/CommandAllocator.h index 78906e30..3d9989f9 100644 --- a/src/Public/D3D12/CommandAllocator.h +++ b/src/Public/D3D12/CommandAllocator.h @@ -8,7 +8,8 @@ namespace stf { - class CommandAllocator : Object + class CommandAllocator + : public Object { public: @@ -18,7 +19,7 @@ namespace stf D3D12_COMMAND_LIST_TYPE Type = D3D12_COMMAND_LIST_TYPE_DIRECT; }; - CommandAllocator(CreationParams InParams); + CommandAllocator(ObjectToken, CreationParams InParams); ID3D12CommandAllocator* GetRaw() const; operator ID3D12CommandAllocator* () const; diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 040b391c..4df7b5cf 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -4,8 +4,12 @@ #include "D3D12/CommandAllocator.h" #include "D3D12/CommandQueue.h" #include "D3D12/GPUDevice.h" +#include "D3D12/GPUResourceManager.h" +#include "D3D12/Shader/Shader.h" +#include "Utility/Error.h" #include "Utility/FunctionTraits.h" +#include "Utility/HLSLTypes.h" #include "Utility/Lambda.h" #include "Utility/Object.h" #include "Utility/Pointer.h" @@ -20,51 +24,169 @@ namespace stf CommandEngineToken() = default; }; + class CommandEngine; class ScopedCommandContext; + class ScopedCommandShader; template - concept CommandEngineFuncType = LambdaType && requires() + concept CommandEngineFuncType = LambdaType && requires { requires T::ParamTypes::Size == 1; requires std::same_as, ScopedCommandContext&>; + requires std::same_as>; }; template concept ExecuteLambdaType = - !CommandEngineFuncType && + !CommandEngineFuncType && TFuncTraits::ParamTypes::Size == 1 && - std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&>; + std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && + requires (T InFunc, ScopedCommandContext& InContext) + { + { InFunc(InContext) } -> ExpectedErrorType; + }; + + template + concept VoidExecuteLambdaType = ExecuteLambdaType && + requires (T InFunc, ScopedCommandContext& InContext) + { + { InFunc(InContext) } -> ExpectedErrorWithValueType; + }; + + template + concept BindShaderLambdaType = !LambdaType && + TFuncTraits::ParamTypes::Size == 1 && + std::is_same_v::ParamTypes::template Type<0>, ScopedCommandShader&>&& + std::is_same_v::ReturnType, ExpectedError>; + + class ScopedGPUResourceManager + { + public: + + ScopedGPUResourceManager(const SharedPtr& InResourceManager); + + ~ScopedGPUResourceManager() noexcept; + + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData); + + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc); + + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); + + ExpectedError QueueReadback(CommandList& InList, const GPUResourceManager::BufferHandle InBufferHandle); + + void SetUAV(CommandList& InList, const GPUResourceManager::BufferUAVHandle InHandle); + + void SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); + + void SetDescriptorHeap(CommandList& InList); + + ExpectedError GetDescriptorIndex(const GPUResourceManager::DescriptorOpaqueHandle InHandle) const; + + private: + + SharedPtr m_ResourceManager; + std::vector m_ConstantBuffers; + std::vector m_CBVs; + std::vector m_Buffers; + std::vector m_BufferUAVs; + }; + + class CommandShaderToken + { + friend class ScopedCommandShader; + CommandShaderToken() = default; + }; + + class ScopedCommandShader + { + public: + + ScopedCommandShader( + const SharedPtr& InShader, + const SharedPtr& InResourceManager, + const SharedPtr& InList); + ScopedCommandShader(const ScopedCommandShader&) = delete; + ScopedCommandShader(ScopedCommandShader&&) = delete; + ScopedCommandShader& operator=(const ScopedCommandShader&) = delete; + ScopedCommandShader& operator=(ScopedCommandShader&&) = delete; + + ExpectedError StageBindingData(const ShaderBinding& InBinding); + ExpectedError StageBindlessResource(std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); + + private: + + SharedPtr m_Shader; + SharedPtr m_ResourceManager; + SharedPtr m_List; + }; class ScopedCommandContext { public: - ScopedCommandContext(CommandEngineToken, CommandList* InList) - : m_List(InList) - {} + ScopedCommandContext(CommandEngineToken, + const SharedPtr& InList, + const SharedPtr& InResourceManager + ); - CommandList* operator->() const - { - return m_List; - } + ScopedCommandContext(const ScopedCommandContext&) = delete; + ScopedCommandContext(ScopedCommandContext&&) = delete; + ScopedCommandContext& operator=(const ScopedCommandContext&) = delete; + ScopedCommandContext& operator=(ScopedCommandContext&&) = delete; - CommandList& operator*() const - { - return *m_List; - } + CommandList* operator->() const; + + CommandList& operator*() const; template - void Section(const std::string_view InName, InLambdaType&& InFunc) + auto Section(const std::string_view InName, InLambdaType&& InFunc) { PIXScopedEvent(m_List->GetRaw(), PIX_COLOR(0, 255, 0), "%s", InName.data()); - InFunc(*this); + return InFunc(*this); + } + + CommandList* GetList() const; + + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InDesc); + + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); + + void SetUAV(const GPUResourceManager::BufferUAVHandle InHandle); + + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData); + + ExpectedError QueueReadback(const GPUResourceManager::BufferHandle InBufferHandle); + + void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); + + template + ExpectedError Dispatch(const uint3 InDispatchConfig, const SharedPtr& InShader, BindFunc&& InFunc) + { + PreBindShader(InShader); + ScopedCommandShader shader(InShader, m_ResourceManager, m_List); + + return InFunc(shader) + .transform( + [&]() + { + SetShaderStateAndDispatch(InShader, InDispatchConfig); + } + ); } private: - CommandList* m_List = nullptr; + + void PreBindShader(const SharedPtr& InShader); + void SetShaderStateAndDispatch(const SharedPtr& InShader, const uint3 InDispatchConfig); + + SharedPtr m_List = nullptr; + SharedPtr m_BoundShader = nullptr; + SharedPtr m_ResourceManager = nullptr; }; - class CommandEngine : Object + class CommandEngine + : public Object { public: @@ -73,35 +195,10 @@ namespace stf SharedPtr Device; }; - CommandEngine(CreationParams InParams); - - template - void Execute(const InLambdaType& InFunc) - { - auto allocator = [this]() - { - if (m_Allocators.size() == 0 || !m_Queue->HasFencePointBeenReached(m_Allocators.front().FencePoint)) - { - return m_Device->CreateCommandAllocator - ( - D3D12_COMMAND_LIST_TYPE_DIRECT, - "Command Allocator" - ); - } - - return std::move(ThrowIfUnexpected(m_Allocators.pop_front()).Allocator); - }(); - - m_List->Reset(allocator); - ScopedCommandContext context(CommandEngineToken{}, m_List.get()); - InFunc(context); - - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - m_Queue->ExecuteCommandList(*m_List); - } + CommandEngine(ObjectToken, const CreationParams& InParams); template - void Execute(InLambdaType&& InFunc) + auto Execute(InLambdaType&& InFunc) { auto allocator = [this]() { @@ -118,24 +215,75 @@ namespace stf }(); m_List->Reset(*allocator); - ScopedCommandContext context(CommandEngineToken{}, m_List.get()); - InFunc(context); + ScopedCommandContext context(CommandEngineToken{}, m_List + , m_ResourceManager + ); - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - m_Queue->ExecuteCommandList(*m_List); + return ExecuteImpl(context, std::move(allocator), std::forward(InFunc)); } template - void Execute(const std::string_view InName, InLambdaType&& InFunc) + auto Execute(const std::string_view InName, InLambdaType&& InFunc) { PIXScopedEvent(m_Queue->GetRaw(), 0ull, "%s", InName.data()); - Execute(std::forward(InFunc)); + return Execute(std::forward(InFunc)); + } + + template + auto ExecuteReadback(const GPUResourceManager::ReadbackResultHandle InReadbackHandle, InFuncType&& InFunc) + { + return m_ResourceManager->ExecuteReadback(InReadbackHandle, std::forward(InFunc)); } void Flush(); private: + template + ExpectedError ExecuteImpl(ScopedCommandContext& InContext, SharedPtr&& InCommandAllocator, InLambdaType&& InFunc) + { + return InFunc(InContext) + .and_then( + [&]() -> ExpectedError + { + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); + m_Queue->ExecuteCommandList(*m_List); + return {}; + } + ) + .or_else( + [&](Error&& InError) -> ExpectedError + { + m_List->Close(); + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); + return Unexpected{ InError }; + } + ); + } + + template + requires (!VoidExecuteLambdaType) + auto ExecuteImpl(ScopedCommandContext& InContext, SharedPtr&& InCommandAllocator, InLambdaType&& InFunc) + { + return InFunc(InContext) + .transform( + [&](auto&& InResult) + { + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); + m_Queue->ExecuteCommandList(*m_List); + return InResult; + } + ) + .transform_error( + [&](Error&& InError) + { + m_List->Close(); + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); + return InError; + } + ); + } + struct FencedAllocator { SharedPtr Allocator; @@ -145,6 +293,7 @@ namespace stf SharedPtr m_Device; SharedPtr m_Queue; SharedPtr m_List; + SharedPtr m_ResourceManager; RingBuffer m_Allocators; }; } \ No newline at end of file diff --git a/src/Public/D3D12/CommandList.h b/src/Public/D3D12/CommandList.h index 49c19a92..dde7fe7c 100644 --- a/src/Public/D3D12/CommandList.h +++ b/src/Public/D3D12/CommandList.h @@ -21,7 +21,8 @@ namespace stf template concept RootSigConstantType = std::is_trivial_v && sizeof(T) == 4; - class CommandList : Object + class CommandList + : public Object { public: @@ -30,8 +31,7 @@ namespace stf ComPtr List = nullptr; }; - CommandList() = default; - CommandList(CreationParams InParams); + CommandList(ObjectToken, CreationParams InParams); void CopyBufferResource(GPUResource& InDest, GPUResource& InSource); @@ -44,10 +44,9 @@ namespace stf m_List->SetComputeRoot32BitConstant(InRootParamIndex, val, InOffset); } - template - void SetComputeRoot32BitConstants(const u32 InRootParamIndex, const std::span InVals, const u32 InOffset) + void SetComputeRoot32BitConstants(const u32 InRootParamIndex, const std::span InVals, const u32 InOffset) { - m_List->SetComputeRoot32BitConstants(InRootParamIndex, static_cast(InVals.size()), InVals.data(), InOffset); + m_List->SetComputeRoot32BitConstants(InRootParamIndex, static_cast(InVals.size()) / 4u, InVals.data(), InOffset); } void SetComputeRootSignature(const RootSignature& InRootSig); @@ -67,6 +66,8 @@ namespace stf m_List->SetGraphicsRoot32BitConstants(InRootParamIndex, static_cast(InVals.size()), InVals.data(), InOffset); } + void SetComputeRootConstantBufferView(const u32 InRootParamIndex, GPUResource& InConstantBuffer); + void SetGraphicsRootSignature(const RootSignature& InRootSig); void SetPipelineState(const PipelineState& InState); diff --git a/src/Public/D3D12/CommandQueue.h b/src/Public/D3D12/CommandQueue.h index d5c67299..70328dba 100644 --- a/src/Public/D3D12/CommandQueue.h +++ b/src/Public/D3D12/CommandQueue.h @@ -3,6 +3,7 @@ #include "D3D12/Fence.h" #include "Utility/Object.h" #include "Utility/Pointer.h" +#include "Utility/Time.h" #include @@ -10,7 +11,8 @@ namespace stf { class CommandList; - class CommandQueue : Object + class CommandQueue + : public Object { public: @@ -20,13 +22,15 @@ namespace stf SharedPtr Fence{}; }; - CommandQueue() = default; - CommandQueue(CreationParams InParams); + CommandQueue(ObjectToken, CreationParams InParams); ~CommandQueue(); bool HasFencePointBeenReached(const Fence::FencePoint& InFencePoint) const; - Fence::FencePoint Signal(); - void WaitOnFence(const Fence::FencePoint& InFencePoint); + [[nodiscard]] Fence::FencePoint Signal(); + [[nodiscard]] Fence::FencePoint NextSignal(); + Fence::Expected WaitOnFenceCPU(const Fence::FencePoint& InFencePoint); + Fence::Expected WaitOnFenceCPU(const Fence::FencePoint& InFencePoint, const Milliseconds InTimeout); + void WaitOnFenceGPU(const Fence::FencePoint& InFencePoint); void SyncWithQueue(CommandQueue& InQueue); void FlushQueue(); diff --git a/src/Public/D3D12/Descriptor.h b/src/Public/D3D12/Descriptor.h index 04c1fd5a..c5ce56cd 100644 --- a/src/Public/D3D12/Descriptor.h +++ b/src/Public/D3D12/Descriptor.h @@ -35,6 +35,15 @@ namespace stf return m_HeapIndex; } + friend bool operator==(const DescriptorHandle& InA, const DescriptorHandle& InB) + { + return InA.m_CPUAddress.ptr == InB.m_CPUAddress.ptr && + InA.m_GPUAddress.ptr == InB.m_GPUAddress.ptr && + InA.m_HeapIndex == InB.m_HeapIndex; + } + + friend bool operator!=(const DescriptorHandle&, const DescriptorHandle&) = default; + private: D3D12_CPU_DESCRIPTOR_HANDLE m_CPUAddress{ 0 }; diff --git a/src/Public/D3D12/DescriptorHeap.h b/src/Public/D3D12/DescriptorHeap.h index 5f3cf2a3..80a9dfd7 100644 --- a/src/Public/D3D12/DescriptorHeap.h +++ b/src/Public/D3D12/DescriptorHeap.h @@ -10,7 +10,8 @@ namespace stf { - class DescriptorHeap : Object + class DescriptorHeap + : public Object { public: @@ -28,8 +29,7 @@ namespace stf template using Expected = Expected; - DescriptorHeap() = default; - DescriptorHeap(Desc InParams) noexcept; + DescriptorHeap(ObjectToken, Desc InParams) noexcept; Expected CreateDescriptorRange(const u32 InBeginIndex, const u32 InNum) const; Expected CreateDescriptorHandle(const u32 InIndex) const; diff --git a/src/Public/D3D12/DescriptorManager.h b/src/Public/D3D12/DescriptorManager.h new file mode 100644 index 00000000..607b0131 --- /dev/null +++ b/src/Public/D3D12/DescriptorManager.h @@ -0,0 +1,90 @@ +#pragma once + +#include "Platform.h" + +#include "D3D12/BindlessFreeListAllocator.h" +#include "D3D12/DescriptorHeap.h" +#include "D3D12/CommandList.h" +#include "D3D12/GPUDevice.h" + +#include "Utility/Error.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" + +namespace stf +{ + namespace Errors + { + ErrorFragment DescriptorManagerIsFull(); + ErrorFragment UnknownDescriptorManagerError(); + + ErrorFragment InvalidDescriptorManagerDescriptor(const u32 InIndex); + ErrorFragment DescriptorManagerDescriptorNotAllocated(const u32 InIndex); + ErrorFragment ShrinkAttemptedOnDescriptorManager(const u32 InCurrentSize, const u32 InRequestedSize); + } + + class DescriptorManager + : public Object + { + + struct Token {}; + + public: + + struct CreationParams + { + SharedPtr Device; + u32 InitialSize = 16u; + }; + + enum class EErrorType + { + Unknown, + AllocatorFull, + AttemptedShrink, + DescriptorAlreadyFree, + DescriptorNotAllocated, + DescriptorInvalid + }; + + class Descriptor + { + public: + + Descriptor(Token, const SharedPtr& InOwner, BindlessFreeListAllocator::BindlessIndex InIndex); + + ExpectedError Resolve() const; + + DescriptorManager* GetOwner(Token) const; + + BindlessFreeListAllocator::BindlessIndex GetIndex(Token) const; + + private: + + SharedPtr m_Owner = nullptr; + BindlessFreeListAllocator::BindlessIndex m_Index; + }; + + DescriptorManager(ObjectToken, const CreationParams& InParams); + + + ExpectedError Acquire(); + ExpectedError Release(const Descriptor& InDescriptor); + + ExpectedError> Resize(const u32 InNewSize); + + u32 GetSize() const; + u32 GetCapacity() const; + + void SetDescriptorHeap(CommandList& InCommandList); + + ExpectedError ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const; + + private: + + SharedPtr m_Device; + SharedPtr m_GPUHeap; + SharedPtr m_CPUHeap; + BindlessFreeListAllocator m_Allocator; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/Fence.h b/src/Public/D3D12/Fence.h index 4af4f40a..3c9a25d2 100644 --- a/src/Public/D3D12/Fence.h +++ b/src/Public/D3D12/Fence.h @@ -4,12 +4,13 @@ #include "Utility/Expected.h" #include "Utility/Object.h" #include "Utility/Pointer.h" - +#include "Utility/Time.h" #include namespace stf { - class Fence : Object + class Fence + : public Object { public: @@ -35,20 +36,40 @@ namespace stf u64 InitialValue = 0; }; - Fence() = default; - Fence(CreationParams InParams); + enum class EErrorType + { + FencePointIsFromAnotherFence, + ErrorCreatingEvent, + WaitFailed + }; - [[nodiscard]] FencePoint Signal(ID3D12CommandQueue* InQueue); - bool WaitCPU(const FencePoint& InFencePoint) const; - void WaitOnQueue(ID3D12CommandQueue* InQueue) const; - Expected HasCompleted(const FencePoint& InFencePoint) const; + enum class ECPUWaitResult + { + FenceAlreadyReached, + WaitTimedOut, + WaitFenceReached + }; + + template + using Expected = Expected; + + Fence(ObjectToken, CreationParams InParams); + + [[nodiscard]] FencePoint Signal(ID3D12CommandQueue& InQueue); + [[nodiscard]] FencePoint NextSignal(); + Expected WaitCPU(const FencePoint& InFencePoint) const; + Expected WaitCPU(const FencePoint& InFencePoint, const Milliseconds InTimeout) const; + void WaitOnQueue(ID3D12CommandQueue& InQueue, const FencePoint& InFencePoint) const; + Expected HasCompleted(const FencePoint& InFencePoint) const; ID3D12Fence* GetRaw() const; operator ID3D12Fence* () const; private: - bool Validate(const FencePoint& InFencePoint) const; + Expected Validate(const FencePoint& InFencePoint) const; + + Expected WaitForFencePoint(const FencePoint& InFencePoint, DWORD InTimeout) const; ComPtr m_Fence = nullptr; u64 m_NextValue = 0; diff --git a/src/Public/D3D12/FencedResourceFreeList.h b/src/Public/D3D12/FencedResourceFreeList.h new file mode 100644 index 00000000..6c72447b --- /dev/null +++ b/src/Public/D3D12/FencedResourceFreeList.h @@ -0,0 +1,199 @@ + +#pragma once + +#include "Platform.h" + +#include "Container/RingBuffer.h" +#include "D3D12/CommandQueue.h" +#include "D3D12/Fence.h" +#include "Utility/Error.h" +#include "Utility/Exception.h" +#include "Utility/Expected.h" +#include "Utility/Pointer.h" +#include "Utility/VersionedIndex.h" + +#include +#include +#include + +namespace stf +{ + template + class FencedResourceFreeList; + + template + class FencedResourceFreeListToken + { + friend class FencedResourceFreeList; + FencedResourceFreeListToken() {} + }; + + namespace Errors::FencedResourceFreeList + { + inline ErrorFragment StaleHandle(const u32 InExpectedVersion, const u32 InActualVersion) + { + return ErrorFragment::Make<"Handle version mismatch (Expected: {}, Actual Handle: {}). Likely a stale resource handle">(InExpectedVersion, InActualVersion); + } + + inline ErrorFragment HandleOutOfRange(const u32 InIndex) + { + return ErrorFragment::Make<"Handle index ({}) out of range. Is this handle from another free list?">(InIndex); + } + } + + template + class FencedResourceFreeList + { + public: + + struct CreationParams + { + SharedPtr Queue; + }; + + class Handle + { + public: + + Handle(FencedResourceFreeListToken, u32VersionedIndex InIndex) + : m_Index(InIndex) + { + } + + u32VersionedIndex GetIndex(FencedResourceFreeListToken) const + { + return m_Index; + } + + friend bool operator==(const Handle&, const Handle&) = default; + friend bool operator!=(const Handle&, const Handle&) = default; + + private: + + u32VersionedIndex m_Index; + }; + + FencedResourceFreeList(const CreationParams& InParams) + : m_Queue(InParams.Queue) + { + } + + [[nodiscard]] Handle Manage(T&& InResource) + { + TickDeferredReleases(); + + return m_FreeList.pop_front() + .and_then( + [&](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const u32 index = InVersionedIndex.GetIndex(); + const u32 version = InVersionedIndex.GetVersion(); + auto& resource = m_Resources[index]; + + ThrowIfFalse(version == resource.Version); + resource.Resource = std::move(InResource); + + return Handle{ FencedResourceFreeListToken{}, InVersionedIndex }; + } + ) + .or_else( + [&](const Error&) -> ExpectedError + { + const u32VersionedIndex versionedIndex{ static_cast(m_Resources.size()) }; + + m_Resources.emplace_back(std::move(InResource), versionedIndex.GetVersion()); + + return Handle{ FencedResourceFreeListToken{}, versionedIndex }; + } + ).value(); + } + + ExpectedError Release(const Handle InHandle) + { + return InternalValidateHandle(InHandle) + .and_then( + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const auto nextVersion = InVersionedIndex.Next(); + const auto index = nextVersion.GetIndex(); + m_Resources[index].Version = nextVersion.GetVersion(); + m_DeferredReleasedHandles.push_back( + FencedResource + { + .VersionedIndex = nextVersion, + .FencePoint = m_Queue->Signal() + }); + + return {}; + } + ); + } + + ExpectedError ValidateHandle(const Handle InHandle) const + { + return InternalValidateHandle(InHandle).transform([](const auto&) {}); + } + + ExpectedError Get(const Handle InHandle) const + { + return InternalValidateHandle(InHandle) + .and_then( + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const auto index = InVersionedIndex.GetIndex(); + + return m_Resources[index].Resource; + } + ); + } + + private: + + ExpectedError InternalValidateHandle(const Handle InHandle) const + { + const auto versionedIndex = InHandle.GetIndex(FencedResourceFreeListToken {}); + const u32 index = versionedIndex.GetIndex(); + const u32 version = versionedIndex.GetVersion(); + + if (index >= static_cast(m_Resources.size())) + { + return Unexpected{ Error{ Errors::FencedResourceFreeList::HandleOutOfRange(index) } }; + } + + if (m_Resources[index].Version != version) + { + return Unexpected{ Error{ Errors::FencedResourceFreeList::StaleHandle(m_Resources[index].Version, version) } }; + } + + return versionedIndex; + } + + void TickDeferredReleases() + { + while (!m_DeferredReleasedHandles.empty() && m_Queue->HasFencePointBeenReached(m_DeferredReleasedHandles.front().FencePoint)) + { + const auto& releasedResource = ThrowIfUnexpected(m_DeferredReleasedHandles.pop_front()); + const auto versionedIndex = releasedResource.VersionedIndex; + m_FreeList.push_back(versionedIndex); + } + } + + struct FencedResource + { + u32VersionedIndex VersionedIndex{}; + Fence::FencePoint FencePoint; + }; + + struct VersionedResource + { + T Resource; + u32 Version{}; + }; + + std::vector m_Resources; + RingBuffer m_DeferredReleasedHandles; + RingBuffer m_FreeList; + + SharedPtr m_Queue; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/FencedResourcePool.h b/src/Public/D3D12/FencedResourcePool.h new file mode 100644 index 00000000..b9c08f92 --- /dev/null +++ b/src/Public/D3D12/FencedResourcePool.h @@ -0,0 +1,134 @@ + +#pragma once + +#include "Platform.h" + +#include "Container/RingBuffer.h" +#include "D3D12/CommandQueue.h" +#include "D3D12/Fence.h" +#include "Utility/Exception.h" +#include "Utility/MoveOnly.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" + +#include + +namespace stf +{ + template + class FencedResourcePool; + + template + class FencedResourcePoolToken + { + friend class FencedResourcePool; + FencedResourcePoolToken() {} + }; + + template + class FencedResourcePool + : public Object + { + public: + + struct CreationParams + { + std::function()> CreateFunc; + SharedPtr Queue; + }; + + class ScopedResource + : MoveOnly + { + public: + + ScopedResource(FencedResourcePoolToken, SharedPtr&& InResource, SharedPtr InPool) + : m_Resource(std::move(InResource)) + , m_Pool(std::move(InPool)) + { + } + + ScopedResource(ScopedResource&& In) noexcept + : m_Resource(std::exchange(In.m_Resource, nullptr)) + , m_Pool(std::exchange(In.m_Pool, nullptr)) + { + } + + ScopedResource& operator=(ScopedResource&& In) noexcept + { + Release(); + m_Pool = std::exchange(In.m_Pool, nullptr); + m_Resource = std::exchange(In.m_Resource, nullptr); + return *this; + } + + ~ScopedResource() + { + Release(); + } + + T* operator->() const + { + return m_Resource.get(); + } + + T& operator*() const + { + return *m_Resource; + } + + operator T* () const + { + return m_Resource.get(); + } + + + private: + + void Release() + { + if (m_Pool && m_Resource) + { + m_Pool->Release(FencedResourcePoolToken{}, std::move(m_Resource)); + } + } + + SharedPtr m_Resource; + SharedPtr m_Pool; + }; + + FencedResourcePool(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_CreateFunc(std::move(InParams.CreateFunc)) + , m_Queue(std::move(InParams.Queue)) + { + } + + ScopedResource Request() + { + if (m_Pool.size() == 0 || !m_Queue->HasFencePointBeenReached(m_Pool.front().FencePoint)) + { + return ScopedResource{ FencedResourcePoolToken{}, m_CreateFunc(), SharedFromThis() }; + } + + return ScopedResource{ FencedResourcePoolToken{}, std::move(ThrowIfUnexpected(m_Pool.pop_front()).Resource), SharedFromThis() }; + } + + void Release(FencedResourcePoolToken, SharedPtr&& InResource) + { + m_Pool.push_back(FencedResource{ .Resource = std::move(InResource), .FencePoint = m_Queue->Signal() }); + } + + private: + + struct FencedResource + { + SharedPtr Resource; + Fence::FencePoint FencePoint; + }; + + RingBuffer m_Pool; + std::function()> m_CreateFunc; + SharedPtr m_Queue; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index 5bc1511f..7db0134b 100644 --- a/src/Public/D3D12/GPUDevice.h +++ b/src/Public/D3D12/GPUDevice.h @@ -30,12 +30,12 @@ namespace stf struct GPUAdapterInfo { std::wstring Name; - uint64_t DedicatedVRAM = 0; - uint64_t SystemRAM = 0; - uint32_t VendorId = 0; - uint32_t DeviceId = 0; - uint32_t SubSysId = 0; - uint32_t Revision = 0; + u64 DedicatedVRAM = 0; + u64 SystemRAM = 0; + u32 VendorId = 0; + u32 DeviceId = 0; + u32 SubSysId = 0; + u32 Revision = 0; }; struct D3D12FeatureInfo @@ -62,21 +62,21 @@ namespace stf struct GPUVirtualAddressInfo { - uint32_t MaxBitsPerResource = 0; - uint32_t MaxBitsPerProcess = 0; + u32 MaxBitsPerResource = 0; + u32 MaxBitsPerProcess = 0; }; struct GPUWaveOperationInfo { - uint32_t MinWaveLaneCount = 0; - uint32_t MaxWaveLaneCount = 0; - uint32_t TotalLaneCount = 0; + u32 MinWaveLaneCount = 0; + u32 MaxWaveLaneCount = 0; + u32 TotalLaneCount = 0; bool IsSupported = false; }; struct GPUArchitectureInfo { - uint32_t GPUIndex = 0; + u32 GPUIndex = 0; bool SupportsTileBasedRendering = false; bool UMA = false; bool CacheCoherentUMA = false; @@ -85,13 +85,24 @@ namespace stf struct VariableRateShadingInfo { - uint32_t ImageTileSize = 0; + u32 ImageTileSize = 0; bool AdditionalShadingRates = false; bool PerPrimitiveShadingRateSupportedWithViewportIndexing = false; bool BackgroundProcessingSupported = false; D3D12_VARIABLE_SHADING_RATE_TIER Tier = D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED; }; + struct DescriptorHeapProperties + { + u32 MaxSamplers = 0; + u32 MaxStaticSamplers = 0; + u32 MaxViews = 0; + u32 ViewDescriptorSize = 0; + u32 RTVDescriptorSize = 0; + u32 DSVDescriptorSize = 0; + u32 SamplerDescriptorSize = 0; + }; + struct GPUHardwareInfo { GPUAdapterInfo AdapterInfo; @@ -100,6 +111,7 @@ namespace stf GPUVirtualAddressInfo VirtualAddressInfo; GPUArchitectureInfo ArchitectureInfo; VariableRateShadingInfo VRSInfo; + DescriptorHeapProperties DescriptorHeapInfo; }; template @@ -108,7 +120,8 @@ namespace stf std::is_same_v || std::is_same_v; - class GPUDevice : Object + class GPUDevice + : public Object { public: @@ -132,8 +145,52 @@ namespace stf bool EnableGPUCapture = false; }; - GPUDevice() = default; - GPUDevice(const CreationParams InDesc); + struct CommittedResourceDesc + { + D3D12_HEAP_PROPERTIES HeapProps = + { + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 0u, + .VisibleNodeMask = 0u + }; + + D3D12_HEAP_FLAGS HeapFlags = D3D12_HEAP_FLAG_NONE; + D3D12_RESOURCE_DESC1 ResourceDesc = + { + .Dimension = D3D12_RESOURCE_DIMENSION_UNKNOWN, + .Alignment = 0u, + .Width = 0u, + .Height = 0u, + .DepthOrArraySize = 0u, + .MipLevels = 0u, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc + { + .Count = 0, + .Quality = 0 + }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_NONE, + .SamplerFeedbackMipRegion = + { + .Width = 0u, + .Height = 0u, + .Depth = 0u + } + }; + + D3D12_BARRIER_LAYOUT BarrierLayout = D3D12_BARRIER_LAYOUT_UNDEFINED; + + std::optional ClearValue = std::nullopt; + + std::span CastableFormats = {}; + + std::string Name = "DefaultResource"; + }; + + GPUDevice(ObjectToken, const CreationParams InDesc); ~GPUDevice(); bool IsValid() const; @@ -144,13 +201,7 @@ namespace stf SharedPtr CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC& InDesc, const std::string_view InName = "DefaultCommandQueue") const; SharedPtr CreateCommittedResource( - const D3D12_HEAP_PROPERTIES& InHeapProps, - const D3D12_HEAP_FLAGS InFlags, - const D3D12_RESOURCE_DESC1& InResourceDesc, - const D3D12_BARRIER_LAYOUT InInitialLayout, - const D3D12_CLEAR_VALUE* InClearValue = nullptr, - const std::span InCastableFormats = {}, - const std::string_view InName = "DefaultResource" + const CommittedResourceDesc& InDesc ) const; SharedPtr CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC& InDesc, const std::string_view InName = "DefaultDescriptorHeap") const; @@ -168,7 +219,7 @@ namespace stf }; ComPtr raw = nullptr; ThrowIfFailed(m_Device->CreatePipelineState(&desc, IID_PPV_ARGS(raw.GetAddressOf()))); - return MakeShared(PipelineState::CreationParams{ std::move(raw) }); + return Object::New(PipelineState::CreationParams{ std::move(raw) }); } SharedPtr CreateRootSignature(const D3D12_VERSIONED_ROOT_SIGNATURE_DESC& InDesc) const; @@ -176,6 +227,7 @@ namespace stf void CopyDescriptors(const DescriptorRange& InDestination, const DescriptorRange& InSource, const D3D12_DESCRIPTOR_HEAP_TYPE InType) const; + void CreateConstantBufferView(const GPUResource& InResource, const DescriptorHandle InHandle) const; void CreateShaderResourceView(const GPUResource& InResource, const DescriptorHandle InHandle) const; void CreateUnorderedAccessView(const GPUResource& InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc, const DescriptorHandle InHandle) const; @@ -191,13 +243,8 @@ namespace stf ComPtr m_Device = nullptr; - SharedPtr m_Info = nullptr; + UniquePtr m_Info = nullptr; HMODULE m_PixHandle = nullptr; - - u32 m_CBVDescriptorSize = 0; - u32 m_RTVDescriptorSize = 0; - u32 m_DSVDescriptorSize = 0; - u32 m_SamplerDescriptorSize = 0; }; } \ No newline at end of file diff --git a/src/Public/D3D12/GPUResource.h b/src/Public/D3D12/GPUResource.h index 7702bcc1..18f10ab4 100644 --- a/src/Public/D3D12/GPUResource.h +++ b/src/Public/D3D12/GPUResource.h @@ -29,12 +29,12 @@ namespace stf { public: - MappedResource(GPUResourceToken, ComPtr InResource); + MappedResource(GPUResourceToken, const ComPtr& InResource); MappedResource(const MappedResource&) = delete; MappedResource& operator=(const MappedResource&) = delete; ~MappedResource(); - std::span Get() const + std::span Get() const { return m_MappedData; } @@ -42,10 +42,11 @@ namespace stf private: ComPtr m_Resource; - std::span m_MappedData; + std::span m_MappedData; }; - class GPUResource : Object + class GPUResource + : public Object { public: @@ -54,10 +55,10 @@ namespace stf ComPtr Resource; std::optional ClearValue{}; GPUEnhancedBarrier InitialBarrier{}; + std::string Name; }; - GPUResource() = default; - GPUResource(CreationParams InParams) noexcept; + GPUResource(ObjectToken, const CreationParams& InParams) noexcept; ID3D12Resource2* GetRaw() const noexcept; operator ID3D12Resource2* () const noexcept; @@ -67,12 +68,15 @@ namespace stf D3D12_RESOURCE_DESC1 GetDesc() const noexcept; std::optional GetClearValue() const noexcept; + std::string GetName() const; + u64 GetGPUAddress() const noexcept; MappedResource Map() const; private: + std::string m_Name; ComPtr m_Resource; std::optional m_ClearValue{}; GPUEnhancedBarrier m_CurrentBarrier; diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h new file mode 100644 index 00000000..3a969094 --- /dev/null +++ b/src/Public/D3D12/GPUResourceManager.h @@ -0,0 +1,281 @@ + +#pragma once + +#include "Container/FreeList.h" +#include "D3D12/CommandQueue.h" +#include "D3D12/DescriptorManager.h" +#include "D3D12/FencedResourceFreeList.h" +#include "D3D12/GPUDevice.h" +#include "D3D12/GPUResource.h" +#include "Utility/Concepts.h" +#include "Utility/FunctionTraits.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" +#include "Utility/VersionedIndex.h" + +#include +#include + +namespace stf +{ + template + concept ExecuteReadbackType = + TFuncTraits::ParamTypes::Size == 1 && + std::is_same_v::ParamTypes::template Type<0>, const MappedResource&>&& + requires (T InFunc, const MappedResource& InResource) + { + { InFunc(InResource) } -> ExpectedErrorType; + }; + + template + concept VoidExecuteReadbackType = ExecuteReadbackType && + requires (T InFunc, const MappedResource & InResource) + { + { InFunc(InResource) } -> ExpectedErrorWithValueType; + }; + + class CommandList; + + namespace Errors::GPUResourceManager + { + ErrorFragment ReadbackHasNotBeenCompleted(const std::string_view InSourceName); + } + + class GPUResourceManager + : public Object + { + struct Private {}; + + public: + + using ResourceManager = FencedResourceFreeList>; + using DescriptorFreeList = FencedResourceFreeList; + using ResourceHandle = typename ResourceManager::Handle; + using DescriptorOpaqueHandle = typename DescriptorFreeList::Handle; + using DescriptorHeapReleaseManager = FencedResourceFreeList>; + + struct CreationParams + { + SharedPtr Device; + SharedPtr Queue; + }; + + struct ConstantBufferDesc + { + std::string Name = "DefaultConstantBuffer"; + u32 RequestedSize = 0u; + }; + + class ConstantBufferHandle + { + public: + + ConstantBufferHandle(Private, const ResourceHandle InHandle); + + ResourceHandle GetHandle() const; + + private: + + ResourceHandle m_Handle; + }; + + class ConstantBufferViewHandle + { + public: + + ConstantBufferViewHandle(Private, const ResourceHandle InBufferHandle, const DescriptorOpaqueHandle InCBVHandle); + + ResourceHandle GetBufferHandle() const; + DescriptorOpaqueHandle GetCBVHandle() const; + + private: + + ResourceHandle m_BufferHandle; + DescriptorOpaqueHandle m_CBVHandle; + }; + + struct BufferDesc + { + std::string Name = "DefaultBuffer"; + u32 RequestedSize = 0u; + D3D12_RESOURCE_FLAGS Flags = D3D12_RESOURCE_FLAG_NONE; + }; + + class BufferHandle + { + public: + + BufferHandle(Private, const ResourceHandle InHandle); + + ResourceHandle GetHandle() const; + + private: + + ResourceHandle m_Handle; + }; + + class BufferUAVHandle + { + public: + + BufferUAVHandle(Private, const ResourceHandle InBufferHandle, const DescriptorOpaqueHandle InUAVHandle); + + ResourceHandle GetBufferHandle() const; + DescriptorOpaqueHandle GetUAVHandle() const; + + private: + + ResourceHandle m_BufferHandle; + DescriptorOpaqueHandle m_UAVHandle; + }; + + struct ReadbackBufferDesc + { + BufferHandle Source; + }; + + class ReadbackBufferHandle + { + public: + + ReadbackBufferHandle(Private, const ResourceHandle InReadbackHandle, const ResourceHandle InSourceHandle); + + ResourceHandle GetReadbackHandle() const; + ResourceHandle GetSourceHandle() const; + + private: + + ResourceHandle m_ReadbackHandle; + ResourceHandle m_SourceHandle; + }; + + private: + + struct InFlightReadback + { + ReadbackBufferHandle Handle; + Fence::FencePoint FencePoint; + std::string SourceBufferName; + }; + + public: + + using InFlightReadbackList = FreeList; + using InFlightReadbackHandle = InFlightReadbackList::Handle; + + class ReadbackResultHandle + { + public: + + ReadbackResultHandle(Private, const InFlightReadbackHandle InHandle); + + InFlightReadbackHandle GetReadbackHandle() const; + + private: + InFlightReadbackHandle m_Handle; + }; + + GPUResourceManager(ObjectToken InToken, const CreationParams& InParams); + + [[nodiscard]] BufferHandle Acquire(const BufferDesc& InDesc); + [[nodiscard]] ConstantBufferHandle Acquire(const ConstantBufferDesc& InDesc); + ExpectedError Acquire(const ReadbackBufferDesc& InDesc); + + [[nodiscard]] BufferUAVHandle CreateUAV(const BufferHandle InHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); + [[nodiscard]] ConstantBufferViewHandle CreateCBV(const ConstantBufferHandle InHandle); + + [[nodiscard]] ExpectedError GetDescriptorIndex(const DescriptorOpaqueHandle InHandle) const; + + template + ExpectedError UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) + { + return UploadData(std::as_bytes(std::span{ &InData }), InBufferHandle); + } + + ExpectedError UploadData(const std::span InBytes, const ConstantBufferHandle InBufferHandle); + + ExpectedError Release(const BufferHandle InHandle); + ExpectedError Release(const ConstantBufferHandle InHandle); + ExpectedError Release(const BufferUAVHandle InHandle); + ExpectedError Release(const ConstantBufferViewHandle InHandle); + + ExpectedError QueueReadback(CommandList& InCommandList, const ReadbackBufferHandle InHandle); + + template + auto ExecuteReadback(const ReadbackResultHandle InHandle, FuncType&& InFunc) + { + using RetType = decltype(InFunc(std::declval())); + return m_Readbacks.Get(InHandle.GetReadbackHandle()) + .and_then( + [&](const InFlightReadback& InReadback) -> RetType + { + if (!m_Queue->HasFencePointBeenReached(InReadback.FencePoint)) + { + return Unexpected{ Error{ Errors::GPUResourceManager::ReadbackHasNotBeenCompleted(InReadback.SourceBufferName) } }; + } + + return ExecuteReadbackImpl(InReadback.Handle, InHandle.GetReadbackHandle(), std::forward(InFunc)); + } + ); + } + + ExpectedError SetRootDescriptor(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); + + ExpectedError SetUAV(CommandList& InCommandList, const BufferUAVHandle InHandle); + + void SetDescriptorHeap(CommandList& InCommandList); + + private: + + template + ExpectedError ExecuteReadbackImpl(const ReadbackBufferHandle InReadbackBufferHandle, const InFlightReadbackHandle InInflightReadbackHandle, InLambdaType&& InFunc) + { + return m_Resources.Get(InReadbackBufferHandle.GetReadbackHandle()) + .and_then( + [&](const SharedPtr& InReadbackBuffer) + { + return InFunc(InReadbackBuffer->Map()); + }) + .and_then( + [&]() -> ExpectedError + { + ThrowIfUnexpected(m_Resources.Release(InReadbackBufferHandle.GetReadbackHandle())); + ThrowIfUnexpected(m_Readbacks.Release(InInflightReadbackHandle)); + + return {}; + } + ); + } + + template + requires (!VoidExecuteReadbackType) + auto ExecuteReadbackImpl(const ReadbackBufferHandle InReadbackBufferHandle, const InFlightReadbackHandle InInflightReadbackHandle, InLambdaType&& InFunc) + { + return m_Resources.Get(InReadbackBufferHandle.GetReadbackHandle()) + .and_then( + [&](const SharedPtr& InReadbackBuffer) + { + return InFunc(InReadbackBuffer->Map()); + }) + .transform( + [&](auto&& Result) + { + ThrowIfUnexpected(m_Resources.Release(InReadbackBufferHandle.GetReadbackHandle())); + ThrowIfUnexpected(m_Readbacks.Release(InInflightReadbackHandle)); + + return Result; + } + ); + } + + SharedPtr m_Device; + SharedPtr m_Queue; + SharedPtr m_DescriptorManager; + + ResourceManager m_Resources; + DescriptorFreeList m_Descriptors; + DescriptorHeapReleaseManager m_HeapReleaseManager; + + InFlightReadbackList m_Readbacks; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/Shader/PipelineState.h b/src/Public/D3D12/Shader/PipelineState.h index 5da96e14..234a0800 100644 --- a/src/Public/D3D12/Shader/PipelineState.h +++ b/src/Public/D3D12/Shader/PipelineState.h @@ -7,7 +7,8 @@ namespace stf { - class PipelineState : Object + class PipelineState + : public Object { public: @@ -16,8 +17,7 @@ namespace stf ComPtr Raw = nullptr; }; - PipelineState() = default; - PipelineState(CreationParams InParams) noexcept; + PipelineState(ObjectToken, CreationParams InParams) noexcept; ID3D12PipelineState* GetRaw() const { diff --git a/src/Public/D3D12/Shader/RootSignature.h b/src/Public/D3D12/Shader/RootSignature.h index 1a4fcf96..1dd08fde 100644 --- a/src/Public/D3D12/Shader/RootSignature.h +++ b/src/Public/D3D12/Shader/RootSignature.h @@ -1,16 +1,13 @@ #pragma once - -#include "Platform.h" #include "Utility/Object.h" #include "Utility/Pointer.h" -#include - #include namespace stf { - class RootSignature : Object + class RootSignature + : public Object { public: @@ -21,8 +18,7 @@ namespace stf ComPtr Blob; }; - RootSignature() = default; - RootSignature(CreationParams InParams); + RootSignature(ObjectToken, CreationParams InParams); const D3D12_VERSIONED_ROOT_SIGNATURE_DESC* GetDesc() const; ID3D12RootSignature* GetRaw() const; diff --git a/src/Public/D3D12/Shader/Shader.h b/src/Public/D3D12/Shader/Shader.h new file mode 100644 index 00000000..fd46783d --- /dev/null +++ b/src/Public/D3D12/Shader/Shader.h @@ -0,0 +1,62 @@ + +#pragma once +#include "D3D12/GPUDevice.h" +#include "D3D12/Shader/CompiledShaderData.h" +#include "D3D12/Shader/RootSignature.h" +#include "D3D12/Shader/ShaderBinding.h" +#include "D3D12/Shader/ShaderBindingMap.h" +#include "Utility/Error.h" +#include "Utility/HLSLTypes.h" +#include "Utility/Object.h" + +namespace stf +{ + namespace Errors + { + Error NoShaderReflectionDataAvailable(); + } + + class ShaderToken + { + ShaderToken() = default; + friend class Shader; + }; + + class Shader + : public Object + { + public: + struct CreationParams : ShaderToken + { + CompiledShaderData ShaderData; + ShaderBindingMap BindingMap; + }; + + Shader(ObjectToken, const CreationParams& InParams); + + static ExpectedError> Make(const CompiledShaderData& InShaderData, GPUDevice& InDevice); + + ExpectedError StageBindingData(const ShaderBinding& InBindings); + + template + requires requires(T InFunc, u32 InRootParamIndex, ShaderBindingMap::StagingInfo InStagingInfo) + { + { InFunc(InRootParamIndex, InStagingInfo) } -> std::same_as; + } + void ForEachStagingBuffer(T&& InFunc) + { + m_BindingMap.ForEachStagingBuffer(std::forward(InFunc)); + } + + uint3 GetThreadGroupSize() const; + + const RootSignature& GetRootSig() const; + + IDxcBlob* GetCompiledShader() const; + + private: + + CompiledShaderData m_ShaderData; + ShaderBindingMap m_BindingMap; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/Shader/ShaderBindingMap.h b/src/Public/D3D12/Shader/ShaderBindingMap.h new file mode 100644 index 00000000..7df269b6 --- /dev/null +++ b/src/Public/D3D12/Shader/ShaderBindingMap.h @@ -0,0 +1,92 @@ +#pragma once +#include "D3D12/GPUDevice.h" +#include "D3D12/Shader/RootSignature.h" +#include "D3D12/Shader/ShaderBinding.h" +#include "Utility/Error.h" +#include "Utility/Pointer.h" +#include "Utility/TransparentStringHash.h" + +#include +#include +#include + +#include + +namespace stf +{ + namespace Errors + { + Error OnlyConstantBuffersSupportedForBinding(); + Error ConstantBufferCantBeInRootConstants(const std::string_view InBufferName); + Error RootSignatureDWORDLimitReached(); + + Error ConstantBufferMustBeBoundToDecriptorTable(const std::string_view InBufferName); + + Error BindingIsSmallerThanBindingData(const std::string_view InBindingName, const u32 InConstantBufferSize, const u64 InBindingDataSize); + Error BindingDoesNotExist(const std::string_view InBindingName); + } + + template + concept StagingBufferFunctionType = requires(T InFunc, u32 InRootParamIndex, StagingInfoType InStagingInfo) + { + { InFunc(InRootParamIndex, InStagingInfo) } -> std::same_as; + }; + + class ShaderBindingMap + { + public: + + enum class EBindType + { + RootConstants, + RootDescriptor, + DescriptorTable + }; + + + struct StagingInfo + { + std::vector Buffer; + EBindType Type = EBindType::RootConstants; + }; + + using StagingBufferMap = std::unordered_map; + + static ExpectedError Make(ID3D12ShaderReflection& InReflection, GPUDevice& InDevice); + + const RootSignature& GetRootSig() const; + + ExpectedError StageBindingData(const ShaderBinding& InBinding); + + template + requires StagingBufferFunctionType + void ForEachStagingBuffer(T&& InFunc) + { + for (const auto& [rootParamIndex, stagingInfo] : m_RootParamBuffers) + { + InFunc(rootParamIndex, stagingInfo); + } + } + + private: + + struct BindingInfo + { + u32 RootParamIndex = 0; + u32 OffsetIntoBuffer = 0; + u32 BindingSize = 0; + EBindType Type = EBindType::RootConstants; + }; + + using BindingMapType = std::unordered_map>; + + ShaderBindingMap( + SharedPtr&& InRootSignature, + BindingMapType&& InNameToBindingsMap, + StagingBufferMap&& InStagingBufferMap); + + SharedPtr m_RootSignature; + BindingMapType m_NameToBindingInfo; + StagingBufferMap m_RootParamBuffers; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/Shader/ShaderCompiler.h b/src/Public/D3D12/Shader/ShaderCompiler.h index adaa570a..3e00eb36 100644 --- a/src/Public/D3D12/Shader/ShaderCompiler.h +++ b/src/Public/D3D12/Shader/ShaderCompiler.h @@ -3,7 +3,7 @@ #include "D3D12/Shader/CompiledShaderData.h" #include "D3D12/Shader/ShaderEnums.h" #include "D3D12/Shader/VirtualShaderDirectoryMappingManager.h" -#include "Utility/Expected.h" +#include "Utility/Error.h" #include #include @@ -18,21 +18,24 @@ namespace stf { namespace fs = std::filesystem; - using CompilationResult = Expected; - - std::ostream& operator<<(std::ostream& InStream, const CompilationResult& InResult); + namespace Errors + { + Error EmptyShaderCodeSource(); + Error EmptyVirtualPath(); + Error UnknownVirtualShaderMappingError(); + Error ResolvedPathIsInvalid(const std::string_view InAbsolutePath, const std::string_view InPath); + Error ReportShaderCompilationError(const std::string_view InError); + } class ShaderCodeSource { public: - using ToStringResult = Expected; - ShaderCodeSource() = default; ShaderCodeSource(std::string InSourceCode); ShaderCodeSource(fs::path InSourcePath); - ToStringResult ToString(const VirtualShaderDirectoryMappingManager& InManager) const; + ExpectedError ToString(const VirtualShaderDirectoryMappingManager& InManager) const; private: std::variant m_Source; @@ -63,7 +66,7 @@ namespace stf ShaderCompiler(); ShaderCompiler(std::vector InMappings); - CompilationResult CompileShader(const ShaderCompilationJobDesc& InJob) const; + ExpectedError CompileShader(const ShaderCompilationJobDesc& InJob) const; private: diff --git a/src/Public/D3D12/Shader/ShaderReflectionUtils.h b/src/Public/D3D12/Shader/ShaderReflectionUtils.h index eeed919a..5cd98fad 100644 --- a/src/Public/D3D12/Shader/ShaderReflectionUtils.h +++ b/src/Public/D3D12/Shader/ShaderReflectionUtils.h @@ -5,7 +5,9 @@ namespace stf { + bool IsArray(ID3D12ShaderReflectionType& InType); bool IsOrContainsArray(ID3D12ShaderReflectionType& InType); - bool ConstantBufferCanBeBoundToRootConstants(ID3D12ShaderReflectionConstantBuffer& InBuffer); + bool ConstantBufferCanBeBoundToRootConstants(ID3D12ShaderReflectionConstantBuffer& InBuffer); + bool ConstantBufferCanBeBoundToRootDescriptor(ID3D12ShaderReflectionConstantBuffer& InBuffer); } \ No newline at end of file diff --git a/src/Public/Framework/ShaderTestCommon.h b/src/Public/Framework/ShaderTestCommon.h index 0565d1b5..e949adfb 100644 --- a/src/Public/Framework/ShaderTestCommon.h +++ b/src/Public/Framework/ShaderTestCommon.h @@ -2,10 +2,12 @@ #include "Platform.h" -#include "Framework/HLSLTypes.h" #include "Framework/TypeByteReader.h" #include "Framework/TestDataBufferLayout.h" +#include "Utility/Error.h" +#include "Utility/HLSLTypes.h" + #include #include #include @@ -54,39 +56,22 @@ namespace stf friend std::ostream& operator<<(std::ostream& InOs, const TestRunResults& In); }; - enum class ETestRunErrorType - { - Unknown, - DescriptorManagement, - ShaderCompilation, - Binding, - RootSignatureGeneration - }; - - struct ErrorTypeAndDescription - { - ETestRunErrorType Type = ETestRunErrorType::Unknown; - std::string Error {}; - friend bool operator==(const ErrorTypeAndDescription&, const ErrorTypeAndDescription&) = default; - friend std::ostream& operator<<(std::ostream& InOs, const ErrorTypeAndDescription& In); - }; - class Results { public: Results() = default; - Results(ErrorTypeAndDescription InError); + Results(Error InError); Results(TestRunResults InResults); operator bool() const; const TestRunResults* GetTestResults() const; - const ErrorTypeAndDescription* GetTestRunError() const; + const Error* GetTestRunError() const; friend std::ostream& operator<<(std::ostream& InOs, const Results& In); private: - std::variant m_Result; + std::variant m_Result; }; } \ No newline at end of file diff --git a/src/Public/Framework/ShaderTestDescriptorManager.h b/src/Public/Framework/ShaderTestDescriptorManager.h deleted file mode 100644 index 0fec6a26..00000000 --- a/src/Public/Framework/ShaderTestDescriptorManager.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "Platform.h" - -#include "D3D12/BindlessFreeListAllocator.h" -#include "D3D12/DescriptorHeap.h" -#include "D3D12/CommandList.h" -#include "D3D12/GPUDevice.h" -#include "D3D12/GPUResource.h" - -#include "Utility/Expected.h" -#include "Utility/Object.h" -#include "Utility/Pointer.h" - -namespace stf -{ - - struct ShaderTestUAV - { - SharedPtr Resource; - BindlessFreeListAllocator::BindlessIndex Handle; - }; - - class ShaderTestDescriptorManager : Object - { - public: - - struct CreationParams - { - SharedPtr Device; - u32 InitialSize = 16u; - }; - - enum class EErrorType - { - Unknown, - AllocatorFull, - AttemptedShrink, - DescriptorAlreadyFree - }; - - template - using Expected = Expected; - - ShaderTestDescriptorManager(CreationParams InParams); - - [[nodiscard]] Expected CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); - Expected ReleaseUAV(const ShaderTestUAV& InUAV); - - [[nodiscard]] Expected> Resize(const u32 InNewSize); - - u32 GetSize() const; - u32 GetCapacity() const; - - void SetDescriptorHeap(CommandList& InCommandList); - - private: - - SharedPtr m_Device; - SharedPtr m_GPUHeap; - SharedPtr m_CPUHeap; - BindlessFreeListAllocator m_Allocator; - DescriptorRange m_CPUDescriptors; - DescriptorRange m_GPUDescriptors; - }; -} \ No newline at end of file diff --git a/src/Public/Framework/ShaderTestDriver.h b/src/Public/Framework/ShaderTestDriver.h index 60a85000..00c60d85 100644 --- a/src/Public/Framework/ShaderTestDriver.h +++ b/src/Public/Framework/ShaderTestDriver.h @@ -5,13 +5,12 @@ #include "D3D12/Shader/PipelineState.h" #include "D3D12/Shader/RootSignature.h" -#include "Framework/HLSLTypes.h" -#include "Framework/ShaderTestDescriptorManager.h" -#include "Framework/ShaderTestShader.h" +#include "Framework/ShaderTestCommon.h" #include "Framework/TestDataBufferLayout.h" #include "Framework/TypeByteReader.h" #include "Utility/Expected.h" +#include "Utility/HLSLTypes.h" #include "Utility/Object.h" #include "Utility/Pointer.h" @@ -21,7 +20,8 @@ namespace stf { - class ShaderTestDriver : Object + class ShaderTestDriver + : public Object { public: @@ -32,22 +32,19 @@ namespace stf struct TestDesc { - ShaderTestShader& Shader; + SharedPtr Shader; const TestDataBufferLayout& TestBufferLayout; std::vector Bindings; std::string_view TestName; uint3 DispatchConfig; }; - ShaderTestDriver(CreationParams InParams); - - SharedPtr CreateBuffer(const D3D12_HEAP_TYPE InType, const D3D12_RESOURCE_DESC1& InDesc); - ShaderTestUAV CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); + ShaderTestDriver(ObjectToken, CreationParams InParams); TypeReaderIndex RegisterByteReader(std::string InTypeIDName, MultiTypeByteReader InByteReader); TypeReaderIndex RegisterByteReader(std::string InTypeIDName, SingleTypeByteReader InByteReader); - Expected RunShaderTest(TestDesc InTestDesc); + ExpectedError RunShaderTest(TestDesc&& InTestDesc); private: @@ -56,9 +53,7 @@ namespace stf SharedPtr m_Device; SharedPtr m_CommandEngine; - SharedPtr m_DescriptorManager; - std::vector> m_DeferredDeletedDescriptorHeaps; MultiTypeByteReaderMap m_ByteReaderMap; }; } \ No newline at end of file diff --git a/src/Public/Framework/ShaderTestFixture.h b/src/Public/Framework/ShaderTestFixture.h index 0307f689..80ee7e8a 100644 --- a/src/Public/Framework/ShaderTestFixture.h +++ b/src/Public/Framework/ShaderTestFixture.h @@ -3,12 +3,11 @@ #include "D3D12/GPUDevice.h" #include "D3D12/Shader/ShaderBinding.h" #include "D3D12/Shader/ShaderCompiler.h" -#include "Framework/HLSLTypes.h" #include "Framework/ShaderTestDriver.h" -#include "Framework/ShaderTestShader.h" #include "Framework/TestDataBufferLayout.h" #include "Stats/StatSystem.h" -#include "Utility/Expected.h" +#include "Utility/Error.h" +#include "Utility/HLSLTypes.h" #include "Utility/Pointer.h" #include "Utility/TransparentStringHash.h" #include @@ -118,7 +117,7 @@ namespace stf Results RunTestImpl(RuntimeTestDesc InTestDesc, const bool InIsFailureRetry); - Expected, ErrorTypeAndDescription> CompileShader(const std::string_view InName, const EShaderType InType, CompilationEnvDesc InCompileDesc, const bool InTakingCapture) const; + ExpectedError CompileShader(const std::string_view InName, const EShaderType InType, CompilationEnvDesc InCompileDesc, const bool InTakingCapture) const; void PopulateDefaultByteReaders(); bool ShouldTakeCapture(const EGPUCaptureMode InCaptureMode, const bool InIsFailureRetry) const; diff --git a/src/Public/Framework/ShaderTestShader.h b/src/Public/Framework/ShaderTestShader.h deleted file mode 100644 index e7745961..00000000 --- a/src/Public/Framework/ShaderTestShader.h +++ /dev/null @@ -1,61 +0,0 @@ - -#pragma once - -#include "Platform.h" - -#include "D3D12/CommandList.h" -#include "D3D12/GPUDevice.h" -#include "D3D12/Shader/CompiledShaderData.h" -#include "D3D12/Shader/RootSignature.h" -#include "D3D12/Shader/ShaderBinding.h" - -#include "Framework/HLSLTypes.h" -#include "Framework/ShaderTestCommon.h" -#include "Utility/Expected.h" -#include "Utility/Object.h" -#include "Utility/TransparentStringHash.h" - -#include -#include -#include - -namespace stf -{ - class ShaderTestShader : Object - { - public: - struct CreationParams - { - CompiledShaderData ShaderData; - SharedPtr Device; - }; - - ShaderTestShader(CreationParams InParams); - - Expected Init(); - Expected BindConstantBufferData(const std::span InBindings); - void SetConstantBufferData(CommandList& InList) const; - - uint3 GetThreadGroupSize() const; - - RootSignature* GetRootSig() const; - - IDxcBlob* GetCompiledShader() const; - - private: - - struct BindingInfo - { - u32 RootParamIndex = 0; - u32 OffsetIntoBuffer = 0; - u32 BindingSize = 0; - }; - - CompiledShaderData m_ShaderData; - SharedPtr m_Device; - SharedPtr m_RootSignature; - - std::unordered_map> m_NameToBindingInfo; - std::unordered_map> m_RootParamBuffers; - }; -} \ No newline at end of file diff --git a/src/Public/Framework/TestDataBufferProcessor.h b/src/Public/Framework/TestDataBufferProcessor.h index 840e7f48..c1a3c40d 100644 --- a/src/Public/Framework/TestDataBufferProcessor.h +++ b/src/Public/Framework/TestDataBufferProcessor.h @@ -2,10 +2,10 @@ #include "Platform.h" -#include "Framework/HLSLTypes.h" #include "Framework/ShaderTestCommon.h" #include "Framework/TestDataBufferLayout.h" #include "Framework/TypeByteReader.h" +#include "Utility/HLSLTypes.h" #include #include diff --git a/src/Public/Stats/StatSystem.h b/src/Public/Stats/StatSystem.h index 3c21a0dd..96b4b4f7 100644 --- a/src/Public/Stats/StatSystem.h +++ b/src/Public/Stats/StatSystem.h @@ -16,7 +16,7 @@ namespace stf T Stat; }; - using TimedStat = NamedStat; + using TimedStat = NamedStat>; class StatSystem { @@ -50,10 +50,9 @@ namespace stf return m_Gen; } - template - operator bool(this ThisType&& InThis) + operator bool() const { - return InThis.m_IsValid != 0; + return m_IsValid != 0; } friend auto operator<=>(Handle, Handle) = default; diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index f72d7ad9..f10f2ef2 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -29,12 +29,18 @@ namespace stf template concept DefaultConstructibleType = std::is_default_constructible_v; + template + concept ArithmeticType = std::is_arithmetic_v; + template concept MoveAssignableType = std::is_move_assignable_v; template concept MoveConstructibleType = std::is_move_constructible_v; + template + concept TriviallyCopyableType = std::is_trivially_copyable_v; + template concept PureFunctionType = std::is_function_v>>; @@ -72,7 +78,10 @@ namespace stf concept ConstexprDefaultConstructableEmptyCallableType = ConstexprDefaultConstructableType && EmptyCallableType; template typename Template, typename... Ts> - concept InstantiatableFrom = TIsInstantiationOf>::Value; + concept InstantiatableFrom = TIsInstantiationOf, Template>::Value; + + template typename Template> + concept InstantiationOf = TIsInstantiationOf::Value; template concept Newable = requires(void* InBuff, Ts&&... In) @@ -139,4 +148,17 @@ namespace stf (alignof(T) == 4 || alignof(T) == 2 || alignof(T) == 8) && Formattable; + + template + concept CValidBitField = + std::unsigned_integral && + (sizeof(BackingType) * 8) == (Bits + ...) && + ((Bits != 0) && ...); + + template + concept OStreamable = requires(std::ostream& InOutStream, const T & In) + { + { InOutStream << In } -> std::same_as; + }; + } diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h new file mode 100644 index 00000000..3d55020c --- /dev/null +++ b/src/Public/Utility/Error.h @@ -0,0 +1,264 @@ + +#pragma once + +#include "Platform.h" + +#include "Utility/Concepts.h" +#include "Utility/Expected.h" +#include "Utility/FixedString.h" +#include "Utility/StringLiteral.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace stf +{ + class ErrorFragmentToken + { + friend class ErrorFragment; + constexpr ErrorFragmentToken() = default; + }; + + class ErrorFragment + { + public: + + ErrorFragment() = delete; + + constexpr ErrorFragment(ErrorFragmentToken, StringLiteral InFormat, std::string InFragment) + : m_Format(InFormat) + , m_Fragment(std::move(InFragment)) + { + } + + template + constexpr static ErrorFragment Make() + { + return ErrorFragment{ + ErrorFragmentToken{}, + StringLiteral(InFormat.Data), + InFormat.Data + }; + } + + template + requires (Formattable && ...) + static ErrorFragment Make(ArgTypes&&... InArgs) + { + return ErrorFragment{ + ErrorFragmentToken{}, + StringLiteral(InFormat.Data), + std::format(InFormat.Data, std::forward(InArgs)...) + }; + } + + friend bool operator==(const ErrorFragment&, const ErrorFragment&) = default; + friend bool operator!=(const ErrorFragment&, const ErrorFragment&) = default; + + bool HasSameFormat(const ErrorFragment& In) const + { + return HasFormat(In.Format()); + } + + bool HasFormat(const StringLiteral In) const + { + return m_Format == In; + } + + StringLiteral Format() const + { + return m_Format; + } + + std::string_view Error() const + { + return m_Fragment; + } + + private: + StringLiteral m_Format; + std::string m_Fragment{}; + }; + + class Error + { + public: + + Error() = default; + + explicit Error(ErrorFragment&& InFragment) + { + Append(std::move(InFragment)); + } + + template + static Error FromFragment() + { + return Error{ ErrorFragment::Make() }; + } + + template + requires (Formattable && ...) + static Error FromFragment(ArgTypes&&... InArgs) + { + return Error{ ErrorFragment::Make(std::forward(InArgs)...) }; + } + + void Append(ErrorFragment&& InFragment) + { + m_Fragments.push_back(std::move(InFragment)); + } + + void Append(const ErrorFragment& InFragment) + { + m_Fragments.push_back((InFragment)); + } + + bool HasFragmentWithFormat(const StringLiteral InFormat) const + { + return std::ranges::any_of( + m_Fragments, + [&](const ErrorFragment& InFragment) + { + return InFragment.HasFormat(InFormat); + }); + } + + bool HasFragment(const ErrorFragment& InErrorFragment) const + { + return std::ranges::any_of( + m_Fragments, + [&](const ErrorFragment& InFragment) + { + return InFragment == InErrorFragment; + }); + } + + template OutType> + auto FormatTo(OutType InIterator) const + { + for (const auto& fragment : m_Fragments | std::views::reverse) + { + std::ranges::copy(fragment.Error(), InIterator); + *InIterator++ = '\n'; + } + return InIterator; + } + + Error& operator+=(const ErrorFragment& InFragment) + { + Append(InFragment); + return *this; + } + + Error& operator+=(ErrorFragment&& InFragment) + { + Append(std::move(InFragment)); + return *this; + } + + Error& operator+=(const Error& InError) + { + for (const auto& fragment : InError.m_Fragments) + { + Append(fragment); + } + return *this; + } + + Error& operator+=(Error&& InError) + { + for (auto&& fragment : InError.m_Fragments) + { + Append(std::move(fragment)); + } + return *this; + } + + friend std::ostream& operator<<(std::ostream& InOutStream, const Error& InError); + + friend bool operator==(const Error&, const Error&) = default; + friend bool operator!=(const Error&, const Error&) = default; + + friend Error operator+(const Error& InA, const Error& InB) + { + Error ret; + ret += InA; + ret += InB; + + return ret; + } + + private: + + std::vector m_Fragments{}; + }; + + inline Error operator+(const ErrorFragment& InA, const ErrorFragment& InB) + { + Error ret; + ret += InA; + ret += InB; + + return ret; + } + + inline Error operator+(const Error& InA, const ErrorFragment& InB) + { + Error ret; + ret += InA; + ret += InB; + + return ret; + } + + template + using ExpectedError = Expected; + + template + struct TIsExpectedError : std::integral_constant {}; + + template + struct TIsExpectedError> : std::integral_constant {}; + + template + struct TExpectedErrorHasValue : std::integral_constant {}; + + template + struct TExpectedErrorHasValue, T> : std::integral_constant {}; + + template + concept ExpectedErrorType = TIsExpectedError::value; + + template + concept ExpectedErrorWithValueType = TExpectedErrorHasValue::value; + +} + +template<> +struct std::formatter : std::formatter { + auto format(const stf::ErrorFragment& In, auto& ctx) const { + return std::formatter::format(In.Error(), ctx); + } +}; + +template<> +struct std::formatter : std::formatter { + auto format(const stf::Error& In, auto& ctx) const { + return In.FormatTo(ctx.out()); + } +}; + +namespace stf +{ + inline std::ostream& operator<<(std::ostream& InOutStream, const Error& InError) + { + std::print(InOutStream, "{}", InError); + return InOutStream; + } +} diff --git a/src/Public/Utility/Expected.h b/src/Public/Utility/Expected.h index 8b22028a..bece7baa 100644 --- a/src/Public/Utility/Expected.h +++ b/src/Public/Utility/Expected.h @@ -1,6 +1,10 @@ #pragma once +#include "Utility/Concepts.h" +#include "Utility/Type.h" + #include +#include namespace stf { @@ -10,3 +14,32 @@ namespace stf template using Unexpected = std::unexpected; } + +template +std::ostream& operator<<(std::ostream& InOutStream, const stf::Expected& InExpected) +{ + if (InExpected.has_value()) + { + if constexpr (stf::OStreamable) + { + InOutStream << InExpected.value(); + } + else + { + std::print(InOutStream, "Unstreamable expected type: {}", stf::TypeToString()); + } + } + else + { + if constexpr (stf::OStreamable) + { + InOutStream << InExpected.error(); + } + else + { + std::print(InOutStream, "Unstreamable error type: {}", stf::TypeToString()); + } + } + + return InOutStream; +} diff --git a/src/Public/Utility/FixedString.h b/src/Public/Utility/FixedString.h index 595685aa..25ed9847 100644 --- a/src/Public/Utility/FixedString.h +++ b/src/Public/Utility/FixedString.h @@ -1,7 +1,10 @@ #pragma once #include "Platform.h" + +#include "Utility/StringLiteral.h" #include +#include namespace stf { @@ -15,6 +18,16 @@ namespace stf { std::copy(std::cbegin(InString), std::cend(InString), std::begin(Data)); } + + constexpr std::string_view View() const + { + return std::string_view{ Data }; + } + + consteval StringLiteral Literal() const + { + return StringLiteral{ View() }; + } }; template diff --git a/src/Public/Framework/HLSLTypes.h b/src/Public/Utility/HLSLTypes.h similarity index 100% rename from src/Public/Framework/HLSLTypes.h rename to src/Public/Utility/HLSLTypes.h diff --git a/src/Public/Utility/Object.h b/src/Public/Utility/Object.h index 8e542dd4..f0c62dff 100644 --- a/src/Public/Utility/Object.h +++ b/src/Public/Utility/Object.h @@ -1,15 +1,50 @@ #pragma once +#include "Utility/Concepts.h" +#include "Utility/Pointer.h" + namespace stf { - class Object + class ObjectToken + { + friend class Object; + ObjectToken() = default; + }; + + class Object; + + template + concept ObjectType = + std::derived_from && + std::constructible_from && + !std::same_as; + + class Object : public SharedFromThis { public: - Object() = default; + Object() = delete; + explicit Object(ObjectToken) {}; Object(const Object&) = delete; Object(Object&&) = delete; Object& operator=(const Object&) = delete; Object& operator=(Object&&) = delete; + + virtual ~Object() {} + + template + requires ObjectType + static SharedPtr New(ParamTypes&&... InArgs) + { + return MakeShared(ObjectToken{}, std::forward(InArgs)...); + } + + protected: + + template + auto SharedFromThis(this ThisType&& InThis) + { + return std::static_pointer_cast>(std::forward(InThis).shared_from_this()); + } }; } \ No newline at end of file diff --git a/src/Public/Utility/Pointer.h b/src/Public/Utility/Pointer.h index 619fb28b..43503105 100644 --- a/src/Public/Utility/Pointer.h +++ b/src/Public/Utility/Pointer.h @@ -1,4 +1,6 @@ #pragma once + +#include "Utility/Concepts.h" #include #include @@ -11,16 +13,21 @@ namespace stf template using SharedPtr = std::shared_ptr; + template + using SharedFromThis = std::enable_shared_from_this; + template using ComPtr = Microsoft::WRL::ComPtr; template + requires std::constructible_from auto MakeUnique(Ts&&... InArgs) { return std::make_unique(std::forward(InArgs)...); } template + requires std::constructible_from auto MakeShared(Ts&&... InArgs) { return std::make_shared(std::forward(InArgs)...); diff --git a/src/Public/Utility/StringLiteral.h b/src/Public/Utility/StringLiteral.h new file mode 100644 index 00000000..51ecf593 --- /dev/null +++ b/src/Public/Utility/StringLiteral.h @@ -0,0 +1,33 @@ + +#pragma once + +#include + +namespace stf +{ + class StringLiteral + { + public: + + consteval StringLiteral(const std::string_view InView) + : m_View(InView) + { + } + + constexpr operator std::string_view() const + { + return View(); + } + + constexpr std::string_view View() const + { + return m_View; + } + + constexpr friend bool operator==(const StringLiteral&, const StringLiteral&) = default; + constexpr friend bool operator!=(const StringLiteral&, const StringLiteral&) = default; + + private: + std::string_view m_View{}; + }; +} \ No newline at end of file diff --git a/src/Public/Utility/Time.h b/src/Public/Utility/Time.h index 3d19d7da..e3f7f7f1 100644 --- a/src/Public/Utility/Time.h +++ b/src/Public/Utility/Time.h @@ -2,71 +2,24 @@ #pragma once #include "Platform.h" +#include "Utility/Concepts.h" + #include namespace stf { - using SecondsF = std::chrono::duration; - using MillisecondsF = std::chrono::duration>; - using MicrosecondsF = std::chrono::duration>; - using NanosecondsF = std::chrono::duration>; - using Clock = std::chrono::steady_clock; - using TimePoint = std::chrono::time_point; - - class Duration - { - public: - - constexpr Duration() = default; - - constexpr Duration(Clock::duration InDuration) - : m_Duration(InDuration) - { - } - - constexpr Duration(SecondsF InSeconds) - : m_Duration(std::chrono::duration_cast(InSeconds)) - { - } + template + using Seconds = std::chrono::duration; - constexpr Duration(MillisecondsF InMilliseconds) - : m_Duration(std::chrono::duration_cast(InMilliseconds)) - { - } + template + using Milliseconds = std::chrono::duration; - constexpr operator SecondsF() const - { - return std::chrono::duration_cast(m_Duration); - } + template + using Microseconds = std::chrono::duration; - constexpr operator MillisecondsF() const - { - return std::chrono::duration_cast(m_Duration); - } + template + using Nanoseconds = std::chrono::duration; - constexpr operator MicrosecondsF() const - { - return std::chrono::duration_cast(m_Duration); - } - - constexpr operator NanosecondsF() const - { - return std::chrono::duration_cast(m_Duration); - } - - constexpr operator Clock::duration() const - { - return m_Duration; - } - - template - constexpr auto Count() const - { - return std::chrono::duration_cast(m_Duration).count(); - } - - private: - - Clock::duration m_Duration{}; - }; -} \ No newline at end of file + using Clock = std::chrono::steady_clock; + using TimePoint = std::chrono::time_point; +} diff --git a/src/Public/Utility/Tuple.h b/src/Public/Utility/Tuple.h index 81b44920..94acc2a9 100644 --- a/src/Public/Utility/Tuple.h +++ b/src/Public/Utility/Tuple.h @@ -1,13 +1,14 @@ #pragma once #include +#include namespace stf { +#if defined(__clang__) && __clang_major__ <= 19 + template + using Tuple = std::tuple; +#else template using Tuple = tuplet::tuple; - - template - constexpr Tuple tie(T&... t) { - return { t... }; - } +#endif } \ No newline at end of file diff --git a/src/Public/Utility/TypeList.h b/src/Public/Utility/TypeList.h index 499862cc..75f3603c 100644 --- a/src/Public/Utility/TypeList.h +++ b/src/Public/Utility/TypeList.h @@ -107,7 +107,7 @@ namespace stf class TypeList; template - concept TypeListType = TIsInstantiationOf::Value; + concept TypeListType = TIsInstantiationOf::Value; template class TypeList diff --git a/src/Public/Utility/TypeTraits.h b/src/Public/Utility/TypeTraits.h index 514feece..48dd73c9 100644 --- a/src/Public/Utility/TypeTraits.h +++ b/src/Public/Utility/TypeTraits.h @@ -58,14 +58,14 @@ namespace stf // This template check won't work for any template that takes a NTTP // This paper talks about what needs to be in the standard for this to occur. // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1985r3.pdf - template class Template, typename T> + template class Template> struct TIsInstantiationOf { static constexpr bool Value = false; }; template class Template, typename... InArgs> - struct TIsInstantiationOf> + struct TIsInstantiationOf, Template> { static constexpr bool Value = true; }; diff --git a/src/Public/Utility/VersionedIndex.h b/src/Public/Utility/VersionedIndex.h new file mode 100644 index 00000000..c0fb3719 --- /dev/null +++ b/src/Public/Utility/VersionedIndex.h @@ -0,0 +1,62 @@ +#pragma once + +#include "Utility/Exception.h" +#include "Utility/Concepts.h" +#include "Platform.h" + +#include + +namespace stf +{ + + template + requires + ((sizeof(BackingType) * 8) > NumIndexBits) && + CValidBitField + class VersionedIndex + { + public: + + static constexpr u32 NumVersionBits = (sizeof(BackingType) * 8) - NumIndexBits; + static constexpr BackingType MaxIndex = (1u << NumIndexBits) - 1u; + static constexpr BackingType MaxVersion = (1u << NumVersionBits) - 1u; + + VersionedIndex() = default; + VersionedIndex(const BackingType InIndex) + : VersionedIndex(InIndex, 0) + { + } + + VersionedIndex Next() const + { + return VersionedIndex{ m_Index, m_Version + 1u }; + } + + BackingType GetIndex() const + { + return m_Index; + } + + BackingType GetVersion() const + { + return m_Version; + } + + friend bool operator==(const VersionedIndex&, const VersionedIndex&) = default; + friend bool operator!=(const VersionedIndex&, const VersionedIndex&) = default; + + private: + + VersionedIndex(const BackingType InIndex, const BackingType InVersion) + : m_Index(InIndex) + , m_Version(InVersion & MaxVersion) + { + ThrowIfFalse(InIndex <= MaxIndex, std::format("Provided index ({}) is not in range [0, {}]", InIndex, MaxIndex)); + } + + BackingType m_Index : NumIndexBits{}; + BackingType m_Version : NumVersionBits{}; + }; + + using u32VersionedIndex = VersionedIndex; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 445afaac..51a061e2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -81,6 +81,8 @@ set(SHADER_SOURCES Shader/HLSLFrameworkTests/Asserts/IsFalse.hlsl Shader/HLSLFrameworkTests/Asserts/IsTrue.hlsl Shader/HLSLFrameworkTests/Asserts/NotEqual.hlsl + Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/BindingsHave2ByteAlignment.hlsl + Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/ConstantBufferArray.hlsl Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindings.hlsl Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindingsTooLarge.hlsl Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/MultipleConstantBuffers.hlsl @@ -139,14 +141,19 @@ set(SHADER_SOURCES ) set(SOURCES + Private/Container/FreeListTests.cpp Private/Container/RingBufferTests.cpp Private/D3D12/BindlessFreeListAllocatorTests.cpp Private/D3D12/CommandEngineTests.cpp Private/D3D12/CommandListTests.cpp + Private/D3D12/CommandQueueTests.cpp Private/D3D12/DescriptorFreeListAllocatorTests.cpp Private/D3D12/DescriptorHeapTests.cpp + Private/D3D12/DescriptorManagerTests.cpp Private/D3D12/DescriptorRingAllocatorTests.cpp Private/D3D12/DescriptorTests.cpp + Private/D3D12/FencedResourceFreeListTests.cpp + Private/D3D12/FencedResourcePoolTests.cpp Private/D3D12/Shader/HLSLTests.cpp Private/D3D12/Shader/ReflectionTests.cpp Private/D3D12/Shader/ShaderBindingTests.cpp @@ -194,9 +201,7 @@ set(SOURCES Private/Framework/HLSLFramework/ThreadDimensionsTests.cpp Private/Framework/HLSLFramework/ThreadIdRegistrationTests.cpp Private/Framework/HLSLFramework/TTLTypeTraitsTests.cpp - Private/Framework/HLSLTypesTests.cpp Private/Framework/SectionsInHLSLProofOfConceptTests.cpp - Private/Framework/ShaderTestDescriptorManagerTests.cpp Private/Framework/ShaderTestFixtureTests.cpp Private/Framework/TestDataBufferLayoutTests.cpp Private/Framework/TestDataBufferProcessorTests.cpp @@ -207,14 +212,20 @@ set(SOURCES Private/Utility/AlignedOffsetTests.cpp Private/Utility/ConceptsTests.cpp Private/Utility/EnumReflectionTests.cpp + Private/Utility/ErrorTests.cpp + Private/Utility/ExpectedTests.cpp Private/Utility/FunctionTraitsTests.cpp + Private/Utility/HLSLTypesTests.cpp Private/Utility/LambdaTests.cpp Private/Utility/ObjectTests.cpp + Private/Utility/PointerTests.cpp Private/Utility/TupleTests.cpp Private/Utility/TypeListTests.cpp Private/Utility/TypeTraitsTests.cpp + Private/Utility/VersionedIndexTests.cpp Public/D3D12/Shader/ShaderCompilerTestsCommon.h Public/Framework/HLSLFramework/HLSLFrameworkTestsCommon.h + Public/TestUtilities/ErrorMatchers.h Public/TestUtilities/Noisy.h ) diff --git a/test/Private/Container/FreeListTests.cpp b/test/Private/Container/FreeListTests.cpp new file mode 100644 index 00000000..e6e366fd --- /dev/null +++ b/test/Private/Container/FreeListTests.cpp @@ -0,0 +1,76 @@ + +#include "TestUtilities/ErrorMatchers.h" + +#include + +#include + +#include + +SCENARIO("FreeListTests") +{ + using namespace stf; + + using FreeListType = FreeList; + + auto getResource = + [](const FreeListType& InFreeList, const FreeListType::Handle InHandle) + { + const auto ret = InFreeList.Get(InHandle); + REQUIRE(ret); + return ret.value(); + }; + + auto resourceGenerator = + [id = 0]() mutable + { + return id++; + }; + + GIVEN("An empty free list") + { + FreeListType freeList; + + WHEN("Resource requested") + { + const auto firstHandle = freeList.Manage(resourceGenerator()); + + REQUIRE(freeList.ValidateHandle(firstHandle)); + const auto firstResource = getResource(freeList, firstHandle); + + AND_WHEN("resource is released") + { + const auto releaseResult = freeList.Release(firstHandle); + REQUIRE(releaseResult); + + THEN("handle is no longer valid") + { + const auto getResult = freeList.Get(firstHandle); + REQUIRE_FALSE(getResult); + + REQUIRE_THAT(getResult.error(), ErrorContainsFormat(Errors::FreeList::StaleHandle(0, 0).Format())); + } + + AND_WHEN("resource is acquired again") + { + const auto secondHandle = freeList.Manage(resourceGenerator()); + REQUIRE(freeList.ValidateHandle(secondHandle)); + const auto secondResource = getResource(freeList, secondHandle); + + THEN("first and second resource are different") + { + REQUIRE(firstResource != secondResource); + } + + THEN("trying to access first resource fails") + { + const auto getResultForFirst = freeList.Get(firstHandle); + + REQUIRE_FALSE(getResultForFirst); + REQUIRE_THAT(getResultForFirst.error(), ErrorContainsFormat(Errors::FreeList::StaleHandle(0, 0).Format())); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Private/Container/RingBufferTests.cpp b/test/Private/Container/RingBufferTests.cpp index a2bc5c6f..7ed5ce93 100644 --- a/test/Private/Container/RingBufferTests.cpp +++ b/test/Private/Container/RingBufferTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include @@ -106,6 +107,23 @@ SCENARIO("RingBufferTests") REQUIRE(expected == buffer.size()); REQUIRE(expected == buffer.front().Num); } + + AND_WHEN("another item is pushed back") + { + static constexpr i64 secondExpected = 2; + buffer.push_back(secondExpected); + + THEN("buffer contains item") + { + REQUIRE(secondExpected == buffer.size()); + auto popResult = buffer.pop_front(); + REQUIRE(popResult); + REQUIRE(expected == popResult.value().Num); + auto secondPopResult = buffer.pop_front(); + REQUIRE(secondPopResult); + REQUIRE(secondExpected == secondPopResult.value().Num); + } + } } WHEN("iterated on") @@ -180,12 +198,13 @@ SCENARIO("RingBufferTests") WHEN("Resized to something smaller") { - const auto resizeResult = buffer.resize(size - 1); + constexpr u64 smallerSize = size - 1ull; + const auto resizeResult = buffer.resize(smallerSize); THEN("fails") { REQUIRE_FALSE(resizeResult.has_value()); - REQUIRE(resizeResult.error() == RingBuffer::EErrorType::AttemptedShrink); + REQUIRE(resizeResult.error().HasFragment(Errors::RingBuffer::AttemptedShrink(buffer.size(), size - 1ull))); } } @@ -285,12 +304,13 @@ SCENARIO("RingBufferTests") WHEN("Resized to something smaller") { - const auto resizeResult = buffer.resize(capacity - 1); + constexpr u64 smallerCapacity = capacity - 1; + const auto resizeResult = buffer.resize(smallerCapacity); THEN("fails") { REQUIRE_FALSE(resizeResult.has_value()); - REQUIRE(resizeResult.error() == RingBuffer::EErrorType::AttemptedShrink); + REQUIRE_THAT(resizeResult.error(), ErrorContains(Errors::RingBuffer::AttemptedShrink(buffer.size(), smallerCapacity))); } } diff --git a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp index 9d19d89b..96c597ab 100644 --- a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp +++ b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include @@ -29,8 +30,8 @@ SCENARIO("BindlessFreeListAllocatorTests") const auto allocation = allocator.Allocate(); THEN("return expected error") { - REQUIRE(!allocation); - REQUIRE(allocation.error() == BindlessFreeListAllocator::EErrorType::EmptyError); + REQUIRE_FALSE(allocation); + REQUIRE_THAT(allocation.error(), ErrorContains(Errors::BindlessFreeListAllocator::Empty())); } } } @@ -80,7 +81,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("release failed") { REQUIRE_FALSE(releaseResult.has_value()); - REQUIRE(releaseResult.error() == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); + REQUIRE_THAT(releaseResult.error(), ErrorContains(Errors::BindlessFreeListAllocator::IndexAlreadyReleased(invalidAllocation.value().GetIndex()))); } } } @@ -109,7 +110,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("release failed") { REQUIRE_FALSE(finalReleaseOnInitialAllocatorResult.has_value()); - REQUIRE(finalReleaseOnInitialAllocatorResult.error() == BindlessFreeListAllocator::EErrorType::InvalidIndex); + REQUIRE_THAT(finalReleaseOnInitialAllocatorResult.error(), ErrorContains(Errors::BindlessFreeListAllocator::InvalidIndex(finalAllocation.value()))); } } @@ -155,7 +156,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("Release fails") { REQUIRE_FALSE(secondReleaseResult.has_value()); - REQUIRE(secondReleaseResult.error() == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); + REQUIRE_THAT(secondReleaseResult.error(), ErrorContains(Errors::BindlessFreeListAllocator::IndexAlreadyReleased(bindlessIndex1.value()))); REQUIRE(initialCapacity == allocator.GetCapacity()); REQUIRE(0 == allocator.GetSize()); } @@ -174,7 +175,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("State is as expected") { - for (const auto allocation : allocations) + for (const auto& allocation : allocations) { REQUIRE(allocation.has_value()); } @@ -234,14 +235,14 @@ SCENARIO("BindlessFreeListAllocatorTests") using ReleaseType = decltype(allocator.Release(std::declval().value())); std::vector releases; - for (const auto allocation : allocations) + for (const auto& allocation : allocations) { releases.push_back(allocator.Release(allocation.value())); } THEN("releases succeeded") { - for (const auto release : releases) + for (const auto& release : releases) { REQUIRE(release.has_value()); } @@ -275,7 +276,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("allocation is unique") { - for (const auto oldAllocation : allocations) + for (const auto& oldAllocation : allocations) { REQUIRE(allocation != oldAllocation); } diff --git a/test/Private/D3D12/CommandEngineTests.cpp b/test/Private/D3D12/CommandEngineTests.cpp index 03c151ca..cf3f20af 100644 --- a/test/Private/D3D12/CommandEngineTests.cpp +++ b/test/Private/D3D12/CommandEngineTests.cpp @@ -1,98 +1,105 @@ #include "D3D12/CommandEngine.h" +#include #include "Utility/Lambda.h" namespace CommandEngineFuncTypeTests { using namespace stf; - template + template struct CallableType { - void operator()(T...) {} + Ret operator()(T...) { return Ret{}; } }; struct NonCallableType { }; - template - using FreeFuncType = void(T...); + template + using FreeFuncType = Ret(T...); - template - using LambdaType = decltype([](T...) {}); + template + using LambdaType = decltype([](T...) { return Ret{}; }); - template - using EngineLambdaType = decltype(Lambda([](T...) {})); + template + using EngineLambdaType = decltype(Lambda([](T...) { return Ret{}; })); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(CommandEngineFuncType>); + using ValidRet = ExpectedError; + using InvalidRet = i32; - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); - static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); + static_assert(!CommandEngineFuncType>); } namespace ExecuteLambdaTypeTests { using namespace stf; - template + template struct CallableType { - void operator()(T...) {} + Ret operator()(T...) { return Ret{}; } }; struct NonCallableType { }; - template - using FreeFuncType = void(T...); + template + using FreeFuncType = Ret(T...); + + template + using LambdaType = decltype([](T...) { return Ret{}; }); - template - using LambdaType = decltype([](T...) {}); + template + using EngineLambdaType = decltype(Lambda([](T...) { return Ret{}; })); - template - using EngineLambdaType = decltype(Lambda([](T...){})); + using ValidRet = ExpectedError; + using InvalidRet = i32; - static_assert(ExecuteLambdaType>); - static_assert(ExecuteLambdaType>); - static_assert(ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); + static_assert(ExecuteLambdaType>); + static_assert(ExecuteLambdaType>); + static_assert(ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); - static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); + static_assert(!ExecuteLambdaType>); } \ No newline at end of file diff --git a/test/Private/D3D12/CommandQueueTests.cpp b/test/Private/D3D12/CommandQueueTests.cpp new file mode 100644 index 00000000..ce5a6cf1 --- /dev/null +++ b/test/Private/D3D12/CommandQueueTests.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include + +#include + +#include +#include + +class CommandQueueTestFixture +{ + +protected: + + void BeginTestCase(const stf::GPUDevice::EDeviceType InType) const + { + device = stf::Object::New ( + stf::GPUDevice::CreationParams + { + .DeviceType = InType + }); + } + + void EndTestCase() const + { + device = nullptr; + } + + mutable stf::SharedPtr device; +}; + +TEST_CASE_PERSISTENT_FIXTURE( CommandQueueTestFixture, "Scenario: CommandQueueTests") +{ + using namespace stf; + + const auto deviceType = GENERATE + ( + GPUDevice::EDeviceType::Hardware, + GPUDevice::EDeviceType::Software + ); + + GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) + { + SECTION("Setup") + { + REQUIRE_FALSE(device); + BeginTestCase(deviceType); + REQUIRE(device); + } + + AND_GIVEN("Two command queues created") + { + auto directQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + auto copyQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + THEN("queues are valid") + { + REQUIRE(directQueue); + REQUIRE(copyQueue); + } + + WHEN("Fence signalled with no work") + { + const auto fencePoint = directQueue->Signal(); + + THEN("Fence point will have been reached immediately") + { + REQUIRE(directQueue->HasFencePointBeenReached(fencePoint)); + } + } + + WHEN("Next signalled fence point queried") + { + const auto futureFencePoint = copyQueue->NextSignal(); + + THEN("Fence point will not have been reached") + { + REQUIRE_FALSE(copyQueue->HasFencePointBeenReached(futureFencePoint)); + } + + AND_WHEN("Future fence point is waited on by another queue") + { + directQueue->WaitOnFenceGPU(futureFencePoint); + + AND_WHEN("The queue that is waiting is signalled") + { + const auto waitingFencePoint = directQueue->Signal(); + + THEN("Fence point is not reached") + { + REQUIRE_FALSE(directQueue->HasFencePointBeenReached(waitingFencePoint)); + } + + AND_WHEN("The future fence point is eventually signalled") + { + [[maybe_unused]] const auto actualFencePoint = copyQueue->Signal(); + const auto waitResult = directQueue->WaitOnFenceCPU(waitingFencePoint, Milliseconds{ 1u }); + + THEN("Future fence point has been reached") + { + REQUIRE(copyQueue->HasFencePointBeenReached(futureFencePoint)); + } + + THEN("Waiting queue is no longer waiting") + { + REQUIRE(waitResult.has_value()); + REQUIRE((waitResult.value() == Fence::ECPUWaitResult::FenceAlreadyReached || waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached)); + REQUIRE(directQueue->HasFencePointBeenReached(waitingFencePoint)); + } + } + } + } + } + + [[maybe_unused]] const auto directSignal = directQueue->Signal(); + [[maybe_unused]] const auto copySignal = copyQueue->Signal(); + } + + SECTION("Teardown") + { + EndTestCase(); + } + } +} \ No newline at end of file diff --git a/test/Private/D3D12/DescriptorHeapTests.cpp b/test/Private/D3D12/DescriptorHeapTests.cpp index 01a22ad9..a8cc692d 100644 --- a/test/Private/D3D12/DescriptorHeapTests.cpp +++ b/test/Private/D3D12/DescriptorHeapTests.cpp @@ -14,7 +14,7 @@ namespace DescriptorHeapTestPrivate public: Fixture() - : device(stf::MakeShared( + : device(stf::Object::New( stf::GPUDevice::CreationParams { })) diff --git a/test/Private/D3D12/DescriptorManagerTests.cpp b/test/Private/D3D12/DescriptorManagerTests.cpp new file mode 100644 index 00000000..fad687b9 --- /dev/null +++ b/test/Private/D3D12/DescriptorManagerTests.cpp @@ -0,0 +1,285 @@ + +#include "TestUtilities/ErrorMatchers.h" +#include +#include +#include + +#include + +#include +#include + +namespace DescriptorManagerTestPrivate +{ + class Fixture + { + public: + + Fixture() + : device(stf::Object::New( + stf::GPUDevice::CreationParams + { + })) + { + } + + protected: + + stf::SharedPtr device; + }; +} + +TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor Manager Tests") +{ + using namespace stf; + GIVEN("An initial size of 1") + { + auto manager = Object::New( + DescriptorManager::CreationParams{ + .Device = device, + .InitialSize = 1 + }); + + + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(0 == manager->GetSize()); + + WHEN("Allocation made") + { + auto firstAllocationResult = manager->Acquire(); + + REQUIRE(firstAllocationResult.has_value()); + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(1 == manager->GetSize()); + + AND_WHEN("Allocation released") + { + auto firstReleaseResult = manager->Release(firstAllocationResult.value()); + + THEN("Succeeds") + { + REQUIRE(firstReleaseResult.has_value()); + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(0 == manager->GetSize()); + } + + AND_WHEN("Allocation made") + { + auto secondAllocationResult = manager->Acquire(); + + THEN("Succeeds") + { + REQUIRE(secondAllocationResult.has_value()); + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(1 == manager->GetSize()); + } + } + + AND_WHEN("Same allocation released") + { + auto secondReleaseResult = manager->Release(firstAllocationResult.value()); + + THEN("Fails") + { + REQUIRE_FALSE(secondReleaseResult.has_value()); + REQUIRE_THAT(secondReleaseResult.error(), ErrorContainsFormat(Errors::BindlessFreeListAllocator::IndexAlreadyReleased(0u).Format())); + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(0 == manager->GetSize()); + } + } + } + + AND_WHEN("Allocation made") + { + auto secondAllocationResult = manager->Acquire(); + + THEN("Fails") + { + REQUIRE_FALSE(secondAllocationResult.has_value()); + REQUIRE(secondAllocationResult.error().HasFragment(Errors::DescriptorManagerIsFull())); + REQUIRE(1 == manager->GetCapacity()); + REQUIRE(1 == manager->GetSize()); + } + } + + AND_WHEN("Manager is resized to 4") + { + auto firstResizeResult = manager->Resize(4); + + REQUIRE(firstResizeResult.has_value()); + REQUIRE(4 == manager->GetCapacity()); + REQUIRE(1 == manager->GetSize()); + + AND_WHEN("Another allocation made") + { + auto secondAllocationResult = manager->Acquire(); + + THEN("Succeeds") + { + REQUIRE(secondAllocationResult.has_value()); + REQUIRE(4 == manager->GetCapacity()); + REQUIRE(2 == manager->GetSize()); + } + } + } + } + } + + GIVEN("A full descriptor manager") + { + constexpr u32 initialSize = 4; + auto manager = Object::New( + DescriptorManager::CreationParams{ + .Device = device, + .InitialSize = initialSize + }); + + const auto descriptors = + [&](const u32 InNum) + { + std::vector descriptors; + descriptors.reserve(InNum); + + for (u32 i = 0; i < InNum; ++i) + { + auto maybeDescriptor = manager->Acquire(); + REQUIRE(maybeDescriptor.has_value()); + auto descriptor = maybeDescriptor.value(); + + descriptors.push_back(descriptor); + } + + return descriptors; + }(initialSize); + + const auto resolvedDescriptors = + [&]() + { + std::vector resolvedDescriptors; + resolvedDescriptors.reserve(descriptors.size()); + + for (const auto& descriptor : descriptors) + { + auto maybeResolved = descriptor.Resolve(); + REQUIRE(maybeResolved.has_value()); + + resolvedDescriptors.push_back(maybeResolved.value()); + } + + return resolvedDescriptors; + }(); + + REQUIRE(initialSize == manager->GetCapacity()); + REQUIRE(initialSize == manager->GetSize()); + + THEN("All descriptors are unique") + { + for (u32 i = 0; i < (initialSize -1u); ++i) + { + for (u32 j = i + 1; j < initialSize; ++j) + { + REQUIRE(resolvedDescriptors[i] != resolvedDescriptors[j]); + } + } + } + + WHEN("Allocation made") + { + auto allocationResult = manager->Acquire(); + + THEN("Fails") + { + REQUIRE_FALSE(allocationResult.has_value()); + REQUIRE(allocationResult.error().HasFragment(Errors::DescriptorManagerIsFull())); + REQUIRE(4 == manager->GetCapacity()); + REQUIRE(4 == manager->GetSize()); + } + } + + WHEN("Second descriptor released") + { + auto releaseResult = manager->Release(descriptors[1]); + + THEN("release succeeds") + { + REQUIRE(releaseResult.has_value()); + REQUIRE(initialSize == manager->GetCapacity()); + REQUIRE((initialSize - 1) == manager->GetSize()); + } + + AND_WHEN("Another allocation made") + { + auto secondAllocationResult = manager->Acquire(); + + THEN("Allocation succeeds") + { + REQUIRE(secondAllocationResult.has_value()); + const auto maybeSecondResolved = secondAllocationResult.value().Resolve(); + REQUIRE(maybeSecondResolved.has_value()); + + REQUIRE(initialSize == manager->GetCapacity()); + REQUIRE(initialSize == manager->GetSize()); + REQUIRE(maybeSecondResolved.value() == resolvedDescriptors[1]); + } + } + } + + WHEN("Resized") + { + constexpr u32 newSize = initialSize + initialSize; + auto resizeResult = manager->Resize(newSize); + + REQUIRE(newSize == manager->GetCapacity()); + REQUIRE(initialSize == manager->GetSize()); + + AND_WHEN("Another allocation made") + { + auto secondAllocationResult = manager->Acquire(); + THEN("allocation succeeds") + { + REQUIRE(secondAllocationResult.has_value()); + const auto allocatedDescriptor = secondAllocationResult.value(); + const auto maybeResolvedAllocatedDescriptor = allocatedDescriptor.Resolve(); + REQUIRE(maybeResolvedAllocatedDescriptor.has_value()); + const auto resolvedAllocatedDescriptor = maybeResolvedAllocatedDescriptor.value(); + REQUIRE(newSize == manager->GetCapacity()); + REQUIRE(initialSize + 1 == manager->GetSize()); + + for (const auto& resovledDescriptor : resolvedDescriptors) + { + REQUIRE(resovledDescriptor.GetHeapIndex() != resolvedAllocatedDescriptor.GetHeapIndex()); + } + } + } + + AND_WHEN("descriptors are resolved again") + { + const auto newlyResolvedDescriptors = + [&]() + { + std::vector ret; + ret.reserve(descriptors.size()); + + std::ranges::transform(descriptors, std::back_inserter(ret), + [](const DescriptorManager::Descriptor& InDescriptor) + { + const auto maybeResolved = InDescriptor.Resolve(); + REQUIRE(maybeResolved.has_value()); + return maybeResolved.value(); + }); + + return ret; + }(); + + THEN("newly resolved descriptors are not equal by have same heap index") + { + for (const auto& [oldDescriptor, newDescriptor] : std::views::zip(resolvedDescriptors, newlyResolvedDescriptors)) + { + REQUIRE(oldDescriptor != newDescriptor); + REQUIRE(oldDescriptor.GetHeapIndex() == newDescriptor.GetHeapIndex()); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Private/D3D12/DescriptorTests.cpp b/test/Private/D3D12/DescriptorTests.cpp index adcc19ec..ce3f2d39 100644 --- a/test/Private/D3D12/DescriptorTests.cpp +++ b/test/Private/D3D12/DescriptorTests.cpp @@ -3,10 +3,77 @@ #include #include +#include #include #include +SCENARIO("DescriptorHandleTests") +{ + using namespace stf; + const auto [given, left, right, expected] = GENERATE( + table + ( + { + std::tuple + { + "Left and Right are equal", + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + true + }, + std::tuple + { + "CPU addresses differ", + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 24 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + false + }, + std::tuple + { + "GPU addresses differ", + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{34}, 2}, + false + }, + std::tuple + { + "Heap indexes differ", + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{144}, 2}, + DescriptorHandle{ D3D12_CPU_DESCRIPTOR_HANDLE{ 42 }, D3D12_GPU_DESCRIPTOR_HANDLE{34}, 12}, + false + } + } + ) + ); + + GIVEN(given) + { + WHEN("compared for equality") + { + const bool equals = left == right; + const bool notEquals = left != right; + + if (expected) + { + THEN("is equal") + { + REQUIRE(equals); + REQUIRE_FALSE(notEquals); + } + } + else + { + THEN("is not equal") + { + REQUIRE_FALSE(equals); + REQUIRE(notEquals); + } + } + } + } +} + SCENARIO("DescriptorRangeTests") { using namespace stf; diff --git a/test/Private/D3D12/FencedResourceFreeListTests.cpp b/test/Private/D3D12/FencedResourceFreeListTests.cpp new file mode 100644 index 00000000..7b3ec7f2 --- /dev/null +++ b/test/Private/D3D12/FencedResourceFreeListTests.cpp @@ -0,0 +1,225 @@ + +#include "TestUtilities/ErrorMatchers.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +class FencedResourceFreeListTestFixture +{ +protected: + + void BeginTestCase(const stf::GPUDevice::EDeviceType InType) const + { + device = stf::Object::New( + stf::GPUDevice::CreationParams + { + .DeviceType = InType + }); + } + + void EndTestCase() const + { + device = nullptr; + } + + mutable stf::SharedPtr device; +}; + +TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: FencedResourceFreeListTests") +{ + using namespace stf; + + using FreeListType = FencedResourceFreeList; + + const auto deviceType = GENERATE + ( + GPUDevice::EDeviceType::Hardware, + GPUDevice::EDeviceType::Software + ); + + auto getResource = + [](const FreeListType& InFreeList, const FreeListType::Handle InHandle) + { + const auto ret = InFreeList.Get(InHandle); + REQUIRE(ret); + return ret.value(); + }; + + auto resourceGenerator = + [id = 0]() mutable + { + return id++; + }; + + auto manageResource = + [&](FreeListType& InFreeList) + { + auto resource = resourceGenerator(); + const auto ret = resource; + const auto handle = InFreeList.Manage(std::move(resource)); + + REQUIRE(InFreeList.ValidateHandle(handle)); + const auto getResult = getResource(InFreeList, handle); + + REQUIRE(getResult == ret); + + return Tuple{ ret, handle }; + }; + + GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) + { + SECTION("Setup") + { + REQUIRE_FALSE(device); + BeginTestCase(deviceType); + REQUIRE(device); + } + + AND_GIVEN("An empty FencedResourcePool and two command queues created") + { + auto directQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + REQUIRE(directQueue); + + auto copyQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + REQUIRE(copyQueue); + + FreeListType freeList{ + FreeListType::CreationParams + { + .Queue = directQueue + } }; + + WHEN("Resource requested") + { + const auto [firstResource, firstHandle] = manageResource(freeList); + + AND_WHEN("resource is immediately released with no GPU work") + { + const auto releaseResult = freeList.Release(firstHandle); + REQUIRE(releaseResult); + + THEN("handle is no longer valid") + { + const auto getResult = freeList.Get(firstHandle); + REQUIRE_FALSE(getResult); + + REQUIRE_THAT(getResult.error(), ErrorContainsFormat(Errors::FencedResourceFreeList::StaleHandle(0, 0).Format())); + } + + AND_WHEN("resource is acquired again") + { + const auto [secondResource, secondHandle] = manageResource(freeList); + + THEN("first and second resource are different") + { + REQUIRE(firstResource != secondResource); + } + + THEN("trying to access first resource fails") + { + const auto getResultForFirst = freeList.Get(firstHandle); + + REQUIRE_FALSE(getResultForFirst); + REQUIRE_THAT(getResultForFirst.error(), ErrorContainsFormat(Errors::FencedResourceFreeList::StaleHandle(0, 0).Format())); + } + } + } + + AND_WHEN("Queue is executing work") + { + const auto firstCopyFence = copyQueue->NextSignal(); + directQueue->WaitOnFenceGPU(firstCopyFence); + + REQUIRE_FALSE(copyQueue->HasFencePointBeenReached(firstCopyFence)); + + AND_WHEN("resource is released") + { + const auto releaseResult = freeList.Release(firstHandle); + REQUIRE(releaseResult); + + AND_WHEN("resource is requested again") + { + const auto [secondResource, secondHandle] = manageResource(freeList); + + THEN("second handle is to a different resource from the first") + { + REQUIRE(firstResource != secondResource); + } + + AND_WHEN("yet another resource requested") + { + const auto [thirdResource, thirdHandle] = manageResource(freeList); + + THEN("third resource is different from the second") + { + REQUIRE(thirdResource != secondResource); + } + } + } + + AND_WHEN("GPU work has finished") + { + [[maybe_unused]] const auto finishedCopyFence = copyQueue->Signal(); + const auto nextDirectFence = directQueue->Signal(); + const auto waitResult = directQueue->WaitOnFenceCPU(nextDirectFence, Milliseconds{ 1u }); + + REQUIRE(waitResult.has_value()); + REQUIRE((waitResult.value() == Fence::ECPUWaitResult::FenceAlreadyReached || waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached)); + REQUIRE(directQueue->HasFencePointBeenReached(nextDirectFence)); + + AND_WHEN("resource is requested again") + { + const auto [secondResource, secondHandle] = manageResource(freeList); + + THEN("second handle is to a different resource from the first") + { + REQUIRE(firstResource != secondResource); + } + } + } + } + } + } + + [[maybe_unused]] const auto directSignal = directQueue->Signal(); + [[maybe_unused]] const auto copySignal = copyQueue->Signal(); + } + + SECTION("Teardown") + { + EndTestCase(); + } + } +} \ No newline at end of file diff --git a/test/Private/D3D12/FencedResourcePoolTests.cpp b/test/Private/D3D12/FencedResourcePoolTests.cpp new file mode 100644 index 00000000..b9f9b638 --- /dev/null +++ b/test/Private/D3D12/FencedResourcePoolTests.cpp @@ -0,0 +1,263 @@ + +#include +#include +#include +#include +#include + +#include "Utility/EnumReflection.h" +#include "Utility/Object.h" + +#include +#include +#include + +#include +#include + +class FencedResourcePoolTestFixture +{ +protected: + + void BeginTestCase(const stf::GPUDevice::EDeviceType InType) const + { + device = stf::Object::New( + stf::GPUDevice::CreationParams + { + .DeviceType = InType + }); + } + + void EndTestCase() const + { + device = nullptr; + } + + mutable stf::SharedPtr device; +}; + +TEST_CASE_PERSISTENT_FIXTURE(FencedResourcePoolTestFixture, "Scenario: FencedResourcePoolTests") +{ + using namespace stf; + + const auto deviceType = GENERATE + ( + GPUDevice::EDeviceType::Hardware, + GPUDevice::EDeviceType::Software + ); + + GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) + { + SECTION("Setup") + { + REQUIRE_FALSE(device); + BeginTestCase(deviceType); + REQUIRE(device); + } + + AND_GIVEN("An empty FencedResourcePool and two command queues created") + { + auto directQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + auto copyQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + } + ); + + auto pool = Object::New>( + FencedResourcePool::CreationParams + { + .CreateFunc = [id = 0]() mutable + { + return MakeShared(id++); + }, + .Queue = directQueue + }); + + std::vectorRequest())> resources; + + THEN("pool and queues are valid") + { + REQUIRE(directQueue); + REQUIRE(copyQueue); + REQUIRE(pool); + } + + WHEN("Resource requested") + { + resources.push_back(pool->Request()); + + THEN("resource is as expected") + { + REQUIRE(1 == resources.size()); + REQUIRE(resources[0]); + REQUIRE(0 == *resources[0]); + } + + AND_WHEN("resource is immediately released with no GPU work") + { + resources.pop_back(); + + AND_WHEN("resource is requested again") + { + resources.push_back(pool->Request()); + THEN("second resource is the same as the first") + { + REQUIRE(1 == resources.size()); + REQUIRE(resources[0]); + REQUIRE(0 == *resources[0]); + } + } + } + + AND_WHEN("Queue is executing work") + { + const auto firstCopyFence = copyQueue->NextSignal(); + directQueue->WaitOnFenceGPU(firstCopyFence); + + THEN("Fence has not been reached") + { + REQUIRE_FALSE(copyQueue->HasFencePointBeenReached(firstCopyFence)); + } + + AND_WHEN("resource is released") + { + resources.pop_back(); + + AND_WHEN("resource is requested again") + { + resources.push_back(pool->Request()); + + THEN("second resource is different from the first") + { + REQUIRE(1 == resources.size()); + REQUIRE(resources[0]); + REQUIRE(1 == *resources[0]); + } + + AND_WHEN("yet another resource requested") + { + resources.push_back(pool->Request()); + + THEN("third resource is different from the second") + { + REQUIRE(2 == resources.size()); + REQUIRE(resources[1]); + REQUIRE(2 == *resources[1]); + } + } + } + + AND_WHEN("GPU work has finished") + { + [[maybe_unused]] const auto finishedCopyFence = copyQueue->Signal(); + const auto nextDirectFence = directQueue->Signal(); + const auto waitResult = directQueue->WaitOnFenceCPU(nextDirectFence, Milliseconds{ 1u }); + + THEN("No longer waiting on fence") + { + REQUIRE(waitResult.has_value()); + REQUIRE((waitResult.value() == Fence::ECPUWaitResult::FenceAlreadyReached || waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached)); + REQUIRE(directQueue->HasFencePointBeenReached(nextDirectFence)); + } + + AND_WHEN("resource is requested again") + { + resources.push_back(pool->Request()); + THEN("second resource is the same as the first") + { + REQUIRE(1 == resources.size()); + REQUIRE(resources[0]); + REQUIRE(0 == *resources[0]); + } + } + } + } + } + } + + WHEN("Multiple resources are requested") + { + static constexpr i32 initialNumResources = 5; + + std::ranges::for_each(std::ranges::iota_view{ 0, initialNumResources }, + [&](i32) + { + resources.push_back(pool->Request()); + }); + + THEN("resources are as expected") + { + REQUIRE(initialNumResources == resources.size()); + std::ranges::for_each(std::ranges::iota_view{ 0, initialNumResources }, + [&](const i32 InIndex) + { + REQUIRE(InIndex == *resources[InIndex]); + }); + } + + AND_WHEN("Queue is executing work") + { + const auto firstFutureFence = copyQueue->NextSignal(); + directQueue->WaitOnFenceGPU(firstFutureFence); + + THEN("Fence has not been reached yet") + { + REQUIRE_FALSE(copyQueue->HasFencePointBeenReached(firstFutureFence)); + } + + AND_WHEN("Middle resource is released") + { + resources.erase(resources.begin() + initialNumResources / 2); + + AND_WHEN("queue finishes executing work") + { + [[maybe_unused]] const auto firstActualFence = copyQueue->Signal(); + const auto nextDirectFence = directQueue->Signal(); + const auto waitResult = directQueue->WaitOnFenceCPU(nextDirectFence, Milliseconds{ 1u }); + + THEN("queue is no longer executing work") + { + REQUIRE(waitResult.has_value()); + REQUIRE((waitResult.value() == Fence::ECPUWaitResult::FenceAlreadyReached || waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached)); + REQUIRE(directQueue->HasFencePointBeenReached(nextDirectFence)); + } + + AND_WHEN("resource is requested") + { + resources.push_back(pool->Request()); + + THEN("resource is the same as released") + { + REQUIRE(initialNumResources == resources.size()); + REQUIRE(initialNumResources / 2 == *resources.back()); + } + } + } + } + } + } + + [[maybe_unused]] const auto directSignal = directQueue->Signal(); + [[maybe_unused]] const auto copySignal = copyQueue->Signal(); + } + + SECTION("Teardown") + { + EndTestCase(); + } + } +} \ No newline at end of file diff --git a/test/Private/D3D12/Shader/HLSLTests.cpp b/test/Private/D3D12/Shader/HLSLTests.cpp index 698cec91..2dfb2045 100644 --- a/test/Private/D3D12/Shader/HLSLTests.cpp +++ b/test/Private/D3D12/Shader/HLSLTests.cpp @@ -311,7 +311,7 @@ SCENARIO("HLSLTests") { THEN("Compilation Succeeds") { - const auto error = errors.has_value() ? "" : errors.error(); + const auto error = errors.has_value() ? "" : std::format("{}", errors.error()); CAPTURE(error); REQUIRE(errors.has_value()); } diff --git a/test/Private/D3D12/Shader/ShaderBindingTests.cpp b/test/Private/D3D12/Shader/ShaderBindingTests.cpp index 6cc14dda..3c132e54 100644 --- a/test/Private/D3D12/Shader/ShaderBindingTests.cpp +++ b/test/Private/D3D12/Shader/ShaderBindingTests.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include diff --git a/test/Private/D3D12/Shader/ShaderIncludeHandlerTests.cpp b/test/Private/D3D12/Shader/ShaderIncludeHandlerTests.cpp index c7854c4c..a3ee5ff4 100644 --- a/test/Private/D3D12/Shader/ShaderIncludeHandlerTests.cpp +++ b/test/Private/D3D12/Shader/ShaderIncludeHandlerTests.cpp @@ -21,7 +21,7 @@ SCENARIO("ShaderIncludeHandlerTests") THEN("Success") { - const auto error = result.has_value() ? "" : result.error(); + const auto error = result.has_value() ? "" : std::format("{}", result.error()); CAPTURE(error); REQUIRE(result.has_value()); } diff --git a/test/Private/D3D12/Shader/ShaderModelTests.cpp b/test/Private/D3D12/Shader/ShaderModelTests.cpp index 6f2379f6..05c4ea35 100644 --- a/test/Private/D3D12/Shader/ShaderModelTests.cpp +++ b/test/Private/D3D12/Shader/ShaderModelTests.cpp @@ -119,7 +119,7 @@ SCENARIO("ShaderModelTests") { THEN("Compilation Succeeds") { - const auto error = errors.has_value() ? "" : errors.error(); + const auto error = errors.has_value() ? "" : std::format("{}", errors.error()); CAPTURE(error); REQUIRE(errors.has_value()); } diff --git a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp index 648a8232..5a760d11 100644 --- a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp +++ b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp @@ -1,14 +1,37 @@ #include "Framework/HLSLFramework/HLSLFrameworkTestsCommon.h" -#include +#include #include #include +#include #include #include -TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - Bindings - ValueBindings") +class ValueBindingsFixture + : public ShaderTestFixtureBaseFixture +{ +public: + + ValueBindingsFixture() + : ShaderTestFixtureBaseFixture( + stf::ShaderTestFixture::FixtureDesc + { + .Mappings{ GetTestVirtualDirectoryMapping() }, + .GPUDeviceParams + { + .DebugLevel = stf::GPUDevice::EDebugLevel::DebugLayer, + .DeviceType = stf::GPUDevice::EDeviceType::Software, + .EnableGPUCapture = false + } + } + ) + { + } +}; + +TEST_CASE_PERSISTENT_FIXTURE(ValueBindingsFixture, "HLSLFrameworkTests - Bindings - ValueBindings") { using namespace stf; @@ -16,13 +39,24 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { float A{ 4.0f }; i32 B{ 42 }; - int2 Padding1 {}; + int2 Padding1{}; float4 C{ 1.0f, 2.0f, 3.0f, 4.0f }; }; + struct TooLargeStruct + { + float2 D; + float2 Padding1; + int3 E{42, 2, 4}; + + i32 F[16]{}; + i32 G[16]{}; + i32 H[16]{}; + }; + auto [testName, testFile, bindings, expectedResult] = GENERATE ( - table, Expected> + table, ExpectedError> ( { std::tuple @@ -59,7 +93,7 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { "D", float3{5.0f, 6.0f, 7.0f}}, { "E", int3{123, 456, 789}} }, - Unexpected{ ErrorTypeAndDescription {.Type = ETestRunErrorType::Binding } } + Unexpected{ Errors::BindingIsSmallerThanBindingData("D", sizeof(float2), sizeof(float3))} }, std::tuple { @@ -72,22 +106,18 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { "E", int3{123, 456, 789}}, { "F", i32{ 234 }} }, - Unexpected{ ErrorTypeAndDescription { .Type = ETestRunErrorType::Binding } } + Unexpected{ Errors::BindingDoesNotExist("F") } }, std::tuple { - "Too Many Parameters", + "Too Many Parameters for Root sig constants", "GlobalBindingsTooLarge", std::vector { - { "MyParam", GlobalBindingsStruct{} }, - { "D", float2{5.0f, 6.0f}}, - { "E", int3{123, 456, 789}}, - { "F", std::array{}}, - { "G", std::array{}}, - { "H", std::array{}} + { "Param1", GlobalBindingsStruct{} }, + { "Param2", TooLargeStruct{}} }, - Unexpected{ ErrorTypeAndDescription {.Type = ETestRunErrorType::RootSignatureGeneration } } + true }, std::tuple { @@ -95,8 +125,14 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - "WithArray", std::vector { + { + "Param", std::array{ + 2.0f, 0.0f, 0.0f, 0.0f, + 42.0f + } + } }, - Unexpected{ ErrorTypeAndDescription {.Type = ETestRunErrorType::RootSignatureGeneration } } + true }, std::tuple { @@ -119,6 +155,29 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - {"SecondParam", GlobalBindingsStruct{.A = 102.5f, .B = 4195, .C{5.0f, 10.0f, 15.0f, 28.5f}}} }, false + }, + std::tuple + { + "Array of constant buffers", + "ConstantBufferArray", + std::vector + { + {"Buffs", std::array{4.0f, 4.0f}} + }, + Unexpected{ Errors::ConstantBufferMustBeBoundToDecriptorTable("Buffs")} + }, + std::tuple + { + "Params have alignment of less than 4 bytes", + "BindingsHave2ByteAlignment", + std::vector + { + {"Param1", u16{2}}, + {"Param2", u16{2}}, + {"Param3", u16{2}}, + {"Param4", u16{2}} + }, + true } } ) @@ -129,15 +188,15 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { return ShaderTestFixture::RuntimeTestDesc + { + .CompilationEnv { - .CompilationEnv - { - .Source = fs::path(std::format("/Tests/Binding/ValueBindingsTests/{}.hlsl", testFile)) - }, - .TestName = "Main", - .Bindings = std::move(bindings), - .ThreadGroupCount{1, 1, 1} - }; + .Source = fs::path(std::format("/Tests/Binding/ValueBindingsTests/{}.hlsl", testFile)) + }, + .TestName = "Main", + .Bindings = std::move(bindings), + .ThreadGroupCount{1, 1, 1} + }; }; DYNAMIC_SECTION(testName) @@ -145,15 +204,16 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - const auto actual = fixture.RunTest(getDesc()); if (expectedResult.has_value()) { - const auto results = actual.GetTestResults(); - REQUIRE(results); - REQUIRE(!!actual == expectedResult.value() ); + CAPTURE(actual); + + const bool testResult = actual; + REQUIRE(testResult == expectedResult.value()); } else { const auto results = actual.GetTestRunError(); REQUIRE(results); - REQUIRE(results->Type == expectedResult.error().Type); + REQUIRE(*results == expectedResult.error()); } } } \ No newline at end of file diff --git a/test/Private/Framework/HLSLFramework/TestDataBuffer/ResultsProcessing/AssertInfoWithDataTests.cpp b/test/Private/Framework/HLSLFramework/TestDataBuffer/ResultsProcessing/AssertInfoWithDataTests.cpp index db879792..abc96058 100644 --- a/test/Private/Framework/HLSLFramework/TestDataBuffer/ResultsProcessing/AssertInfoWithDataTests.cpp +++ b/test/Private/Framework/HLSLFramework/TestDataBuffer/ResultsProcessing/AssertInfoWithDataTests.cpp @@ -22,7 +22,7 @@ TEST_CASE_PERSISTENT_FIXTURE(AssertInfoWithDataTestsFixture, "HLSLFrameworkTests { using namespace stf; auto serializeImpl = OverloadSet{ - [] (const T& InVal, std::vector& InOutBytes) -> std::enable_if_t::Value> + [] (const T& InVal, std::vector& InOutBytes) -> std::enable_if_t::Value> { static constexpr u32 size = sizeof(T); static constexpr u32 align = alignof(T); diff --git a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp deleted file mode 100644 index 2bcc9b85..00000000 --- a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp +++ /dev/null @@ -1,258 +0,0 @@ - -#include -#include -#include -#include - -#include -#include - -namespace DescriptorManagerTestPrivate -{ - class Fixture - { - public: - - Fixture() - : device(stf::MakeShared( - stf::GPUDevice::CreationParams - { - })) - , resource(device->CreateCommittedResource( - CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), - D3D12_HEAP_FLAG_NONE, - CD3DX12_RESOURCE_DESC1::Buffer(1024, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS), - D3D12_BARRIER_LAYOUT_UNDEFINED)) - , uavDesc( - D3D12_UNORDERED_ACCESS_VIEW_DESC - { - .Format = DXGI_FORMAT_R32_TYPELESS, - .ViewDimension = D3D12_UAV_DIMENSION_BUFFER, - .Buffer - { - .FirstElement = 0, - .NumElements = 15, - .StructureByteStride = 0, - .CounterOffsetInBytes = 0, - .Flags = D3D12_BUFFER_UAV_FLAG_RAW - } - }) - { - } - - protected: - - stf::SharedPtr device; - stf::SharedPtr resource; - D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc; - }; -} - -TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor Manager Tests") -{ - using namespace stf; - GIVEN("An initial size of 1") - { - auto manager = MakeShared( - ShaderTestDescriptorManager::CreationParams{ - .Device = device, - .InitialSize = 1 - }); - - THEN("State is as expected") - { - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(0 == manager->GetSize()); - } - - WHEN("Allocation made") - { - auto firstAllocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Succeeds") - { - REQUIRE(firstAllocationResult.has_value()); - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(1 == manager->GetSize()); - } - - AND_WHEN("Allocation released") - { - auto firstReleaseResult = manager->ReleaseUAV(firstAllocationResult.value()); - - THEN("Succeeds") - { - REQUIRE(firstReleaseResult.has_value()); - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(0 == manager->GetSize()); - } - - AND_WHEN("Allocation made") - { - auto secondAllocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Succeeds") - { - REQUIRE(secondAllocationResult.has_value()); - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(1 == manager->GetSize()); - } - } - - AND_WHEN("Same allocation released") - { - auto secondReleaseResult = manager->ReleaseUAV(firstAllocationResult.value()); - - THEN("Fails") - { - REQUIRE_FALSE(secondReleaseResult.has_value()); - REQUIRE(secondReleaseResult.error() == ShaderTestDescriptorManager::EErrorType::DescriptorAlreadyFree); - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(0 == manager->GetSize()); - } - } - } - - AND_WHEN("Allocation made") - { - auto secondAllocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Fails") - { - REQUIRE_FALSE(secondAllocationResult.has_value()); - REQUIRE(secondAllocationResult.error() == ShaderTestDescriptorManager::EErrorType::AllocatorFull); - REQUIRE(1 == manager->GetCapacity()); - REQUIRE(1 == manager->GetSize()); - } - } - - AND_WHEN("Manager is resized to 4") - { - auto firstResizeResult = manager->Resize(4); - - THEN("Resize succeeds") - { - REQUIRE(firstResizeResult.has_value()); - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(1 == manager->GetSize()); - } - - AND_WHEN("Another allocation made") - { - auto secondAllocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Succeeds") - { - REQUIRE(secondAllocationResult.has_value()); - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(2 == manager->GetSize()); - } - } - } - } - } - - GIVEN("A full descriptor manager") - { - auto manager = MakeShared( - ShaderTestDescriptorManager::CreationParams{ - .Device = device, - .InitialSize = 4 - }); - using ResultType = decltype(manager->CreateUAV(resource, uavDesc)); - - std::vector uavs; - - for (i32 i = 0; i < 4; ++i) - { - uavs.push_back(manager->CreateUAV(resource, uavDesc)); - } - - THEN("State is as expected") - { - for (const auto& uav : uavs) - { - REQUIRE(uav.has_value()); - } - - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(4 == manager->GetSize()); - } - - THEN("All uavs are unique") - { - for (i32 i = 0; i < 3; ++i) - { - for (i32 j = i + 1; j < 4; ++j) - { - REQUIRE(uavs[i].value().Handle.GetIndex() != uavs[j].value().Handle.GetIndex()); - } - } - } - - WHEN("Allocation made") - { - auto allocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Fails") - { - REQUIRE_FALSE(allocationResult.has_value()); - REQUIRE(allocationResult.error() == ShaderTestDescriptorManager::EErrorType::AllocatorFull); - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(4 == manager->GetSize()); - } - } - - WHEN("Second uav released") - { - auto releaseResult = manager->ReleaseUAV(uavs[1].value()); - - THEN("release succeeds") - { - REQUIRE(releaseResult.has_value()); - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(3 == manager->GetSize()); - } - - AND_WHEN("Another allocation made") - { - auto secondAllocationResult = manager->CreateUAV(resource, uavDesc); - - THEN("Allocation succeeds") - { - REQUIRE(secondAllocationResult.has_value()); - REQUIRE(4 == manager->GetCapacity()); - REQUIRE(4 == manager->GetSize()); - REQUIRE(secondAllocationResult.value().Handle.GetIndex() == uavs[1].value().Handle.GetIndex()); - } - } - } - - WHEN("Resized") - { - auto resizeResult = manager->Resize(8); - - THEN("State is as expected") - { - REQUIRE(8 == manager->GetCapacity()); - REQUIRE(4 == manager->GetSize()); - } - - AND_WHEN("Another allocation made") - { - auto secondAllocationResult = manager->CreateUAV(resource, uavDesc); - THEN("allocation succeeds") - { - REQUIRE(secondAllocationResult.has_value()); - REQUIRE(8 == manager->GetCapacity()); - REQUIRE(5 == manager->GetSize()); - - for (const auto& uav : uavs) - { - REQUIRE(uav.value().Handle.GetIndex() != secondAllocationResult.value().Handle.GetIndex()); - } - } - } - } - } -} \ No newline at end of file diff --git a/test/Private/Framework/TestDataBufferProcessorTests.cpp b/test/Private/Framework/TestDataBufferProcessorTests.cpp index 4e58424f..31114f1c 100644 --- a/test/Private/Framework/TestDataBufferProcessorTests.cpp +++ b/test/Private/Framework/TestDataBufferProcessorTests.cpp @@ -196,8 +196,8 @@ namespace TestDataBufferProcessorTests ( { std::tuple{ "Default constructed", Results{}, false }, - std::tuple{ "Constructed from empty string", Results{ ErrorTypeAndDescription{ ETestRunErrorType::Unknown, ""} }, false}, - std::tuple{ "Constructed from non-empty string", Results{ ErrorTypeAndDescription{ ETestRunErrorType::Unknown, ""} }, false }, + std::tuple{ "Constructed from empty string", Results{ Error{} }, false}, + std::tuple{ "Constructed from non-empty string", Results{ Error{} }, false }, std::tuple{ "Zero Failed test run", Results{ TestRunResults{} }, true }, std::tuple{ "Non-zero failed test run", Results{ TestRunResults{ {}, {}, {}, 0, 1, uint3{} } }, false } } diff --git a/test/Private/Stats/StatSystemTests.cpp b/test/Private/Stats/StatSystemTests.cpp index 44619aff..33540296 100644 --- a/test/Private/Stats/StatSystemTests.cpp +++ b/test/Private/Stats/StatSystemTests.cpp @@ -62,7 +62,7 @@ SCENARIO("StatSystemTests") THEN("Flushed stat has expected info") { REQUIRE(stats[0].Name == expectedName); - REQUIRE(stats[0].Stat.Count() > 0); + REQUIRE(stats[0].Stat.count() > 0); } AND_WHEN("Stats flushed again without another scoped stat") @@ -92,7 +92,7 @@ SCENARIO("StatSystemTests") THEN("Flushed stat has expected info") { REQUIRE(newStats[0].Name == expectedSecondStat); - REQUIRE(newStats[0].Stat.Count() > 0); + REQUIRE(newStats[0].Stat.count() > 0); } } } @@ -114,9 +114,9 @@ SCENARIO("StatSystemTests") THEN("Flushed stats have expected info") { REQUIRE(newStats[0].Name == expectedName); - REQUIRE(newStats[0].Stat.Count() > 0); + REQUIRE(newStats[0].Stat.count() > 0); REQUIRE(newStats[1].Name == expectedSecondStat); - REQUIRE(newStats[1].Stat.Count() > 0); + REQUIRE(newStats[1].Stat.count() > 0); } } } diff --git a/test/Private/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 6decbaed..37ae6caa 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include namespace CallableTypeTests { @@ -403,6 +405,34 @@ namespace InstantiatableFromTests static_assert(InstantiatableFrom); } +namespace InstantiatableOfTests +{ + using namespace stf; + struct A {}; + struct B {}; + + template + struct OneTypeParam {}; + + template + struct OtherOneTypeParam {}; + + template + struct TwoTypeParam {}; + + template + struct OtherTwoTypeParam {}; + + + static_assert(InstantiationOf, OneTypeParam>); + static_assert(InstantiationOf, OneTypeParam>); + static_assert(!InstantiationOf, OtherOneTypeParam>); + static_assert(!InstantiationOf, OtherOneTypeParam>); + + static_assert(InstantiationOf, TwoTypeParam>); + static_assert(!InstantiationOf, OtherTwoTypeParam>); +} + namespace NewableTests { using namespace stf; @@ -415,4 +445,42 @@ namespace NewableTests static_assert(Newable); static_assert(!Newable); +} + +namespace ValidBitFieldTests +{ + using namespace stf; + + struct NonIntegral + { + }; + + static_assert(CValidBitField); + static_assert(CValidBitField); + static_assert(CValidBitField); + + static_assert(!CValidBitField); + static_assert(!CValidBitField); + static_assert(!CValidBitField); + static_assert(!CValidBitField); + + static_assert(!CValidBitField); + static_assert(!CValidBitField); +} + +namespace OStreamableTests +{ + using namespace stf; + struct TestOStreamable + { + friend std::ostream& operator<<(std::ostream& InStream, const TestOStreamable&) + { + return InStream; + } + }; + + struct TestNotOStreamable {}; + + static_assert(OStreamable); + static_assert(!OStreamable); } \ No newline at end of file diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp new file mode 100644 index 00000000..dcba94d0 --- /dev/null +++ b/test/Private/Utility/ErrorTests.cpp @@ -0,0 +1,424 @@ + +#include "TestUtilities/ErrorMatchers.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace stf::ErrorFragmentCompileTests +{ + static_assert(Formattable); +} + +namespace stf::ErrorFragmentCompileTests::ConstructionTests +{ + struct FormattableType {}; + struct UnformattableType {}; +} + +template<> +struct std::formatter : std::formatter { + auto format(const auto&, auto& ctx) const { + return std::formatter::format("Hi", ctx); + } +}; + +namespace stf::ErrorFragmentCompileTests::ConstructionTests +{ + template + concept TestConstructError = requires(ArgTypes&&... InArgs) + { + { ErrorFragment::Make<"Test">(std::forward(InArgs)...) }; + }; + + static_assert(Formattable); + static_assert(!Formattable); + + static_assert(TestConstructError, "Expected a single formattable argument to be valid"); + static_assert(TestConstructError, "Expected many formattable arguments to be valid"); + static_assert(TestConstructError<>, "Expected zero arguments to be valid"); + + static_assert(!TestConstructError, "Expected a single unformattable argument to be not be valid"); + static_assert(!TestConstructError, "Expected a single unformattable argument, among formattable arguments to be not be valid"); +} + +namespace stf::ErrorCompileTests::FormattableTests +{ + static_assert(Formattable); +} + +namespace stf::ErrorCompileTests::ConceptsTests +{ + struct A {}; + struct B {}; + + using AExpectedError = ExpectedError; + using AExpected = Expected; + + static_assert(ExpectedErrorType); + static_assert(!ExpectedErrorType); + static_assert(!ExpectedErrorType); + + static_assert(ExpectedErrorWithValueType); + static_assert(!ExpectedErrorWithValueType); +} + +SCENARIO("ErrorFragment Tests") +{ + using namespace stf; + using Catch::Matchers::ContainsSubstring; + + GIVEN("No args") + { + static constexpr FixedString expected = "Test"; + const auto noArgs = ErrorFragment::Make(); + + THEN("State has expected") + { + REQUIRE(noArgs.Format().View() == expected.View()); + REQUIRE(noArgs.Error() == expected.View()); + REQUIRE(noArgs.HasFormat(StringLiteral{ expected.View() })); + } + } + + GIVEN("Two error fragments with same format and no args") + { + static constexpr FixedString expected = "Test"; + const auto errorFrag1 = ErrorFragment::Make(); + const auto errorFrag2 = ErrorFragment::Make(); + + THEN("are equal") + { + REQUIRE(errorFrag1 == errorFrag2); + REQUIRE_FALSE(errorFrag1 != errorFrag2); + + REQUIRE(errorFrag1.HasSameFormat(errorFrag2)); + } + } + + GIVEN("Two error fragments with same format and same args") + { + static constexpr FixedString expected = "Test {}"; + constexpr i32 expectedArg = 42; + const auto errorFrag1 = ErrorFragment::Make(expectedArg); + const auto errorFrag2 = ErrorFragment::Make(expectedArg); + + THEN("are equal and has expected arg") + { + REQUIRE(errorFrag1 == errorFrag2); + REQUIRE_FALSE(errorFrag1 != errorFrag2); + + REQUIRE(errorFrag1.HasSameFormat(errorFrag2)); + + REQUIRE_THAT(std::string{ errorFrag1.Error() }, ContainsSubstring(std::format("{}", expectedArg))); + REQUIRE_THAT(std::string{ errorFrag2.Error() }, ContainsSubstring(std::format("{}", expectedArg))); + } + } + + GIVEN("Two error fragments with same format and different args") + { + static constexpr FixedString expected = "Test {}"; + constexpr i32 expectedArg1 = 42; + constexpr i32 expectedArg2 = 24; + const auto errorFrag1 = ErrorFragment::Make(expectedArg1); + const auto errorFrag2 = ErrorFragment::Make(expectedArg2); + + THEN("are not equal and have expected arg") + { + REQUIRE(errorFrag1 != errorFrag2); + REQUIRE_FALSE(errorFrag1 == errorFrag2); + + REQUIRE(errorFrag1.HasSameFormat(errorFrag2)); + + REQUIRE_THAT(std::string{ errorFrag1.Error() }, ContainsSubstring(std::format("{}", expectedArg1))); + REQUIRE_THAT(std::string{ errorFrag2.Error() }, ContainsSubstring(std::format("{}", expectedArg2))); + } + } + + GIVEN("Two error fragments with different format and no args") + { + static constexpr FixedString expected1 = "Test"; + static constexpr FixedString expected2 = "Other Test"; + const auto errorFrag1 = ErrorFragment::Make(); + const auto errorFrag2 = ErrorFragment::Make(); + + THEN("are not equal") + { + REQUIRE(errorFrag1 != errorFrag2); + REQUIRE_FALSE(errorFrag1 == errorFrag2); + + REQUIRE_FALSE(errorFrag1.HasSameFormat(errorFrag2)); + } + } + + GIVEN("Two error fragments with different format and same args") + { + static constexpr FixedString expected1 = "Test {}"; + static constexpr FixedString expected2 = "Other Test {}"; + constexpr i32 expectedArg = 42; + const auto errorFrag1 = ErrorFragment::Make(expectedArg); + const auto errorFrag2 = ErrorFragment::Make(expectedArg); + + THEN("are not equal and have expected arg") + { + REQUIRE(errorFrag1 != errorFrag2); + REQUIRE_FALSE(errorFrag1 == errorFrag2); + + REQUIRE_FALSE(errorFrag1.HasSameFormat(errorFrag2)); + + REQUIRE_THAT(std::string{ errorFrag1.Error() }, ContainsSubstring(std::format("{}", expectedArg))); + REQUIRE_THAT(std::string{ errorFrag2.Error() }, ContainsSubstring(std::format("{}", expectedArg))); + } + } + + GIVEN("Two error fragments with different format and different args") + { + static constexpr FixedString expected1 = "Test {}"; + static constexpr FixedString expected2 = "Other Test {}"; + constexpr i32 expectedArg1 = 42; + constexpr i32 expectedArg2 = 24; + const auto errorFrag1 = ErrorFragment::Make(expectedArg1); + const auto errorFrag2 = ErrorFragment::Make(expectedArg2); + + THEN("are not equal and have expected arg") + { + REQUIRE(errorFrag1 != errorFrag2); + REQUIRE_FALSE(errorFrag1 == errorFrag2); + + REQUIRE_FALSE(errorFrag1.HasSameFormat(errorFrag2)); + + REQUIRE_THAT(std::string{ errorFrag1.Error() }, ContainsSubstring(std::format("{}", expectedArg1))); + REQUIRE_THAT(std::string{ errorFrag2.Error() }, ContainsSubstring(std::format("{}", expectedArg2))); + } + } +} + +SCENARIO("Error Tests") +{ + using namespace stf; + using Catch::Matchers::ContainsSubstring; + + GIVEN("Default constructed error") + { + Error error{}; + + THEN("prints empty string") + { + REQUIRE(std::format("{}", error) == std::string{}); + } + + WHEN("Appended") + { + const auto errorFrag1 = ErrorFragment::Make<"Error 1">(); + + error.Append(errorFrag1); + + THEN("Error contains error fragment") + { + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag1.Format())); + REQUIRE_THAT(error, ErrorContains(errorFrag1)); + + const auto formattedError = std::format("{}", error); + const auto streamError = [&]() + { + std::stringstream stringBuffer; + stringBuffer << error; + return stringBuffer.str(); + }(); + + REQUIRE(formattedError == streamError); + + REQUIRE_THAT(formattedError, ContainsSubstring(std::format("{}", errorFrag1.Error()))); + } + + AND_WHEN("Appended to again") + { + const auto errorFrag2 = ErrorFragment::Make<"Error 2: {}, {}">(42, 56); + + error += errorFrag2; + + THEN("Both error fragments exist and the latest fragment is before the first") + { + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag1.Format())); + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag2.Format())); + + const auto errorMessage = std::format("{}", error); + + const auto streamError = [&]() + { + std::stringstream stringBuffer; + stringBuffer << error; + return stringBuffer.str(); + }(); + + REQUIRE(errorMessage == streamError); + + const auto error1Range = std::ranges::search(errorMessage, errorFrag1.Error()); + const auto error2Range = std::ranges::search(errorMessage, errorFrag2.Error()); + + REQUIRE(error1Range); + REQUIRE(error2Range); + + REQUIRE(error2Range.cend() < error1Range.cbegin()); + } + } + } + } + + GIVEN("Constructed from a fragment") + { + static constexpr FixedString expectedFormat{ "Error 1" }; + const auto error = Error::FromFragment(); + + THEN("contains expected fragment") + { + REQUIRE_THAT(error, ErrorContainsFormat(expectedFormat.Literal())); + } + } + + GIVEN("Two errors constructed by appending different fragments") + { + + const ErrorFragment frag1 = ErrorFragment::Make<"1">(); + const ErrorFragment frag2 = ErrorFragment::Make<"2">(); + const ErrorFragment frag3 = ErrorFragment::Make<"3">(); + const ErrorFragment frag4 = ErrorFragment::Make<"4">(); + + const Error error1 = frag1 + frag2; + const Error error2 = frag3 + frag4; + + THEN("Errors are as expected") + { + REQUIRE(error1 != error2); + REQUIRE(error1.HasFragmentWithFormat(frag1.Format())); + REQUIRE(error1.HasFragmentWithFormat(frag2.Format())); + REQUIRE(error2.HasFragmentWithFormat(frag3.Format())); + REQUIRE(error2.HasFragmentWithFormat(frag4.Format())); + REQUIRE_FALSE(error2.HasFragmentWithFormat(frag1.Format())); + REQUIRE_FALSE(error2.HasFragmentWithFormat(frag2.Format())); + REQUIRE_FALSE(error1.HasFragmentWithFormat(frag3.Format())); + REQUIRE_FALSE(error1.HasFragmentWithFormat(frag4.Format())); + + REQUIRE_THAT(error1, ErrorContainsFormat(frag1.Format())); + REQUIRE_THAT(error1, ErrorContainsFormat(frag2.Format())); + REQUIRE_THAT(error2, ErrorContainsFormat(frag3.Format())); + REQUIRE_THAT(error2, ErrorContainsFormat(frag4.Format())); + REQUIRE_THAT(error2, !ErrorContainsFormat(frag1.Format())); + REQUIRE_THAT(error2, !ErrorContainsFormat(frag2.Format())); + REQUIRE_THAT(error1, !ErrorContainsFormat(frag3.Format())); + REQUIRE_THAT(error1, !ErrorContainsFormat(frag4.Format())); + + WHEN("errors are appended") + { + const Error error3 = error1 + error2; + + THEN("new error contains all fragments") + { + REQUIRE(error3.HasFragmentWithFormat(frag1.Format())); + REQUIRE(error3.HasFragmentWithFormat(frag2.Format())); + REQUIRE(error3.HasFragmentWithFormat(frag3.Format())); + REQUIRE(error3.HasFragmentWithFormat(frag4.Format())); + + REQUIRE_THAT(error3, ErrorContainsFormat(frag1.Format())); + REQUIRE_THAT(error3, ErrorContainsFormat(frag2.Format())); + REQUIRE_THAT(error3, ErrorContainsFormat(frag3.Format())); + REQUIRE_THAT(error3, ErrorContainsFormat(frag4.Format())); + } + } + } + } +} + +SCENARIO("Error comparison tests") +{ + using namespace stf; + const auto [given, left, right, expected] = GENERATE( + table + ( + { + std::tuple + { + "Errors with same format and no args", + Error::FromFragment<"Error">(), + Error::FromFragment<"Error">(), + true + }, + std::tuple + { + "Errors with different formats and no args", + Error::FromFragment<"Error">(), + Error::FromFragment<"OtherError">(), + false + }, + std::tuple + { + "Errors with same format and args", + Error::FromFragment<"Error {}">(42), + Error::FromFragment<"Error {}">(42), + true + }, + std::tuple + { + "Errors with same format and different args", + Error::FromFragment<"Error {}">(42), + Error::FromFragment<"Error {}">(24), + false + }, + std::tuple + { + "Errors with different format and same args", + Error::FromFragment<"Error 1{}">(42), + Error::FromFragment<"Error 2{}">(42), + false + }, + std::tuple + { + "Errors with different format and different args", + Error::FromFragment<"Error 1{}">(42), + Error::FromFragment<"Error 2{}">(24), + false + }, + std::tuple + { + "Errors with differing number of fragments", + Error::FromFragment<"Error 1">(), + Error::FromFragment<"Error 1">() += ErrorFragment::Make<"Error 2">(), + false + } + } + ) + ); + + GIVEN(given) + { + WHEN("compared") + { + const auto equalResult = left == right; + const auto notEqualResult = left != right; + + if (expected) + { + THEN("compares as equal") + { + REQUIRE(equalResult); + REQUIRE_FALSE(notEqualResult); + } + } + else + { + THEN("compares as not equal") + { + REQUIRE_FALSE(equalResult); + REQUIRE(notEqualResult); + } + } + } + } +} \ No newline at end of file diff --git a/test/Private/Utility/ExpectedTests.cpp b/test/Private/Utility/ExpectedTests.cpp new file mode 100644 index 00000000..ad82958c --- /dev/null +++ b/test/Private/Utility/ExpectedTests.cpp @@ -0,0 +1,97 @@ + + +#include + +#include +#include +#include + +#include + +namespace ExpectedTests +{ + struct OStreamableType + { + inline static const std::string ExpectedString{ "Hello there" }; + + friend std::ostream& operator<<(std::ostream& InOutStream, const OStreamableType&) + { + InOutStream << ExpectedString; + return InOutStream; + } + }; + + struct NotOStreamableType {}; + + const std::string ExpectedUnstreamableString{ stf::TypeToString() }; + + template + std::string StreamExpected(bool InValid) + { + const stf::Expected expected = [&]() -> stf::Expected + { + if (InValid) + { + return stf::Expected{T{}}; + } + else + { + return stf::Unexpected{E{}}; + } + }(); + + std::stringstream buff; + + buff << expected; + + return buff.str(); + } +} + +SCENARIO("Expected tests") +{ + using namespace stf; + using namespace ExpectedTests; + using Catch::Matchers::ContainsSubstring; + + const auto [given, actual, expected] = GENERATE( + table + ( + { + std::tuple{ + "Valid expected has steamable value", + StreamExpected(true), + OStreamableType::ExpectedString + }, + std::tuple{ + "Invalid expected has unstreamable value", + StreamExpected(false), + ExpectedUnstreamableString + }, + std::tuple{ + "Invalid expected has steamable value", + StreamExpected(false), + OStreamableType::ExpectedString + }, + std::tuple{ + "Valid expected has unstreamable value", + StreamExpected(true), + ExpectedUnstreamableString + } + } + ) + ); + + + GIVEN(given) + { + WHEN("streamed") + { + THEN("Expected string streamed") + { + REQUIRE_THAT(actual, ContainsSubstring(expected)); + } + } + } + +} \ No newline at end of file diff --git a/test/Private/Framework/HLSLTypesTests.cpp b/test/Private/Utility/HLSLTypesTests.cpp similarity index 99% rename from test/Private/Framework/HLSLTypesTests.cpp rename to test/Private/Utility/HLSLTypesTests.cpp index 4b717424..1f50c691 100644 --- a/test/Private/Framework/HLSLTypesTests.cpp +++ b/test/Private/Utility/HLSLTypesTests.cpp @@ -1,4 +1,4 @@ -#include "Framework/HLSLTypes.h" +#include #include #include diff --git a/test/Private/Utility/ObjectTests.cpp b/test/Private/Utility/ObjectTests.cpp index a21cd9df..ed550a5d 100644 --- a/test/Private/Utility/ObjectTests.cpp +++ b/test/Private/Utility/ObjectTests.cpp @@ -1,23 +1,65 @@ +#include #include -#include +namespace stf::ObjectTests::ObjectClassTests +{ + static_assert(!std::movable, "Expected Object to not be movable"); + static_assert(!std::copyable, "Expected Object to not be copyable"); + static_assert(!std::default_initializable, "Expected Object to not be default constructible"); + + static_assert(std::has_virtual_destructor_v, "Expected Object to have a virtual destructor"); + static_assert(std::constructible_from, "Expected an Object to be constructible from an ObjectToken"); +} + +namespace stf::ObjectTests::ObjectTypeConceptTests +{ + struct NotInheritedFromObject {}; + struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken) {} }; + struct PrivateInherit : private Object { PrivateInherit(ObjectToken In) : Object(In) {} }; + struct PublicInherit : public Object { PublicInherit(ObjectToken In) : Object(In) {} }; + + static_assert(!ObjectType, "Expected Object itself to not pass the ObjectType concept"); + static_assert(!ObjectType, "Expected a class that does not inherit from Object to not pass the ObjectType concept"); + static_assert(!ObjectType, "Expected a class that does not inherit from Object but takes an ObjectToken to construct to not pass the ObjectType concept"); + static_assert(!ObjectType, "Expected Private inheritance to not pass the ObjectType concept"); + static_assert(ObjectType, "Expected Public inheritance to pass the ObjectType concept"); +} -namespace ObjectTests +namespace stf::ObjectTests::ObjectTypeConstructionTests { + struct NotInheritedFromObject {}; + struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken) {} }; + struct PrivateInherit : private Object { PrivateInherit(ObjectToken In) : Object(In) {} }; + struct PublicInherit : public Object { PublicInherit(ObjectToken In) : Object(In) {} }; + template - concept NotObjectType = requires(T InA, T InB) + concept TestConstructibleWithObjectNew = requires() { - { T{ InA } }; - { T{ std::move(InB) } }; - { InA = InB }; - { InA = std::move(InB) }; + { Object::New() } -> std::same_as>; }; - template - concept ObjectType = !NotObjectType; + static_assert(!TestConstructibleWithObjectNew, "Expected Object itself to not be constructible using Object::New"); + static_assert(!TestConstructibleWithObjectNew, "Expected a class that does not inherit from Object to not be constructible using Object::New"); + static_assert(!TestConstructibleWithObjectNew, "Expected a class that does not inherit from Object but takes an ObjectToken to construct to not be constructible using Object::New"); + static_assert(!TestConstructibleWithObjectNew, "Expected Private inheritance to not be constructible using Object::New"); + static_assert(TestConstructibleWithObjectNew, "Expected Public inheritance to be constructible using Object::New"); +} - struct TestObject : private stf::Object {}; +namespace stf::ObjectTests::SharedFromThisTests +{ + struct TestObject : Object + { + TestObject(ObjectToken InToken) : Object(InToken) {} + + template + decltype(auto) Get(this ThisType&& InThis) + { + return std::forward(InThis).SharedFromThis(); + } + }; - static_assert(ObjectType, "Expected a class that inherits from stf::Object to conform to this concept"); -} \ No newline at end of file + static_assert(std::same_as().Get()), SharedPtr>); + static_assert(std::same_as().Get()), SharedPtr>); + static_assert(std::same_as()).Get()), SharedPtr>); +} diff --git a/test/Private/Utility/PointerTests.cpp b/test/Private/Utility/PointerTests.cpp new file mode 100644 index 00000000..48c9b686 --- /dev/null +++ b/test/Private/Utility/PointerTests.cpp @@ -0,0 +1,138 @@ + +#include + +namespace stf::MakeUniqueTests +{ + template + concept TestMakeUnique = requires(Ts... InArgs) + { + { MakeUnique(InArgs...) } -> std::same_as>; + }; + + struct ParamA {}; + + struct ParamB {}; + + struct OnlyDefault + { + OnlyDefault() {} + }; + + struct TakesA + { + TakesA(ParamA) {} + }; + + struct TakesB + { + TakesB(ParamB) {} + }; + + struct TakesAThenB + { + TakesAThenB(ParamA, ParamB) {} + }; + + struct TakesBThenA + { + TakesBThenA(ParamB, ParamA) {} + }; + + static_assert(TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + + static_assert(!TestMakeUnique); + static_assert(TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(TestMakeUnique); + static_assert(!TestMakeUnique); + + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(!TestMakeUnique); + static_assert(TestMakeUnique); +} + +namespace stf::MakeSharedTests +{ + template + concept TestMakeShared = requires(Ts... InArgs) + { + { MakeShared(InArgs...) } -> std::same_as>; + }; + + struct ParamA {}; + + struct ParamB {}; + + struct OnlyDefault + { + OnlyDefault() {} + }; + + struct TakesA + { + TakesA(ParamA) {} + }; + + struct TakesB + { + TakesB(ParamB) {} + }; + + struct TakesAThenB + { + TakesAThenB(ParamA, ParamB) {} + }; + + struct TakesBThenA + { + TakesBThenA(ParamB, ParamA) {} + }; + + static_assert(TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + + static_assert(!TestMakeShared); + static_assert(TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(TestMakeShared); + static_assert(!TestMakeShared); + + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(!TestMakeShared); + static_assert(TestMakeShared); +} \ No newline at end of file diff --git a/test/Private/Utility/TypeTraitsTests.cpp b/test/Private/Utility/TypeTraitsTests.cpp index 7cffdd93..a13daae4 100644 --- a/test/Private/Utility/TypeTraitsTests.cpp +++ b/test/Private/Utility/TypeTraitsTests.cpp @@ -41,10 +41,10 @@ namespace TIsInstantiationOfTests using NTTPInstantiation = NTTPTemplate; - static_assert(TIsInstantiationOf>::Value); - static_assert(!TIsInstantiationOf>::Value); + static_assert(TIsInstantiationOf, TestTemplate>::Value); + static_assert(!TIsInstantiationOf, OtherTestTemplate>::Value); - static_assert(!TIsInstantiationOf::Value); + static_assert(!TIsInstantiationOf::Value); // This will not compile //static_assert(TIsInstantiationOf::Value); diff --git a/test/Private/Utility/VersionedIndexTests.cpp b/test/Private/Utility/VersionedIndexTests.cpp new file mode 100644 index 00000000..b77a099f --- /dev/null +++ b/test/Private/Utility/VersionedIndexTests.cpp @@ -0,0 +1,100 @@ + +#include + +#include +#include + +namespace VersionedIndexCompileTests +{ + using namespace stf; + template + concept CValidInstantiation = requires() + { + { VersionedIndex{} }; + }; + + static_assert(!CValidInstantiation); + static_assert(!CValidInstantiation); + static_assert(CValidInstantiation); + static_assert(CValidInstantiation); + + static_assert(VersionedIndex::MaxIndex == (1 << 4) - 1); + + static_assert(VersionedIndex::MaxVersion == (1 << 3) - 1); +} + +SCENARIO("VersionedIndexTests - Valid Constructions") +{ + using namespace stf; + static constexpr u32 IndexBits = 24; + using u32Handle = VersionedIndex; + + const auto [given, handle, expectedIndex] = GENERATE( + table + ( + { + std::tuple{"Default constructed", u32Handle{}, 0 }, + std::tuple{"Initialized with max index", u32Handle{u32Handle::MaxIndex}, u32Handle::MaxIndex } + } + ) + ); + + GIVEN(given) + { + THEN("State is as expected") + { + REQUIRE(handle.GetIndex() == expectedIndex); + REQUIRE(handle.GetVersion() == 0u); + REQUIRE(handle == handle); + REQUIRE_FALSE(handle != handle); + + AND_WHEN("versioned") + { + const auto versioned = handle.Next(); + + THEN("versioned handle has same index but is not equal") + { + REQUIRE(versioned.GetIndex() == expectedIndex); + REQUIRE(versioned.GetVersion() == 1u); + REQUIRE(handle != versioned); + REQUIRE_FALSE(handle == versioned); + + AND_WHEN("and when versioned more than the max version times") + { + const auto onePlusMaxVersion = + [&]() + { + auto ret = handle; + for (u32 i = 0; i <= u32Handle::MaxVersion; ++i) + { + ret = ret.Next(); + } + + return ret; + }(); + + THEN("version wraps and is now equal to original handle") + { + REQUIRE(onePlusMaxVersion == handle); + } + } + } + } + } + } +} + +SCENARIO("VersionedIndexTests - Invalid Construction") +{ + using namespace stf; + static constexpr u32 IndexBits = 24; + using u32Handle = VersionedIndex; + + GIVEN("Constructed with index greater than max") + { + THEN("construction throws") + { + REQUIRE_THROWS(u32Handle{ u32Handle::MaxIndex + 1 }); + } + } +} \ No newline at end of file diff --git a/test/Public/TestUtilities/ErrorMatchers.h b/test/Public/TestUtilities/ErrorMatchers.h new file mode 100644 index 00000000..1460fd44 --- /dev/null +++ b/test/Public/TestUtilities/ErrorMatchers.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +namespace stf +{ + class ErrorContainsMatcher + : public ::Catch::Matchers::MatcherGenericBase + { + public: + + ErrorContainsMatcher(const ErrorFragment& InError) + : m_ErrorFragment{ InError } + { + } + + ErrorContainsMatcher(ErrorFragment&& InError) + : m_ErrorFragment{ std::move(InError) } + { + } + + bool match(const Error& InError) const + { + return InError.HasFragment(m_ErrorFragment); + } + + std::string describe() const override + { + return std::format("contains fragment: {}", m_ErrorFragment); + } + + private: + ErrorFragment m_ErrorFragment; + }; + + class ErrorContainsFormatMatcher + : public ::Catch::Matchers::MatcherGenericBase + { + public: + + ErrorContainsFormatMatcher(StringLiteral&& InFormat) + : m_Format{ std::move(InFormat) } + { + } + + ErrorContainsFormatMatcher(const StringLiteral& InFormat) + : m_Format{ InFormat } + { + } + + bool match(const Error& InError) const + { + return InError.HasFragmentWithFormat(m_Format); + } + + std::string describe() const override + { + return std::format("contains fragment with format: {}", m_Format.View()); + } + + private: + StringLiteral m_Format; + }; + + inline ErrorContainsMatcher ErrorContains(const ErrorFragment& InError) + { + return ErrorContainsMatcher{ InError }; + } + + inline ErrorContainsMatcher ErrorContains(ErrorFragment&& InError) + { + return ErrorContainsMatcher{ std::move(InError) }; + } + + inline ErrorContainsFormatMatcher ErrorContainsFormat(const StringLiteral& InFormat) + { + return ErrorContainsFormatMatcher{ InFormat }; + } + + inline ErrorContainsFormatMatcher ErrorContainsFormat(StringLiteral&& InFormat) + { + return ErrorContainsFormatMatcher{ std::move(InFormat) }; + } +} \ No newline at end of file diff --git a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/BindingsHave2ByteAlignment.hlsl b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/BindingsHave2ByteAlignment.hlsl new file mode 100644 index 00000000..a80c5e5b --- /dev/null +++ b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/BindingsHave2ByteAlignment.hlsl @@ -0,0 +1,15 @@ +#include "/Test/STF/ShaderTestFramework.hlsli" + +uint16_t Param1; +uint16_t Param2; +uint16_t Param3; +uint16_t Param4; + +[numthreads(1, 1, 1)] +void Main() +{ + ASSERT(AreEqual, Param1, (uint16_t)2); + ASSERT(AreEqual, Param2, (uint16_t)2); + ASSERT(AreEqual, Param3, (uint16_t)2); + ASSERT(AreEqual, Param4, (uint16_t)2); +} \ No newline at end of file diff --git a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/ConstantBufferArray.hlsl b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/ConstantBufferArray.hlsl new file mode 100644 index 00000000..83e01d8b --- /dev/null +++ b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/ConstantBufferArray.hlsl @@ -0,0 +1,14 @@ +#include "/Test/STF/ShaderTestFramework.hlsli" + +struct MyStruct +{ + float A; +}; + +ConstantBuffer Buffs[2]; + +[numthreads(1, 1, 1)] +void Main() +{ + ASSERT(AreEqual, Buffs[0].A, 4.0f); +} \ No newline at end of file diff --git a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindingsTooLarge.hlsl b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindingsTooLarge.hlsl index 1cc45f7f..ae3da71e 100644 --- a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindingsTooLarge.hlsl +++ b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/GlobalBindingsTooLarge.hlsl @@ -7,21 +7,23 @@ struct MyStruct float4 C; }; -MyStruct MyParam; +MyStruct Param1; -float2 D; -int3 E; +struct TooLarge +{ + float2 D; + int3 E; + + int4x4 F; + int4x4 G; + int4x4 H; +}; -int4x4 F; -int4x4 G; -int4x4 H; +ConstantBuffer Param2; [numthreads(1, 1, 1)] void Main() { - ASSERT(AreEqual, MyParam.A, 4.0f); - ASSERT(AreEqual, MyParam.B, 42); - ASSERT(AreEqual, MyParam.C, float4(1.0, 2.0, 3.0, 4.0)); - ASSERT(AreEqual, D, float2(5.0, 6.0)); - ASSERT(AreEqual, E, int3(123, 456, 789)); + ASSERT(AreEqual, Param1.A, 4.0f); + ASSERT(AreEqual, Param2.E.x, 42); } \ No newline at end of file diff --git a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/WithArray.hlsl b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/WithArray.hlsl index 031ba822..dede37e7 100644 --- a/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/WithArray.hlsl +++ b/test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/WithArray.hlsl @@ -5,5 +5,6 @@ float Param[2]; [numthreads(1, 1, 1)] void Main() { - ASSERT(AreEqual, Param[0], 4.0f); + ASSERT(AreEqual, Param[0], 2.0f); + ASSERT(AreEqual, Param[1], 42.0f); } \ No newline at end of file