From 262a4da5c8cb15ff9fa69f20a54e662a3ebf4e78 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 4 Jan 2025 23:29:03 +0000 Subject: [PATCH 01/76] Guarding MakeUnique and MakeShared with a requires clause. Mainly so that we get better error messages when MakeShared calls are incorrect. Also adding tests --- src/Public/Utility/Pointer.h | 7 ++ test/CMakeLists.txt | 1 + test/Private/Utility/PointerTests.cpp | 138 ++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 test/Private/Utility/PointerTests.cpp 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/test/CMakeLists.txt b/test/CMakeLists.txt index 0f03240d..5f2f559e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -206,6 +206,7 @@ set(SOURCES Private/Utility/FunctionTraitsTests.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 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 From c6cb715f0f2823ffd51839abdb914b5b68c71a82 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 5 Jan 2025 12:11:41 +0000 Subject: [PATCH 02/76] Completely refactor Object. Object now enables shared from this. Anything derived from Object can now only be created using Object::New. Thiis forces all objects derived from Object to be publicly derived and to be owned by a SharedPtr to ensure that shared_from_this never causes issues --- src/Private/D3D12/CommandAllocator.cpp | 5 +- src/Private/D3D12/CommandEngine.cpp | 5 +- src/Private/D3D12/CommandList.cpp | 5 +- src/Private/D3D12/CommandQueue.cpp | 5 +- src/Private/D3D12/DescriptorHeap.cpp | 5 +- src/Private/D3D12/Fence.cpp | 5 +- src/Private/D3D12/GPUDevice.cpp | 19 +++---- src/Private/D3D12/GPUResource.cpp | 5 +- src/Private/D3D12/Shader/PipelineState.cpp | 5 +- src/Private/D3D12/Shader/RootSignature.cpp | 5 +- .../Framework/ShaderTestDescriptorManager.cpp | 5 +- src/Private/Framework/ShaderTestDriver.cpp | 9 ++-- src/Private/Framework/ShaderTestFixture.cpp | 6 +-- src/Private/Framework/ShaderTestShader.cpp | 5 +- src/Public/D3D12/CommandAllocator.h | 5 +- src/Public/D3D12/CommandEngine.h | 5 +- src/Public/D3D12/CommandList.h | 6 +-- src/Public/D3D12/CommandQueue.h | 5 +- src/Public/D3D12/DescriptorHeap.h | 6 +-- src/Public/D3D12/Fence.h | 6 +-- src/Public/D3D12/FencedResourcePool.h | 31 ++++++++++++ src/Public/D3D12/GPUDevice.h | 8 +-- src/Public/D3D12/GPUResource.h | 6 +-- src/Public/D3D12/Shader/PipelineState.h | 6 +-- src/Public/D3D12/Shader/RootSignature.h | 10 ++-- .../Framework/ShaderTestDescriptorManager.h | 5 +- src/Public/Framework/ShaderTestDriver.h | 5 +- src/Public/Framework/ShaderTestShader.h | 5 +- src/Public/Utility/Object.h | 31 +++++++++++- test/Private/D3D12/DescriptorHeapTests.cpp | 2 +- .../ShaderTestDescriptorManagerTests.cpp | 6 +-- test/Private/Utility/ObjectTests.cpp | 50 ++++++++++++++----- 32 files changed, 192 insertions(+), 95 deletions(-) create mode 100644 src/Public/D3D12/FencedResourcePool.h 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..8707a9c4 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -2,8 +2,9 @@ namespace stf { - CommandEngine::CommandEngine(CreationParams InParams) - : m_Device(InParams.Device) + CommandEngine::CommandEngine(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Device(InParams.Device) , m_Queue(InParams.Device->CreateCommandQueue( { .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, diff --git a/src/Private/D3D12/CommandList.cpp b/src/Private/D3D12/CommandList.cpp index 5091d388..94c4951e 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)) { } diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index 7d2d4cdf..cf2bb640 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)) { } 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/Fence.cpp b/src/Private/D3D12/Fence.cpp index 807ae9ce..0995b0b2 100644 --- a/src/Private/D3D12/Fence.cpp +++ b/src/Private/D3D12/Fence.cpp @@ -2,8 +2,9 @@ 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) { } diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 6b2950f6..635fa07f 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -121,8 +121,9 @@ 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) @@ -185,7 +186,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 @@ -194,7 +195,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 @@ -202,7 +203,7 @@ 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), std::move(CreateFence(0ull)) }); + return Object::New(CommandQueue::CreationParams{ std::move(raw), std::move(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 @@ -222,7 +223,7 @@ namespace stf 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} }); + return Object::New(GPUResource::CreationParams{ std::move(raw), InClearValue ? std::optional{*InClearValue} : std::nullopt, {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InInitialLayout} }); } SharedPtr GPUDevice::CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC& InDesc, const std::string_view InName) const @@ -232,7 +233,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 @@ -241,7 +242,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 @@ -255,7 +256,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 diff --git a/src/Private/D3D12/GPUResource.cpp b/src/Private/D3D12/GPUResource.cpp index 07192730..9e964fad 100644 --- a/src/Private/D3D12/GPUResource.cpp +++ b/src/Private/D3D12/GPUResource.cpp @@ -4,8 +4,9 @@ namespace stf { - GPUResource::GPUResource(CreationParams InParams) noexcept - : m_Resource(std::move(InParams.Resource)) + GPUResource::GPUResource(ObjectToken InToken, CreationParams InParams) noexcept + : Object(InToken) + , m_Resource(std::move(InParams.Resource)) , m_ClearValue(InParams.ClearValue) , m_CurrentBarrier(InParams.InitialBarrier) { 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/Framework/ShaderTestDescriptorManager.cpp b/src/Private/Framework/ShaderTestDescriptorManager.cpp index 1a2892e1..e47e8596 100644 --- a/src/Private/Framework/ShaderTestDescriptorManager.cpp +++ b/src/Private/Framework/ShaderTestDescriptorManager.cpp @@ -5,8 +5,9 @@ namespace stf { - ShaderTestDescriptorManager::ShaderTestDescriptorManager(CreationParams InParams) - : m_Device(InParams.Device) + ShaderTestDescriptorManager::ShaderTestDescriptorManager(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_Device(InParams.Device) , m_GPUHeap(InParams.Device->CreateDescriptorHeap( D3D12_DESCRIPTOR_HEAP_DESC { diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index f889f4a9..9a907bd7 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -25,15 +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( + , m_DescriptorManager(Object::New( ShaderTestDescriptorManager::CreationParams{ .Device = m_Device, .InitialSize = 16 diff --git a/src/Private/Framework/ShaderTestFixture.cpp b/src/Private/Framework/ShaderTestFixture.cpp index 4333eb4e..00010196 100644 --- a/src/Private/Framework/ShaderTestFixture.cpp +++ b/src/Private/Framework/ShaderTestFixture.cpp @@ -28,8 +28,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 @@ -177,7 +177,7 @@ namespace stf .transform( [this](CompiledShaderData InData) { - return MakeShared(ShaderTestShader::CreationParams{ .ShaderData = std::move(InData), .Device = m_Device }); + return Object::New(ShaderTestShader::CreationParams{ .ShaderData = std::move(InData), .Device = m_Device }); }) .transform_error( [](std::string InError) diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp index a871ff6c..ccf99de3 100644 --- a/src/Private/Framework/ShaderTestShader.cpp +++ b/src/Private/Framework/ShaderTestShader.cpp @@ -4,8 +4,9 @@ namespace stf { - ShaderTestShader::ShaderTestShader(CreationParams InParams) - : m_ShaderData(std::move(InParams.ShaderData)) + ShaderTestShader::ShaderTestShader(ObjectToken InToken, CreationParams InParams) + : Object(InToken) + , m_ShaderData(std::move(InParams.ShaderData)) , m_Device(std::move(InParams.Device)) , m_RootSignature() , m_NameToBindingInfo() 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..97fa8ef3 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -64,7 +64,8 @@ namespace stf CommandList* m_List = nullptr; }; - class CommandEngine : Object + class CommandEngine + : public Object { public: @@ -73,7 +74,7 @@ namespace stf SharedPtr Device; }; - CommandEngine(CreationParams InParams); + CommandEngine(ObjectToken, CreationParams InParams); template void Execute(const InLambdaType& InFunc) diff --git a/src/Public/D3D12/CommandList.h b/src/Public/D3D12/CommandList.h index 49c19a92..f6eb4d51 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); diff --git a/src/Public/D3D12/CommandQueue.h b/src/Public/D3D12/CommandQueue.h index d5c67299..e32f65a9 100644 --- a/src/Public/D3D12/CommandQueue.h +++ b/src/Public/D3D12/CommandQueue.h @@ -10,7 +10,8 @@ namespace stf { class CommandList; - class CommandQueue : Object + class CommandQueue + : public Object { public: @@ -21,7 +22,7 @@ namespace stf }; CommandQueue() = default; - CommandQueue(CreationParams InParams); + CommandQueue(ObjectToken, CreationParams InParams); ~CommandQueue(); bool HasFencePointBeenReached(const Fence::FencePoint& InFencePoint) const; 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/Fence.h b/src/Public/D3D12/Fence.h index 4af4f40a..c87ae576 100644 --- a/src/Public/D3D12/Fence.h +++ b/src/Public/D3D12/Fence.h @@ -9,7 +9,8 @@ namespace stf { - class Fence : Object + class Fence + : public Object { public: @@ -35,8 +36,7 @@ namespace stf u64 InitialValue = 0; }; - Fence() = default; - Fence(CreationParams InParams); + Fence(ObjectToken, CreationParams InParams); [[nodiscard]] FencePoint Signal(ID3D12CommandQueue* InQueue); bool WaitCPU(const FencePoint& InFencePoint) const; diff --git a/src/Public/D3D12/FencedResourcePool.h b/src/Public/D3D12/FencedResourcePool.h new file mode 100644 index 00000000..1309d79e --- /dev/null +++ b/src/Public/D3D12/FencedResourcePool.h @@ -0,0 +1,31 @@ + +#pragma once + +#include "Platform.h" + +#include "Container/RingBuffer.h" +#include "D3D12/Fence.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" + +#include + +namespace stf +{ + template + class FencedResourcePool : Object + { + public: + + private: + + struct FencedResource + { + SharedPtr Resource; + Fence::FencePoint FencePoint; + }; + + RingBuffer m_Pool; + std::function()> m_CreateFunc; + }; +} \ No newline at end of file diff --git a/src/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index e8473b4e..d5bc8d99 100644 --- a/src/Public/D3D12/GPUDevice.h +++ b/src/Public/D3D12/GPUDevice.h @@ -108,7 +108,8 @@ namespace stf std::is_same_v || std::is_same_v; - class GPUDevice : Object + class GPUDevice + : public Object { public: @@ -132,8 +133,7 @@ namespace stf bool EnableGPUCapture = false; }; - GPUDevice() = default; - GPUDevice(const CreationParams InDesc); + GPUDevice(ObjectToken, const CreationParams InDesc); ~GPUDevice(); bool IsValid() const; @@ -168,7 +168,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; diff --git a/src/Public/D3D12/GPUResource.h b/src/Public/D3D12/GPUResource.h index 7702bcc1..698dd022 100644 --- a/src/Public/D3D12/GPUResource.h +++ b/src/Public/D3D12/GPUResource.h @@ -45,7 +45,8 @@ namespace stf std::span m_MappedData; }; - class GPUResource : Object + class GPUResource + : public Object { public: @@ -56,8 +57,7 @@ namespace stf GPUEnhancedBarrier InitialBarrier{}; }; - GPUResource() = default; - GPUResource(CreationParams InParams) noexcept; + GPUResource(ObjectToken, CreationParams InParams) noexcept; ID3D12Resource2* GetRaw() const noexcept; operator ID3D12Resource2* () const noexcept; 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/Framework/ShaderTestDescriptorManager.h b/src/Public/Framework/ShaderTestDescriptorManager.h index 0fec6a26..91912d63 100644 --- a/src/Public/Framework/ShaderTestDescriptorManager.h +++ b/src/Public/Framework/ShaderTestDescriptorManager.h @@ -21,7 +21,8 @@ namespace stf BindlessFreeListAllocator::BindlessIndex Handle; }; - class ShaderTestDescriptorManager : Object + class ShaderTestDescriptorManager + : public Object { public: @@ -42,7 +43,7 @@ namespace stf template using Expected = Expected; - ShaderTestDescriptorManager(CreationParams InParams); + ShaderTestDescriptorManager(ObjectToken, CreationParams InParams); [[nodiscard]] Expected CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); Expected ReleaseUAV(const ShaderTestUAV& InUAV); diff --git a/src/Public/Framework/ShaderTestDriver.h b/src/Public/Framework/ShaderTestDriver.h index 60a85000..43e01e12 100644 --- a/src/Public/Framework/ShaderTestDriver.h +++ b/src/Public/Framework/ShaderTestDriver.h @@ -21,7 +21,8 @@ namespace stf { - class ShaderTestDriver : Object + class ShaderTestDriver + : public Object { public: @@ -39,7 +40,7 @@ namespace stf uint3 DispatchConfig; }; - ShaderTestDriver(CreationParams InParams); + ShaderTestDriver(ObjectToken, 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); diff --git a/src/Public/Framework/ShaderTestShader.h b/src/Public/Framework/ShaderTestShader.h index e7745961..9394c7e6 100644 --- a/src/Public/Framework/ShaderTestShader.h +++ b/src/Public/Framework/ShaderTestShader.h @@ -21,7 +21,8 @@ namespace stf { - class ShaderTestShader : Object + class ShaderTestShader + : public Object { public: struct CreationParams @@ -30,7 +31,7 @@ namespace stf SharedPtr Device; }; - ShaderTestShader(CreationParams InParams); + ShaderTestShader(ObjectToken, CreationParams InParams); Expected Init(); Expected BindConstantBufferData(const std::span InBindings); diff --git a/src/Public/Utility/Object.h b/src/Public/Utility/Object.h index 8e542dd4..4f0e7fba 100644 --- a/src/Public/Utility/Object.h +++ b/src/Public/Utility/Object.h @@ -1,15 +1,42 @@ #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() noexcept {} + + template + requires ObjectType + static SharedPtr New(ParamTypes&&... InArgs) + { + return MakeShared(ObjectToken{}, std::forward(InArgs)...); + } }; } \ 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/Framework/ShaderTestDescriptorManagerTests.cpp b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp index 2bcc9b85..8cc29330 100644 --- a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp +++ b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp @@ -14,7 +14,7 @@ namespace DescriptorManagerTestPrivate public: Fixture() - : device(stf::MakeShared( + : device(stf::Object::New( stf::GPUDevice::CreationParams { })) @@ -53,7 +53,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor using namespace stf; GIVEN("An initial size of 1") { - auto manager = MakeShared( + auto manager = Object::New( ShaderTestDescriptorManager::CreationParams{ .Device = device, .InitialSize = 1 @@ -154,7 +154,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor GIVEN("A full descriptor manager") { - auto manager = MakeShared( + auto manager = Object::New( ShaderTestDescriptorManager::CreationParams{ .Device = device, .InitialSize = 4 diff --git a/test/Private/Utility/ObjectTests.cpp b/test/Private/Utility/ObjectTests.cpp index a21cd9df..c5d45adb 100644 --- a/test/Private/Utility/ObjectTests.cpp +++ b/test/Private/Utility/ObjectTests.cpp @@ -1,23 +1,47 @@ +#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 In) {} }; + 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 In) {} }; + 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; - - struct TestObject : private stf::Object {}; - - static_assert(ObjectType, "Expected a class that inherits from stf::Object to conform to this concept"); + 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"); } \ No newline at end of file From 3dde82ea6796e06067c8a4294d9954a04f2c4acd Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 5 Jan 2025 12:23:44 +0000 Subject: [PATCH 03/76] Fix unused parameter error in object tests --- test/Private/Utility/ObjectTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Private/Utility/ObjectTests.cpp b/test/Private/Utility/ObjectTests.cpp index c5d45adb..48aca8ff 100644 --- a/test/Private/Utility/ObjectTests.cpp +++ b/test/Private/Utility/ObjectTests.cpp @@ -15,7 +15,7 @@ namespace stf::ObjectTests::ObjectClassTests namespace stf::ObjectTests::ObjectTypeConceptTests { struct NotInheritedFromObject {}; - struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken In) {} }; + struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken) {} }; struct PrivateInherit : private Object { PrivateInherit(ObjectToken In) : Object(In) {} }; struct PublicInherit : public Object { PublicInherit(ObjectToken In) : Object(In) {} }; @@ -29,7 +29,7 @@ namespace stf::ObjectTests::ObjectTypeConceptTests namespace stf::ObjectTests::ObjectTypeConstructionTests { struct NotInheritedFromObject {}; - struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken In) {} }; + struct NotInheritedFromObjectTakesObjectToken { NotInheritedFromObjectTakesObjectToken(ObjectToken) {} }; struct PrivateInherit : private Object { PrivateInherit(ObjectToken In) : Object(In) {} }; struct PublicInherit : public Object { PublicInherit(ObjectToken In) : Object(In) {} }; From bdc63abf83cc37c5faed79c8b7600003a86108d2 Mon Sep 17 00:00:00 2001 From: KStocky Date: Wed, 8 Jan 2025 18:53:26 +0000 Subject: [PATCH 04/76] Refactoring Time utilities to just use chrono rather than my own chrono --- src/Public/Stats/StatSystem.h | 7 ++- src/Public/Utility/Concepts.h | 3 ++ src/Public/Utility/Time.h | 73 +++++--------------------- test/Private/Stats/StatSystemTests.cpp | 8 +-- 4 files changed, 23 insertions(+), 68 deletions(-) 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..5e6496df 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -29,6 +29,9 @@ 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; 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/test/Private/Stats/StatSystemTests.cpp b/test/Private/Stats/StatSystemTests.cpp index 369ca310..154164ba 100644 --- a/test/Private/Stats/StatSystemTests.cpp +++ b/test/Private/Stats/StatSystemTests.cpp @@ -59,7 +59,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") @@ -88,7 +88,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); } } } @@ -109,9 +109,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); } } } From a3494fe56d77a77c28a8037e4a547dd2b3245bf8 Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 9 Jan 2025 17:54:00 +0000 Subject: [PATCH 05/76] Refactoring Fence. Tests not made because they would be flakey tests at best especially the tests for WaitOnCPU --- src/Private/D3D12/CommandQueue.cpp | 4 +- src/Private/D3D12/Fence.cpp | 99 ++++++++++++++++++++++-------- src/Public/D3D12/Fence.h | 33 ++++++++-- 3 files changed, 102 insertions(+), 34 deletions(-) diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index cf2bb640..be9eea5b 100644 --- a/src/Private/D3D12/CommandQueue.cpp +++ b/src/Private/D3D12/CommandQueue.cpp @@ -25,7 +25,7 @@ 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) @@ -35,7 +35,7 @@ namespace stf void CommandQueue::SyncWithQueue(CommandQueue& InQueue) { - InQueue.GetFence().WaitOnQueue(m_Queue.Get()); + InQueue.GetFence().WaitOnQueue(*m_Queue.Get(), Signal()); } void CommandQueue::FlushQueue() diff --git a/src/Private/D3D12/Fence.cpp b/src/Private/D3D12/Fence.cpp index 0995b0b2..b5e042f0 100644 --- a/src/Private/D3D12/Fence.cpp +++ b/src/Private/D3D12/Fence.cpp @@ -1,4 +1,5 @@ #include "D3D12/Fence.h" +#include "Utility/Exception.h" namespace stf { @@ -9,43 +10,41 @@ namespace stf { } - 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]() + { + return m_Fence->GetCompletedValue() >= InFencePoint.SignalledValue; + } + ); } ID3D12Fence* Fence::GetRaw() const @@ -58,8 +57,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/Public/D3D12/Fence.h b/src/Public/D3D12/Fence.h index c87ae576..3c9a25d2 100644 --- a/src/Public/D3D12/Fence.h +++ b/src/Public/D3D12/Fence.h @@ -4,7 +4,7 @@ #include "Utility/Expected.h" #include "Utility/Object.h" #include "Utility/Pointer.h" - +#include "Utility/Time.h" #include namespace stf @@ -36,19 +36,40 @@ namespace stf u64 InitialValue = 0; }; + enum class EErrorType + { + FencePointIsFromAnotherFence, + ErrorCreatingEvent, + WaitFailed + }; + + enum class ECPUWaitResult + { + FenceAlreadyReached, + WaitTimedOut, + WaitFenceReached + }; + + template + using Expected = Expected; + Fence(ObjectToken, CreationParams InParams); - [[nodiscard]] FencePoint Signal(ID3D12CommandQueue* InQueue); - bool WaitCPU(const FencePoint& InFencePoint) const; - void WaitOnQueue(ID3D12CommandQueue* InQueue) const; - Expected HasCompleted(const FencePoint& InFencePoint) const; + [[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; From b29742252c01838645892e7f5bc041802c418cd5 Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 9 Jan 2025 18:47:26 +0000 Subject: [PATCH 06/76] CommandQueue tests --- src/Private/D3D12/CommandQueue.cpp | 18 +++- src/Public/D3D12/CommandQueue.h | 7 +- test/CMakeLists.txt | 1 + test/Private/D3D12/CommandQueueTests.cpp | 120 +++++++++++++++++++++++ 4 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 test/Private/D3D12/CommandQueueTests.cpp diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index be9eea5b..3a0e2dd1 100644 --- a/src/Private/D3D12/CommandQueue.cpp +++ b/src/Private/D3D12/CommandQueue.cpp @@ -28,19 +28,29 @@ namespace stf 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); + } + + 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(), Signal()); + WaitOnFenceGPU(InQueue.Signal()); } void CommandQueue::FlushQueue() { - WaitOnFence(Signal()); + WaitOnFenceCPU(Signal()); } void CommandQueue::ExecuteCommandList(CommandList& InList) diff --git a/src/Public/D3D12/CommandQueue.h b/src/Public/D3D12/CommandQueue.h index e32f65a9..f11be950 100644 --- a/src/Public/D3D12/CommandQueue.h +++ b/src/Public/D3D12/CommandQueue.h @@ -21,13 +21,14 @@ namespace stf SharedPtr Fence{}; }; - CommandQueue() = default; 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); + void WaitOnFenceGPU(const Fence::FencePoint& InFencePoint); void SyncWithQueue(CommandQueue& InQueue); void FlushQueue(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5f2f559e..e0805aae 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -139,6 +139,7 @@ set(SOURCES 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/DescriptorRingAllocatorTests.cpp diff --git a/test/Private/D3D12/CommandQueueTests.cpp b/test/Private/D3D12/CommandQueueTests.cpp new file mode 100644 index 00000000..672a30ea --- /dev/null +++ b/test/Private/D3D12/CommandQueueTests.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +#include + +#include +#include + +SCENARIO("CommandQueueTests") +{ + using namespace stf; + + const auto deviceType = GENERATE + ( + GPUDevice::EDeviceType::Hardware, + GPUDevice::EDeviceType::Software + ); + + GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) + { + auto device = Object::New( + GPUDevice::CreationParams + { + .DebugLevel = GPUDevice::EDebugLevel::DebugLayer, + .DeviceType = deviceType, + .EnableGPUCapture = false + }); + + AND_GIVEN("Two command queues created") + { + auto directQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT + } + ); + + auto copyQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY + } + ); + + 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") + { + const auto actualFencePoint = copyQueue->Signal(); + + THEN("Future fence point has been reached") + { + REQUIRE(copyQueue->HasFencePointBeenReached(futureFencePoint)); + } + + THEN("Waiting queue still not signalled") + { + REQUIRE_FALSE(directQueue->HasFencePointBeenReached(waitingFencePoint)); + } + + AND_WHEN("wait for small amount of time") + { + const auto waitResult = directQueue->GetFence().WaitCPU(waitingFencePoint, Milliseconds{1u}); + + THEN("Waiting queue is no longer waiting") + { + REQUIRE(waitResult.has_value()); + REQUIRE(waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached); + REQUIRE(directQueue->HasFencePointBeenReached(waitingFencePoint)); + } + } + + } + } + } + } + + [[maybe_unused]] const auto directSignal = directQueue->Signal(); + [[maybe_unused]] const auto copySignal = copyQueue->Signal(); + } + } +} \ No newline at end of file From b56dd7a9d576028b0fb2f165df3fc10744a0263f Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 11 Jan 2025 18:26:46 +0000 Subject: [PATCH 07/76] Adding SharedFromThis function to object to provide a type safe way of getting a shared_from_this pointer of the same type as the derived type. --- src/Public/Utility/Object.h | 10 +++++++++- test/Private/Utility/ObjectTests.cpp | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Public/Utility/Object.h b/src/Public/Utility/Object.h index 4f0e7fba..f0c62dff 100644 --- a/src/Public/Utility/Object.h +++ b/src/Public/Utility/Object.h @@ -30,7 +30,7 @@ namespace stf Object& operator=(const Object&) = delete; Object& operator=(Object&&) = delete; - virtual ~Object() noexcept {} + virtual ~Object() {} template requires ObjectType @@ -38,5 +38,13 @@ namespace stf { 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/test/Private/Utility/ObjectTests.cpp b/test/Private/Utility/ObjectTests.cpp index 48aca8ff..ed550a5d 100644 --- a/test/Private/Utility/ObjectTests.cpp +++ b/test/Private/Utility/ObjectTests.cpp @@ -44,4 +44,22 @@ namespace stf::ObjectTests::ObjectTypeConstructionTests 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"); -} \ No newline at end of file +} + +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(std::same_as().Get()), SharedPtr>); + static_assert(std::same_as().Get()), SharedPtr>); + static_assert(std::same_as()).Get()), SharedPtr>); +} From e0845bcfc307da11799f809fdf35f6d256752b5c Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 12 Jan 2025 16:18:01 +0000 Subject: [PATCH 08/76] Making CommandQueue tests 3x faster (3 seconds -> ~900ms). Did this by moving the creation of the GPUDevice to a persistance fixture. One consequence to this is that we no longer have to wait on the GPU for the GPU fence to be hit. I don't know why caching the GPUDevice results in this behaviour. But the change in behaviour is the same for both hardware and WARP devices. --- test/Private/D3D12/CommandQueueTests.cpp | 59 +++++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/test/Private/D3D12/CommandQueueTests.cpp b/test/Private/D3D12/CommandQueueTests.cpp index 672a30ea..6a52ca3e 100644 --- a/test/Private/D3D12/CommandQueueTests.cpp +++ b/test/Private/D3D12/CommandQueueTests.cpp @@ -8,7 +8,29 @@ #include #include -SCENARIO("CommandQueueTests") +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; @@ -20,13 +42,12 @@ SCENARIO("CommandQueueTests") GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) { - auto device = Object::New( - GPUDevice::CreationParams - { - .DebugLevel = GPUDevice::EDebugLevel::DebugLayer, - .DeviceType = deviceType, - .EnableGPUCapture = false - }); + SECTION("Setup") + { + REQUIRE_FALSE(device); + BeginTestCase(deviceType); + REQUIRE(device); + } AND_GIVEN("Two command queues created") { @@ -91,23 +112,10 @@ SCENARIO("CommandQueueTests") REQUIRE(copyQueue->HasFencePointBeenReached(futureFencePoint)); } - THEN("Waiting queue still not signalled") + THEN("Waiting queue is no longer waiting") { - REQUIRE_FALSE(directQueue->HasFencePointBeenReached(waitingFencePoint)); + REQUIRE(directQueue->HasFencePointBeenReached(waitingFencePoint)); } - - AND_WHEN("wait for small amount of time") - { - const auto waitResult = directQueue->GetFence().WaitCPU(waitingFencePoint, Milliseconds{1u}); - - THEN("Waiting queue is no longer waiting") - { - REQUIRE(waitResult.has_value()); - REQUIRE(waitResult.value() == Fence::ECPUWaitResult::WaitFenceReached); - REQUIRE(directQueue->HasFencePointBeenReached(waitingFencePoint)); - } - } - } } } @@ -116,5 +124,10 @@ SCENARIO("CommandQueueTests") [[maybe_unused]] const auto directSignal = directQueue->Signal(); [[maybe_unused]] const auto copySignal = copyQueue->Signal(); } + + SECTION("Teardown") + { + EndTestCase(); + } } } \ No newline at end of file From 2936b8ce5e6863c3355ea136ff2de783f1bdaec6 Mon Sep 17 00:00:00 2001 From: KStocky Date: Wed, 15 Jan 2025 09:37:51 +0000 Subject: [PATCH 09/76] FencedResourcePool and tests. Also added a WaitOnFenceCPU overload for CommandQueue that has a timeout. This is because it is clear that the time between a wait ending and the next action on a queue occuring is indeterminate so we need to wait. --- src/CMakeLists.txt | 1 + src/Private/D3D12/CommandQueue.cpp | 5 + src/Public/D3D12/CommandQueue.h | 2 + src/Public/D3D12/FencedResourcePool.h | 105 ++++++- test/CMakeLists.txt | 1 + test/Private/D3D12/CommandQueueTests.cpp | 3 + .../Private/D3D12/FencedResourcePoolTests.cpp | 257 ++++++++++++++++++ 7 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 test/Private/D3D12/FencedResourcePoolTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cfd7f85..2b04aaba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES Public/D3D12/DescriptorHeap.h Public/D3D12/DescriptorRingAllocator.h Public/D3D12/Fence.h + Public/D3D12/FencedResourcePool.h Public/D3D12/GPUDevice.h Public/D3D12/GPUResource.h Public/D3D12/Shader/CompiledShaderData.h diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index 3a0e2dd1..872eaf02 100644 --- a/src/Private/D3D12/CommandQueue.cpp +++ b/src/Private/D3D12/CommandQueue.cpp @@ -38,6 +38,11 @@ namespace stf 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); diff --git a/src/Public/D3D12/CommandQueue.h b/src/Public/D3D12/CommandQueue.h index f11be950..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 @@ -28,6 +29,7 @@ namespace stf [[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/FencedResourcePool.h b/src/Public/D3D12/FencedResourcePool.h index 1309d79e..b9c08f92 100644 --- a/src/Public/D3D12/FencedResourcePool.h +++ b/src/Public/D3D12/FencedResourcePool.h @@ -4,7 +4,10 @@ #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" @@ -13,10 +16,109 @@ namespace stf { template - class FencedResourcePool : Object + 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 @@ -27,5 +129,6 @@ namespace stf RingBuffer m_Pool; std::function()> m_CreateFunc; + SharedPtr m_Queue; }; } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e0805aae..012be04a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -144,6 +144,7 @@ set(SOURCES Private/D3D12/DescriptorHeapTests.cpp Private/D3D12/DescriptorRingAllocatorTests.cpp Private/D3D12/DescriptorTests.cpp + Private/D3D12/FencedResourcePoolTests.cpp Private/D3D12/Shader/HLSLTests.cpp Private/D3D12/Shader/ReflectionTests.cpp Private/D3D12/Shader/ShaderBindingTests.cpp diff --git a/test/Private/D3D12/CommandQueueTests.cpp b/test/Private/D3D12/CommandQueueTests.cpp index 6a52ca3e..25337f32 100644 --- a/test/Private/D3D12/CommandQueueTests.cpp +++ b/test/Private/D3D12/CommandQueueTests.cpp @@ -106,6 +106,7 @@ TEST_CASE_PERSISTENT_FIXTURE( CommandQueueTestFixture, "Scenario: CommandQueueTe AND_WHEN("The future fence point is eventually signalled") { const auto actualFencePoint = copyQueue->Signal(); + const auto waitResult = directQueue->WaitOnFenceCPU(waitingFencePoint, Milliseconds{ 1u }); THEN("Future fence point has been reached") { @@ -114,6 +115,8 @@ TEST_CASE_PERSISTENT_FIXTURE( CommandQueueTestFixture, "Scenario: CommandQueueTe 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)); } } diff --git a/test/Private/D3D12/FencedResourcePoolTests.cpp b/test/Private/D3D12/FencedResourcePoolTests.cpp new file mode 100644 index 00000000..66ed8f50 --- /dev/null +++ b/test/Private/D3D12/FencedResourcePoolTests.cpp @@ -0,0 +1,257 @@ + +#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 + } + ); + + auto copyQueue = device->CreateCommandQueue( + D3D12_COMMAND_QUEUE_DESC + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY + } + ); + + 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") + { + 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") + { + 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 From acf368e76da72653667a0af36b83bbf4805bcd67 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 8 Dec 2025 14:59:07 +0000 Subject: [PATCH 10/76] Fix merge error and also ignoring expected value --- src/Private/D3D12/CommandQueue.cpp | 2 +- src/Private/D3D12/GPUDevice.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Private/D3D12/CommandQueue.cpp b/src/Private/D3D12/CommandQueue.cpp index 872eaf02..140c2d7c 100644 --- a/src/Private/D3D12/CommandQueue.cpp +++ b/src/Private/D3D12/CommandQueue.cpp @@ -55,7 +55,7 @@ namespace stf void CommandQueue::FlushQueue() { - WaitOnFenceCPU(Signal()); + ThrowIfUnexpected(WaitOnFenceCPU(Signal())); } void CommandQueue::ExecuteCommandList(CommandList& InList) diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 25118956..e9f65f81 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -208,7 +208,7 @@ 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 From ae11d15f5d70bdd8f30e49996b51f1df2b35f7b6 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 8 Dec 2025 15:53:49 +0000 Subject: [PATCH 11/76] Fixing issues found by clang 19. Not entirely sure why they were not found by clang 20... --- test/Private/D3D12/CommandQueueTests.cpp | 12 +++++++++--- test/Private/D3D12/FencedResourcePoolTests.cpp | 14 ++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/test/Private/D3D12/CommandQueueTests.cpp b/test/Private/D3D12/CommandQueueTests.cpp index 25337f32..ce5a6cf1 100644 --- a/test/Private/D3D12/CommandQueueTests.cpp +++ b/test/Private/D3D12/CommandQueueTests.cpp @@ -54,14 +54,20 @@ TEST_CASE_PERSISTENT_FIXTURE( CommandQueueTestFixture, "Scenario: CommandQueueTe auto directQueue = device->CreateCommandQueue( D3D12_COMMAND_QUEUE_DESC { - .Type = D3D12_COMMAND_LIST_TYPE_DIRECT + .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 + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 } ); @@ -105,7 +111,7 @@ TEST_CASE_PERSISTENT_FIXTURE( CommandQueueTestFixture, "Scenario: CommandQueueTe AND_WHEN("The future fence point is eventually signalled") { - const auto actualFencePoint = copyQueue->Signal(); + [[maybe_unused]] const auto actualFencePoint = copyQueue->Signal(); const auto waitResult = directQueue->WaitOnFenceCPU(waitingFencePoint, Milliseconds{ 1u }); THEN("Future fence point has been reached") diff --git a/test/Private/D3D12/FencedResourcePoolTests.cpp b/test/Private/D3D12/FencedResourcePoolTests.cpp index 66ed8f50..b9f9b638 100644 --- a/test/Private/D3D12/FencedResourcePoolTests.cpp +++ b/test/Private/D3D12/FencedResourcePoolTests.cpp @@ -60,14 +60,20 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourcePoolTestFixture, "Scenario: FencedRes auto directQueue = device->CreateCommandQueue( D3D12_COMMAND_QUEUE_DESC { - .Type = D3D12_COMMAND_LIST_TYPE_DIRECT + .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 + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 } ); @@ -157,7 +163,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourcePoolTestFixture, "Scenario: FencedRes AND_WHEN("GPU work has finished") { - const auto finishedCopyFence = copyQueue->Signal(); + [[maybe_unused]] const auto finishedCopyFence = copyQueue->Signal(); const auto nextDirectFence = directQueue->Signal(); const auto waitResult = directQueue->WaitOnFenceCPU(nextDirectFence, Milliseconds{ 1u }); @@ -219,7 +225,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourcePoolTestFixture, "Scenario: FencedRes AND_WHEN("queue finishes executing work") { - const auto firstActualFence = copyQueue->Signal(); + [[maybe_unused]] const auto firstActualFence = copyQueue->Signal(); const auto nextDirectFence = directQueue->Signal(); const auto waitResult = directQueue->WaitOnFenceCPU(nextDirectFence, Milliseconds{ 1u }); From 3be7b207c4016f8f45e85ba41b2f91bb260670ff Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 15 Dec 2025 22:29:18 +0000 Subject: [PATCH 12/76] Add equality operators for descriptor handles --- src/Public/D3D12/Descriptor.h | 9 ++++ test/Private/D3D12/DescriptorTests.cpp | 67 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) 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/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; From 4404e4582fbf65b83d22295184a4e46a3112fbac Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 18 Dec 2025 09:23:47 +0000 Subject: [PATCH 13/76] Implementing a lower level Descriptor manager that handles descriptor heaps and descriptors --- src/CMakeLists.txt | 2 + .../D3D12/BindlessFreeListAllocator.cpp | 11 + src/Private/D3D12/DescriptorManager.cpp | 176 +++++++++++ src/Public/D3D12/BindlessFreeListAllocator.h | 2 + src/Public/D3D12/DescriptorManager.h | 83 ++++++ test/CMakeLists.txt | 1 + test/Private/D3D12/DescriptorManagerTests.cpp | 276 ++++++++++++++++++ .../ShaderTestDescriptorManagerTests.cpp | 4 +- 8 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/Private/D3D12/DescriptorManager.cpp create mode 100644 src/Public/D3D12/DescriptorManager.h create mode 100644 test/Private/D3D12/DescriptorManagerTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2b04aaba..6f802e64 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ 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/FencedResourcePool.h @@ -88,6 +89,7 @@ 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 diff --git a/src/Private/D3D12/BindlessFreeListAllocator.cpp b/src/Private/D3D12/BindlessFreeListAllocator.cpp index 2b4d95a0..d705ecfa 100644 --- a/src/Private/D3D12/BindlessFreeListAllocator.cpp +++ b/src/Private/D3D12/BindlessFreeListAllocator.cpp @@ -126,4 +126,15 @@ namespace stf { return m_Index; } + + BindlessFreeListAllocator::Expected BindlessFreeListAllocator::IsAllocated(const BindlessIndex InIndex) const + { + const u32 index = InIndex; + if (index >= m_NumDescriptors) + { + return Unexpected(EErrorType::InvalidIndex); + } + + return !m_FreeSet[InIndex]; + } } \ No newline at end of file diff --git a/src/Private/D3D12/DescriptorManager.cpp b/src/Private/D3D12/DescriptorManager.cpp new file mode 100644 index 00000000..ff16f9bc --- /dev/null +++ b/src/Private/D3D12/DescriptorManager.cpp @@ -0,0 +1,176 @@ + +#include "D3D12/DescriptorManager.h" + +namespace stf +{ + DescriptorManager::Descriptor::Descriptor(DescriptorManager::Token, const SharedPtr& InOwner, BindlessFreeListAllocator::BindlessIndex InIndex) + : m_Owner(InOwner) + , m_Index(InIndex) + { + } + + DescriptorManager::Expected 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 }) + { + } + + DescriptorManager::Expected DescriptorManager::Acquire() + { + return m_Allocator.Allocate() + .transform( + [this](const BindlessFreeListAllocator::BindlessIndex InHandle) mutable + { + return Descriptor{ Token{}, SharedFromThis(), InHandle }; + }) + .transform_error( + [](const BindlessFreeListAllocator::EErrorType InError) + { + ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::EmptyError); + + return EErrorType::AllocatorFull; + }); + } + + DescriptorManager::Expected DescriptorManager::Release(const Descriptor& InDescriptor) + { + return + m_Allocator.Release(InDescriptor.GetIndex(Token{})) + .transform_error( + [](const BindlessFreeListAllocator::EErrorType InError) + { + ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); + return EErrorType::DescriptorAlreadyFree; + } + ); + } + + DescriptorManager::Expected> DescriptorManager::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 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); + } + ).transform_error( + [](const BindlessFreeListAllocator::EErrorType InError) + { + ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::ShrinkAttempted); + return EErrorType::AttemptedShrink; + } + ); + } + + 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); + } + + DescriptorManager::Expected DescriptorManager::ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const + { + return m_Allocator + .IsAllocated(InIndex) + .transform_error( + [](const BindlessFreeListAllocator::EErrorType InError) + { + ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::InvalidIndex); + + return EErrorType::DescriptorInvalid; + } + ) + .and_then( + [this, InIndex](const bool InIsAllocated) -> Expected + { + if (InIsAllocated) + { + const auto descriptorRange = m_CPUHeap->GetHeapRange(); + + return descriptorRange[InIndex.GetIndex()] + .transform_error( + [](const DescriptorRange::EErrorType InError) + { + ThrowIfFalse(InError == DescriptorRange::EErrorType::InvalidIndex); + + return EErrorType::DescriptorInvalid; + } + ); + } + else + { + return Unexpected{ EErrorType::DescriptorNotAllocated }; + } + } + ); + + } +} diff --git a/src/Public/D3D12/BindlessFreeListAllocator.h b/src/Public/D3D12/BindlessFreeListAllocator.h index abaf3be2..5cbb65ef 100644 --- a/src/Public/D3D12/BindlessFreeListAllocator.h +++ b/src/Public/D3D12/BindlessFreeListAllocator.h @@ -58,6 +58,8 @@ namespace stf u32 GetSize() const; u32 GetCapacity() const; + Expected IsAllocated(const BindlessIndex InIndex) const; + private: using EBufferError = RingBuffer::EErrorType; diff --git a/src/Public/D3D12/DescriptorManager.h b/src/Public/D3D12/DescriptorManager.h new file mode 100644 index 00000000..957f53ff --- /dev/null +++ b/src/Public/D3D12/DescriptorManager.h @@ -0,0 +1,83 @@ +#pragma once + +#include "Platform.h" + +#include "D3D12/BindlessFreeListAllocator.h" +#include "D3D12/DescriptorHeap.h" +#include "D3D12/CommandList.h" +#include "D3D12/GPUDevice.h" + +#include "Utility/Expected.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" + +namespace stf +{ + class DescriptorManager + : public Object + { + + struct Token {}; + + public: + + struct CreationParams + { + SharedPtr Device; + u32 InitialSize = 16u; + }; + + enum class EErrorType + { + Unknown, + AllocatorFull, + AttemptedShrink, + DescriptorAlreadyFree, + DescriptorNotAllocated, + DescriptorInvalid + }; + + template + using Expected = Expected; + + class Descriptor + { + public: + + Descriptor(Token, const SharedPtr& InOwner, BindlessFreeListAllocator::BindlessIndex InIndex); + + Expected 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); + + + Expected Acquire(); + Expected Release(const Descriptor& InDescriptor); + + Expected> Resize(const u32 InNewSize); + + u32 GetSize() const; + u32 GetCapacity() const; + + void SetDescriptorHeap(CommandList& InCommandList); + + Expected 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/test/CMakeLists.txt b/test/CMakeLists.txt index 59500ae7..46a38153 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -146,6 +146,7 @@ set(SOURCES 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/FencedResourcePoolTests.cpp diff --git a/test/Private/D3D12/DescriptorManagerTests.cpp b/test/Private/D3D12/DescriptorManagerTests.cpp new file mode 100644 index 00000000..0dd04ec6 --- /dev/null +++ b/test/Private/D3D12/DescriptorManagerTests.cpp @@ -0,0 +1,276 @@ + +#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(secondReleaseResult.error() == DescriptorManager::EErrorType::DescriptorAlreadyFree); + 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() == DescriptorManager::EErrorType::AllocatorFull); + 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 + }); + using ResultType = decltype(manager->Acquire()); + + const auto [descriptors, resolvedDescriptors] = + [&, this](const u32 InNum) + { + std::vector descriptors; + descriptors.reserve(InNum); + + std::vector resolvedDescriptors; + resolvedDescriptors.reserve(InNum); + + for (u32 i = 0; i < InNum; ++i) + { + auto maybeDescriptor = manager->Acquire(); + REQUIRE(maybeDescriptor.has_value()); + auto descriptor = maybeDescriptor.value(); + + auto maybeResolved = descriptor.Resolve(); + REQUIRE(maybeResolved.has_value()); + + const auto resolvedDescriptor = maybeResolved.value(); + resolvedDescriptors.push_back(resolvedDescriptor); + descriptors.push_back(descriptor); + } + + return Tuple{ std::move(descriptors), std::move(resolvedDescriptors) }; + }(initialSize); + + REQUIRE(initialSize == manager->GetCapacity()); + REQUIRE(initialSize == manager->GetSize()); + + THEN("All descriptors are unique") + { + for (i32 i = 0; i < (initialSize -1); ++i) + { + for (i32 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() == DescriptorManager::EErrorType::AllocatorFull); + 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/Framework/ShaderTestDescriptorManagerTests.cpp b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp index 8cc29330..d67c155a 100644 --- a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp +++ b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp @@ -7,7 +7,7 @@ #include #include -namespace DescriptorManagerTestPrivate +namespace ShaderTestDescriptorManagerTestPrivate { class Fixture { @@ -48,7 +48,7 @@ namespace DescriptorManagerTestPrivate }; } -TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor Manager Tests") +TEST_CASE_PERSISTENT_FIXTURE(ShaderTestDescriptorManagerTestPrivate::Fixture, "Shader Test Descriptor Manager Tests") { using namespace stf; GIVEN("An initial size of 1") From 62bce7807558bcd75fb6a9c0da23c45fcaa642ea Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 18 Dec 2025 17:53:25 +0000 Subject: [PATCH 14/76] Fixing for Clang 19. More issues with tuples implemented as an aggregate. Very annoying --- test/Private/D3D12/DescriptorManagerTests.cpp | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test/Private/D3D12/DescriptorManagerTests.cpp b/test/Private/D3D12/DescriptorManagerTests.cpp index 0dd04ec6..00840c21 100644 --- a/test/Private/D3D12/DescriptorManagerTests.cpp +++ b/test/Private/D3D12/DescriptorManagerTests.cpp @@ -132,42 +132,50 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor .Device = device, .InitialSize = initialSize }); - using ResultType = decltype(manager->Acquire()); - const auto [descriptors, resolvedDescriptors] = - [&, this](const u32 InNum) + const auto descriptors = + [&](const u32 InNum) { std::vector descriptors; descriptors.reserve(InNum); - std::vector resolvedDescriptors; - resolvedDescriptors.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()); - const auto resolvedDescriptor = maybeResolved.value(); - resolvedDescriptors.push_back(resolvedDescriptor); - descriptors.push_back(descriptor); + resolvedDescriptors.push_back(maybeResolved.value()); } - return Tuple{ std::move(descriptors), std::move(resolvedDescriptors) }; - }(initialSize); + return resolvedDescriptors; + }(); REQUIRE(initialSize == manager->GetCapacity()); REQUIRE(initialSize == manager->GetSize()); THEN("All descriptors are unique") { - for (i32 i = 0; i < (initialSize -1); ++i) + for (u32 i = 0; i < (initialSize -1u); ++i) { - for (i32 j = i + 1; j < initialSize; ++j) + for (u32 j = i + 1; j < initialSize; ++j) { REQUIRE(resolvedDescriptors[i] != resolvedDescriptors[j]); } From db7ed0a73e498b2802a74d222018989af95022a6 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 20 Dec 2025 14:53:13 +0000 Subject: [PATCH 15/76] Added concept for valdiating bitfields --- src/Public/Utility/Concepts.h | 6 ++++++ test/Private/Utility/ConceptsTests.cpp | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index 5e6496df..1ee0a36c 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -142,4 +142,10 @@ namespace stf (alignof(T) == 4 || alignof(T) == 2 || alignof(T) == 8) && Formattable; + + template + concept CValidBitField = + std::integral && + (sizeof(BackingType) * 8) == (Bits + ...); + } diff --git a/test/Private/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 6decbaed..5c722f34 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include namespace CallableTypeTests { @@ -415,4 +417,23 @@ 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); } \ No newline at end of file From bb67d947fc65a0c1ee95a222a2ffb346ec373106 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 20 Dec 2025 15:06:46 +0000 Subject: [PATCH 16/76] Not allowing signed integrals for bitfields --- src/Public/Utility/Concepts.h | 2 +- test/Private/Utility/ConceptsTests.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index 1ee0a36c..8ab03f36 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -145,7 +145,7 @@ namespace stf template concept CValidBitField = - std::integral && + std::unsigned_integral && (sizeof(BackingType) * 8) == (Bits + ...); } diff --git a/test/Private/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 5c722f34..4ea330bd 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -436,4 +436,5 @@ namespace ValidBitFieldTests static_assert(!CValidBitField); static_assert(!CValidBitField); + static_assert(!CValidBitField); } \ No newline at end of file From cfa8fcbcf7f8236ce283d8be29e21ba7aff10828 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 20 Dec 2025 19:12:33 +0000 Subject: [PATCH 17/76] Strengthen valid bitfield concept to fail if a bit field has 0 bits --- src/Public/Utility/Concepts.h | 3 ++- test/Private/Utility/ConceptsTests.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index 8ab03f36..654175a3 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -146,6 +146,7 @@ namespace stf template concept CValidBitField = std::unsigned_integral && - (sizeof(BackingType) * 8) == (Bits + ...); + (sizeof(BackingType) * 8) == (Bits + ...) && + ((Bits != 0) && ...); } diff --git a/test/Private/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 4ea330bd..77a1fe8d 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -434,6 +434,7 @@ namespace ValidBitFieldTests static_assert(!CValidBitField); static_assert(!CValidBitField); static_assert(!CValidBitField); + static_assert(!CValidBitField); static_assert(!CValidBitField); static_assert(!CValidBitField); From 7cc2ceb44142a32af02eb0cfe7a3e0708dda29cd Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 20 Dec 2025 19:23:23 +0000 Subject: [PATCH 18/76] Adding VersionedIndex class --- src/CMakeLists.txt | 1 + src/Public/Utility/VersionedIndex.h | 60 +++++++++++ test/CMakeLists.txt | 1 + test/Private/Utility/VersionedIndexTests.cpp | 100 +++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 src/Public/Utility/VersionedIndex.h create mode 100644 test/Private/Utility/VersionedIndexTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f802e64..7087d860 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,6 +82,7 @@ set(SOURCES 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 diff --git a/src/Public/Utility/VersionedIndex.h b/src/Public/Utility/VersionedIndex.h new file mode 100644 index 00000000..0348615c --- /dev/null +++ b/src/Public/Utility/VersionedIndex.h @@ -0,0 +1,60 @@ +#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 + 1 }; + } + + 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{}; + }; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 46a38153..5548f97c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -217,6 +217,7 @@ set(SOURCES 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/Noisy.h diff --git a/test/Private/Utility/VersionedIndexTests.cpp b/test/Private/Utility/VersionedIndexTests.cpp new file mode 100644 index 00000000..698ef358 --- /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 (i32 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 From c1b762bffaeec4bdc82c38794005e654de258993 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 22 Dec 2025 13:47:25 +0000 Subject: [PATCH 19/76] Fixing versionedindex compilation errors. Also adding common versionedindex alias --- src/Public/Utility/VersionedIndex.h | 2 ++ test/Private/Utility/VersionedIndexTests.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Public/Utility/VersionedIndex.h b/src/Public/Utility/VersionedIndex.h index 0348615c..6f852f72 100644 --- a/src/Public/Utility/VersionedIndex.h +++ b/src/Public/Utility/VersionedIndex.h @@ -57,4 +57,6 @@ namespace stf BackingType m_Index : NumIndexBits{}; BackingType m_Version : NumVersionBits{}; }; + + using u32VersionedIndex = VersionedIndex; } \ No newline at end of file diff --git a/test/Private/Utility/VersionedIndexTests.cpp b/test/Private/Utility/VersionedIndexTests.cpp index 698ef358..b77a099f 100644 --- a/test/Private/Utility/VersionedIndexTests.cpp +++ b/test/Private/Utility/VersionedIndexTests.cpp @@ -65,7 +65,7 @@ SCENARIO("VersionedIndexTests - Valid Constructions") [&]() { auto ret = handle; - for (i32 i = 0; i <= u32Handle::MaxVersion; ++i) + for (u32 i = 0; i <= u32Handle::MaxVersion; ++i) { ret = ret.Next(); } From f7523c42214468933e4239c999747fef9d302d93 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 22 Dec 2025 13:57:54 +0000 Subject: [PATCH 20/76] Actually fix compilation errors in VersionedIndex --- src/Public/Utility/VersionedIndex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Public/Utility/VersionedIndex.h b/src/Public/Utility/VersionedIndex.h index 6f852f72..c0fb3719 100644 --- a/src/Public/Utility/VersionedIndex.h +++ b/src/Public/Utility/VersionedIndex.h @@ -29,7 +29,7 @@ namespace stf VersionedIndex Next() const { - return VersionedIndex{ m_Index, m_Version + 1 }; + return VersionedIndex{ m_Index, m_Version + 1u }; } BackingType GetIndex() const From 52bb9635e96cf75f6399c3aa7e81232a9db4de1e Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 23 Dec 2025 16:00:30 +0000 Subject: [PATCH 21/76] String literal class for guaranteeing a string literal --- src/CMakeLists.txt | 1 + src/Public/Utility/StringLiteral.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/Public/Utility/StringLiteral.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7087d860..15d659d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,7 @@ set(SOURCES 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 diff --git a/src/Public/Utility/StringLiteral.h b/src/Public/Utility/StringLiteral.h new file mode 100644 index 00000000..33bfe843 --- /dev/null +++ b/src/Public/Utility/StringLiteral.h @@ -0,0 +1,30 @@ + +#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; + } + + private: + std::string_view m_View{}; + }; +} \ No newline at end of file From 06a9073faf84dfff6088cf89f5f4ceae8fd0d6d7 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 23 Dec 2025 16:43:05 +0000 Subject: [PATCH 22/76] Adding comparison operators to string literal --- src/Public/Utility/StringLiteral.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Public/Utility/StringLiteral.h b/src/Public/Utility/StringLiteral.h index 33bfe843..9a416408 100644 --- a/src/Public/Utility/StringLiteral.h +++ b/src/Public/Utility/StringLiteral.h @@ -24,6 +24,9 @@ namespace stf return m_View; } + friend bool operator==(const StringLiteral&, const StringLiteral&) = default; + friend bool operator!=(const StringLiteral&, const StringLiteral&) = default; + private: std::string_view m_View{}; }; From 6449b7b6ef2c3182c491f4c3299248446736c800 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 27 Dec 2025 15:13:25 +0000 Subject: [PATCH 23/76] Make string literal comparisons constexpr --- src/Public/Utility/StringLiteral.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Public/Utility/StringLiteral.h b/src/Public/Utility/StringLiteral.h index 9a416408..51ecf593 100644 --- a/src/Public/Utility/StringLiteral.h +++ b/src/Public/Utility/StringLiteral.h @@ -24,8 +24,8 @@ namespace stf return m_View; } - friend bool operator==(const StringLiteral&, const StringLiteral&) = default; - friend bool operator!=(const StringLiteral&, const StringLiteral&) = default; + constexpr friend bool operator==(const StringLiteral&, const StringLiteral&) = default; + constexpr friend bool operator!=(const StringLiteral&, const StringLiteral&) = default; private: std::string_view m_View{}; From d5eaab03d04c4149548c9a32e5156bb61447796a Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 27 Dec 2025 15:13:53 +0000 Subject: [PATCH 24/76] Create Trivially copyable concept --- src/Public/Utility/Concepts.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index 654175a3..26726b8f 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -38,6 +38,9 @@ namespace stf template concept MoveConstructibleType = std::is_move_constructible_v; + template + concept TriviallyCopyableType = std::is_trivially_copyable_v; + template concept PureFunctionType = std::is_function_v>>; From 3dd6ee43f7b326345504c57ade905210e359a503 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 27 Dec 2025 15:14:30 +0000 Subject: [PATCH 25/76] Adding function to get a string view from a fixed string --- src/Public/Utility/FixedString.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Public/Utility/FixedString.h b/src/Public/Utility/FixedString.h index 595685aa..b30461e1 100644 --- a/src/Public/Utility/FixedString.h +++ b/src/Public/Utility/FixedString.h @@ -2,6 +2,7 @@ #include "Platform.h" #include +#include namespace stf { @@ -15,6 +16,11 @@ namespace stf { std::copy(std::cbegin(InString), std::cend(InString), std::begin(Data)); } + + constexpr std::string_view View() const + { + return std::string_view{ Data }; + } }; template From 4a0eb4fdc14f7f1217e05ec2dbb7ca6163f86db9 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 27 Dec 2025 17:32:10 +0000 Subject: [PATCH 26/76] New error type and tests --- src/CMakeLists.txt | 1 + src/Public/Utility/Error.h | 152 ++++++++++++++++++ test/CMakeLists.txt | 1 + test/Private/Utility/ErrorTests.cpp | 235 ++++++++++++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 src/Public/Utility/Error.h create mode 100644 test/Private/Utility/ErrorTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 15d659d9..df3ffba2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,7 @@ 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 diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h new file mode 100644 index 00000000..8ec435be --- /dev/null +++ b/src/Public/Utility/Error.h @@ -0,0 +1,152 @@ + +#pragma once + +#include "Platform.h" + +#include "Utility/Concepts.h" +#include "Utility/FixedString.h" +#include "Utility/StringLiteral.h" + +#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 (std::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; + + 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); + }); + } + + 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; + } + + private: + + std::vector m_Fragments{}; + }; +} + +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()); + } +}; \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5548f97c..fea24ff6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -210,6 +210,7 @@ set(SOURCES Private/Utility/AlignedOffsetTests.cpp Private/Utility/ConceptsTests.cpp Private/Utility/EnumReflectionTests.cpp + Private/Utility/ErrorTests.cpp Private/Utility/FunctionTraitsTests.cpp Private/Utility/LambdaTests.cpp Private/Utility/ObjectTests.cpp diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp new file mode 100644 index 00000000..4ee4bde3 --- /dev/null +++ b/test/Private/Utility/ErrorTests.cpp @@ -0,0 +1,235 @@ + +#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 +{ + static_assert(Formattable); +} + +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(error.HasFragmentWithFormat(errorFrag1.Format())); + + REQUIRE_THAT(std::format("{}", error), 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(error.HasFragmentWithFormat(errorFrag1.Format())); + REQUIRE(error.HasFragmentWithFormat(errorFrag2.Format())); + + const auto errorMessage = std::format("{}", error); + 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()); + } + } + } + } +} \ No newline at end of file From 7edd9d56292d0af77d2fe2012197c6f5d202b77f Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 27 Dec 2025 17:45:15 +0000 Subject: [PATCH 27/76] Use Concept rather than std concept for formattable Make a type alias for Expected --- src/Public/Utility/Error.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index 8ec435be..05e3c034 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -4,6 +4,7 @@ #include "Platform.h" #include "Utility/Concepts.h" +#include "Utility/Expected.h" #include "Utility/FixedString.h" #include "Utility/StringLiteral.h" @@ -44,7 +45,7 @@ namespace stf } template - requires (std::formattable && ...) + requires (Formattable && ...) static ErrorFragment Make(ArgTypes&&... InArgs) { return ErrorFragment{ @@ -135,6 +136,9 @@ namespace stf std::vector m_Fragments{}; }; + + template + using ExpectedError = Expected; } template<> From 4950e42735c830b30f45f512b451fd5c6fdb8f40 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 29 Dec 2025 11:06:48 +0000 Subject: [PATCH 28/76] Support stream operator for Error --- src/Public/Utility/Error.h | 19 ++++++++++++++++++- test/Private/Utility/ErrorTests.cpp | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index 05e3c034..9e4282b3 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,11 @@ namespace stf Error() = default; + explicit Error(ErrorFragment&& InFragment) + { + Append(std::move(InFragment)); + } + void Append(ErrorFragment&& InFragment) { m_Fragments.push_back(std::move(InFragment)); @@ -132,6 +138,8 @@ namespace stf return *this; } + friend std::ostream& operator<<(std::ostream& InOutStream, const Error& InError); + private: std::vector m_Fragments{}; @@ -153,4 +161,13 @@ struct std::formatter : std::formatter { auto format(const stf::Error& In, auto& ctx) const { return In.FormatTo(ctx.out()); } -}; \ No newline at end of file +}; + +namespace stf +{ + inline std::ostream& operator<<(std::ostream& InOutStream, const Error& InError) + { + std::print(InOutStream, "{}", InError); + return InOutStream; + } +} \ No newline at end of file diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp index 4ee4bde3..622e7ee2 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -206,7 +207,17 @@ SCENARIO("Error Tests") { REQUIRE(error.HasFragmentWithFormat(errorFrag1.Format())); - REQUIRE_THAT(std::format("{}", error), ContainsSubstring(std::format("{}", errorFrag1.Error()))); + 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") @@ -221,6 +232,16 @@ SCENARIO("Error Tests") REQUIRE(error.HasFragmentWithFormat(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()); From 53c769d5f550213e432a1e39e2513739c06f652d Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 29 Dec 2025 12:21:17 +0000 Subject: [PATCH 29/76] Adding ostream for Expected with tests --- src/Public/Utility/Concepts.h | 6 ++ src/Public/Utility/Expected.h | 33 +++++++++ test/CMakeLists.txt | 1 + test/Private/Utility/ConceptsTests.cpp | 17 +++++ test/Private/Utility/ErrorTests.cpp | 1 - test/Private/Utility/ExpectedTests.cpp | 97 ++++++++++++++++++++++++++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 test/Private/Utility/ExpectedTests.cpp diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index 26726b8f..c988b0dc 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -152,4 +152,10 @@ namespace stf (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/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/test/CMakeLists.txt b/test/CMakeLists.txt index fea24ff6..8d83d353 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -211,6 +211,7 @@ set(SOURCES Private/Utility/ConceptsTests.cpp Private/Utility/EnumReflectionTests.cpp Private/Utility/ErrorTests.cpp + Private/Utility/ExpectedTests.cpp Private/Utility/FunctionTraitsTests.cpp Private/Utility/LambdaTests.cpp Private/Utility/ObjectTests.cpp diff --git a/test/Private/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 77a1fe8d..04825079 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -438,4 +438,21 @@ namespace ValidBitFieldTests 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 index 622e7ee2..2c010c74 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include 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 From e544d0c11289875d2cbeaa26b16623c940b44245 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 29 Dec 2025 19:56:50 +0000 Subject: [PATCH 30/76] New Resource manager that utilizes a free list. With tests --- src/CMakeLists.txt | 1 + src/Public/D3D12/FencedResourceFreeList.h | 186 ++++++++++++++++ test/CMakeLists.txt | 1 + .../D3D12/FencedResourceFreeListTests.cpp | 206 ++++++++++++++++++ 4 files changed, 394 insertions(+) create mode 100644 src/Public/D3D12/FencedResourceFreeList.h create mode 100644 test/Private/D3D12/FencedResourceFreeListTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index df3ffba2..fdc663ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set(SOURCES 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 diff --git a/src/Public/D3D12/FencedResourceFreeList.h b/src/Public/D3D12/FencedResourceFreeList.h new file mode 100644 index 00000000..d6fcb80b --- /dev/null +++ b/src/Public/D3D12/FencedResourceFreeList.h @@ -0,0 +1,186 @@ + +#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() {} + }; + + template + class FencedResourceFreeList + { + public: + + struct CreationParams + { + std::function()> CreateFunc; + 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_CreateFunc(InParams.CreateFunc) + , m_Queue(InParams.Queue) + { + } + + [[nodiscard]] Handle Acquire() + { + TickDeferredReleases(); + if (m_FreeList.empty()) + { + const u32VersionedIndex resourceIndex{ static_cast(m_Resources.size()) }; + + m_Resources.emplace_back(m_CreateFunc(), resourceIndex.GetVersion()); + m_DeferredResources.emplace_back(false); + + return Handle{ FencedResourceFreeListToken {}, resourceIndex }; + } + + return Handle{ FencedResourceFreeListToken{}, ThrowIfUnexpected(m_FreeList.pop_front()) }; + } + + ExpectedError Release(const Handle InHandle) + { + return InternalValidateHandle(InHandle) + .and_then( + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError + { + const auto index = InVersionedIndex.GetIndex(); + + m_DeferredResources[index] = true; + m_DeferredReleasedHandles.push_back( + FencedResource + { + .VersionedIndex = InVersionedIndex.Next(), + .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.get(); + } + ); + } + + 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{ErrorFragment::Make<"Handle index ({}) out of range">(index) } }; + } + + if (m_Resources[index].Version != version) + { + return Unexpected{ Error{ErrorFragment::Make<"Handle version mismatch (Expected: {}, Actual Handle: {}). Likely a stale resource handle">(m_Resources[index].Version, version) } }; + } + + if (m_DeferredResources[index]) + { + return Unexpected{ Error{ErrorFragment::Make<"Handle index ({}) has already been released">(index) } }; + } + + 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; + const u32 index = versionedIndex.GetIndex(); + + m_Resources[index].Version = versionedIndex.GetVersion(); + m_DeferredResources[index] = false; + m_FreeList.push_back(versionedIndex); + } + } + + struct FencedResource + { + u32VersionedIndex VersionedIndex{}; + Fence::FencePoint FencePoint; + }; + + struct VersionedResource + { + SharedPtr Resource; + u32 Version{}; + }; + + std::vector m_Resources; + std::vector m_DeferredResources; + RingBuffer m_DeferredReleasedHandles; + RingBuffer m_FreeList; + + + std::function()> m_CreateFunc; + SharedPtr m_Queue; + }; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d83d353..4756e390 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -149,6 +149,7 @@ set(SOURCES 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 diff --git a/test/Private/D3D12/FencedResourceFreeListTests.cpp b/test/Private/D3D12/FencedResourceFreeListTests.cpp new file mode 100644 index 00000000..250582eb --- /dev/null +++ b/test/Private/D3D12/FencedResourceFreeListTests.cpp @@ -0,0 +1,206 @@ + +#include +#include +#include +#include +#include + +#include "Utility/EnumReflection.h" +#include "Utility/Object.h" + +#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 = + [](FreeListType& InFreeList, const FreeListType::Handle InHandle) + { + const auto ret = InFreeList.Get(InHandle); + REQUIRE(ret); + return ret.value().get(); + }; + + 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 + { + .CreateFunc = [id = 0]() mutable + { + return MakeShared(id++); + }, + .Queue = directQueue + } }; + + WHEN("Resource requested") + { + const auto firstHandle = freeList.Acquire(); + + REQUIRE(freeList.ValidateHandle(firstHandle)); + const auto firstResource = getResource(freeList, firstHandle); + + AND_WHEN("resource is immediately released with no GPU work") + { + const auto releaseResult = freeList.Release(firstHandle); + REQUIRE(releaseResult); + + THEN("handle is no longer valid") + { + REQUIRE_FALSE(freeList.ValidateHandle(firstHandle)); + REQUIRE_FALSE(freeList.Get(firstHandle)); + } + + AND_WHEN("resource is acquired again") + { + const auto secondHandle = freeList.Acquire(); + REQUIRE(freeList.ValidateHandle(secondHandle)); + const auto secondResource = getResource(freeList, secondHandle); + + THEN("second handle is to the same resource as the first") + { + REQUIRE(firstResource == secondResource); + } + } + } + + 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 secondHandle = freeList.Acquire(); + REQUIRE(freeList.ValidateHandle(secondHandle)); + const auto secondResource = getResource(freeList, secondHandle); + + THEN("second handle is to a different resource from the first") + { + REQUIRE(firstResource != secondResource); + } + + AND_WHEN("yet another resource requested") + { + const auto thirdHandle = freeList.Acquire(); + REQUIRE(freeList.ValidateHandle(thirdHandle)); + const auto thirdResource = getResource(freeList, thirdHandle); + + 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 secondHandle = freeList.Acquire(); + REQUIRE(freeList.ValidateHandle(secondHandle)); + const auto secondResource = getResource(freeList, secondHandle); + + THEN("second handle is to the same resource as 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 From 74d8b1d85dfdfc5bdba91f2f8874672fcfba5ce3 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 29 Dec 2025 23:19:03 +0000 Subject: [PATCH 31/76] Modify resource free list to not create resources. This removes the restriction that all resources in a free list must be created in the same way. Meaning that on free list can manage all resources which is nice --- src/Public/D3D12/FencedResourceFreeList.h | 41 ++++++++++--------- .../D3D12/FencedResourceFreeListTests.cpp | 24 ++++++----- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/Public/D3D12/FencedResourceFreeList.h b/src/Public/D3D12/FencedResourceFreeList.h index d6fcb80b..b4c5f6a3 100644 --- a/src/Public/D3D12/FencedResourceFreeList.h +++ b/src/Public/D3D12/FencedResourceFreeList.h @@ -35,7 +35,6 @@ namespace stf struct CreationParams { - std::function()> CreateFunc; SharedPtr Queue; }; @@ -62,25 +61,31 @@ namespace stf }; FencedResourceFreeList(const CreationParams& InParams) - : m_CreateFunc(InParams.CreateFunc) - , m_Queue(InParams.Queue) + : m_Queue(InParams.Queue) { } - [[nodiscard]] Handle Acquire() + [[nodiscard]] Handle Manage(T&& InResource) { TickDeferredReleases(); - if (m_FreeList.empty()) - { - const u32VersionedIndex resourceIndex{ static_cast(m_Resources.size()) }; - - m_Resources.emplace_back(m_CreateFunc(), resourceIndex.GetVersion()); - m_DeferredResources.emplace_back(false); - return Handle{ FencedResourceFreeListToken {}, resourceIndex }; - } + const auto resourceIndex = + [&]() + { + if (m_FreeList.empty()) + { + return u32VersionedIndex{ static_cast(m_Resources.size()) }; + } + else + { + return ThrowIfUnexpected(m_FreeList.pop_front()); + } + }(); - return Handle{ FencedResourceFreeListToken{}, ThrowIfUnexpected(m_FreeList.pop_front()) }; + m_Resources.emplace_back(std::move(InResource), resourceIndex.GetVersion()); + m_DeferredResources.emplace_back(false); + + return Handle{ FencedResourceFreeListToken{}, resourceIndex }; } ExpectedError Release(const Handle InHandle) @@ -109,15 +114,15 @@ namespace stf return InternalValidateHandle(InHandle).transform([](const auto&) {}); } - ExpectedError> Get(const Handle InHandle) const + ExpectedError Get(const Handle InHandle) const { return InternalValidateHandle(InHandle) .and_then( - [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError> + [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError { const auto index = InVersionedIndex.GetIndex(); - return *m_Resources[index].Resource.get(); + return m_Resources[index].Resource; } ); } @@ -170,7 +175,7 @@ namespace stf struct VersionedResource { - SharedPtr Resource; + T Resource; u32 Version{}; }; @@ -179,8 +184,6 @@ namespace stf RingBuffer m_DeferredReleasedHandles; RingBuffer m_FreeList; - - std::function()> m_CreateFunc; SharedPtr m_Queue; }; } \ No newline at end of file diff --git a/test/Private/D3D12/FencedResourceFreeListTests.cpp b/test/Private/D3D12/FencedResourceFreeListTests.cpp index 250582eb..18889616 100644 --- a/test/Private/D3D12/FencedResourceFreeListTests.cpp +++ b/test/Private/D3D12/FencedResourceFreeListTests.cpp @@ -49,11 +49,17 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence ); auto getResource = - [](FreeListType& InFreeList, const FreeListType::Handle InHandle) + [](const FreeListType& InFreeList, const FreeListType::Handle InHandle) { const auto ret = InFreeList.Get(InHandle); REQUIRE(ret); - return ret.value().get(); + return ret.value(); + }; + + auto resourceGenerator = + [id = 0]() mutable + { + return id++; }; GIVEN("DeviceType: " << Enum::UnscopedName(deviceType)) @@ -94,16 +100,12 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence FreeListType freeList{ FreeListType::CreationParams { - .CreateFunc = [id = 0]() mutable - { - return MakeShared(id++); - }, .Queue = directQueue } }; WHEN("Resource requested") { - const auto firstHandle = freeList.Acquire(); + const auto firstHandle = freeList.Manage(resourceGenerator()); REQUIRE(freeList.ValidateHandle(firstHandle)); const auto firstResource = getResource(freeList, firstHandle); @@ -121,7 +123,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is acquired again") { - const auto secondHandle = freeList.Acquire(); + const auto secondHandle = freeList.Manage(resourceGenerator()); REQUIRE(freeList.ValidateHandle(secondHandle)); const auto secondResource = getResource(freeList, secondHandle); @@ -146,7 +148,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is requested again") { - const auto secondHandle = freeList.Acquire(); + const auto secondHandle = freeList.Manage(resourceGenerator()); REQUIRE(freeList.ValidateHandle(secondHandle)); const auto secondResource = getResource(freeList, secondHandle); @@ -157,7 +159,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("yet another resource requested") { - const auto thirdHandle = freeList.Acquire(); + const auto thirdHandle = freeList.Manage(resourceGenerator()); REQUIRE(freeList.ValidateHandle(thirdHandle)); const auto thirdResource = getResource(freeList, thirdHandle); @@ -180,7 +182,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is requested again") { - const auto secondHandle = freeList.Acquire(); + const auto secondHandle = freeList.Manage(resourceGenerator()); REQUIRE(freeList.ValidateHandle(secondHandle)); const auto secondResource = getResource(freeList, secondHandle); From 3cba612b83b2157747272cab948a11359514bbe6 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 29 Dec 2025 23:42:19 +0000 Subject: [PATCH 32/76] Make fences easier to debug --- src/Private/D3D12/Fence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Private/D3D12/Fence.cpp b/src/Private/D3D12/Fence.cpp index b5e042f0..9d3ce6e4 100644 --- a/src/Private/D3D12/Fence.cpp +++ b/src/Private/D3D12/Fence.cpp @@ -42,7 +42,8 @@ namespace stf .transform( [&, this]() { - return m_Fence->GetCompletedValue() >= InFencePoint.SignalledValue; + const auto fenceValue = m_Fence->GetCompletedValue(); + return fenceValue >= InFencePoint.SignalledValue; } ); } From 50fad4f2536ea2996dad5f8e8b09dd4b3c075a55 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 10:59:43 +0000 Subject: [PATCH 33/76] Using a desc to specify the params of committed resources to make it easier to specify commited resources --- src/Private/D3D12/GPUDevice.cpp | 20 +++---- src/Private/Framework/ShaderTestDriver.cpp | 9 +++- src/Public/D3D12/GPUDevice.h | 53 ++++++++++++++++--- .../ShaderTestDescriptorManagerTests.cpp | 9 ++-- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index e9f65f81..65b9466d 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -211,24 +211,24 @@ namespace stf 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 Object::New(GPUResource::CreationParams{ std::move(raw), InClearValue ? std::optional{*InClearValue} : std::nullopt, {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InInitialLayout} }); + SetName(raw.Get(), InDesc.Name); + return Object::New(GPUResource::CreationParams{ std::move(raw), InDesc.ClearValue, {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InDesc.BarrierLayout} }); } SharedPtr GPUDevice::CreateDescriptorHeap(const D3D12_DESCRIPTOR_HEAP_DESC& InDesc, const std::string_view InName) const diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index 8c9a3fc6..6b50fab1 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -47,7 +47,12 @@ namespace stf { const auto heapProps = CD3DX12_HEAP_PROPERTIES(InType); - return m_Device->CreateCommittedResource(heapProps, D3D12_HEAP_FLAG_NONE, InDesc, D3D12_BARRIER_LAYOUT_UNDEFINED); + return m_Device->CreateCommittedResource( + GPUDevice::CommittedResourceDesc + { + .HeapProps = CD3DX12_HEAP_PROPERTIES(InType), + .ResourceDesc = InDesc + }); } ShaderTestUAV ShaderTestDriver::CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) @@ -138,7 +143,7 @@ namespace stf InContext->SetBufferUAV(*assertBuffer); InContext->SetBufferUAV(*allocationBuffer); - InTestDesc.Shader.SetConstantBufferData(*InContext); + InTestDesc.Shader.SetConstantBufferData(InContext); } ); diff --git a/src/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index 5d718d0b..9d3f22cb 100644 --- a/src/Public/D3D12/GPUDevice.h +++ b/src/Public/D3D12/GPUDevice.h @@ -133,6 +133,51 @@ namespace stf bool EnableGPUCapture = false; }; + 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_view Name = "DefaultResource"; + }; + GPUDevice(ObjectToken, const CreationParams InDesc); ~GPUDevice(); @@ -144,13 +189,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; diff --git a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp index d67c155a..d9d4bc8d 100644 --- a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp +++ b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp @@ -19,10 +19,11 @@ namespace ShaderTestDescriptorManagerTestPrivate { })) , 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)) + stf::GPUDevice::CommittedResourceDesc + { + .HeapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), + .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(1024, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) + })) , uavDesc( D3D12_UNORDERED_ACCESS_VIEW_DESC { From 92f2463a868634f8a8319c5ab70242c4249d5fcf Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 11:00:14 +0000 Subject: [PATCH 34/76] Fix compilation error in test driver --- src/Private/Framework/ShaderTestDriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index 6b50fab1..518abe54 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -143,7 +143,7 @@ namespace stf InContext->SetBufferUAV(*assertBuffer); InContext->SetBufferUAV(*allocationBuffer); - InTestDesc.Shader.SetConstantBufferData(InContext); + InTestDesc.Shader.SetConstantBufferData(*InContext); } ); From 6d63e53d66918df23f69aecc3fceb0ebb1e32129 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 11:10:09 +0000 Subject: [PATCH 35/76] Removing unused variable --- src/Private/Framework/ShaderTestDriver.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index 518abe54..bb2537b6 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -45,8 +45,6 @@ namespace stf SharedPtr ShaderTestDriver::CreateBuffer(const D3D12_HEAP_TYPE InType, const D3D12_RESOURCE_DESC1& InDesc) { - const auto heapProps = CD3DX12_HEAP_PROPERTIES(InType); - return m_Device->CreateCommittedResource( GPUDevice::CommittedResourceDesc { From 7be3f214f6f1f228ef01c4ab32a3719e75ac2190 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 11:29:21 +0000 Subject: [PATCH 36/76] Supporting creating cbvs in the device --- src/Private/D3D12/GPUDevice.cpp | 11 +++++++++++ src/Public/D3D12/GPUDevice.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 65b9466d..1325a5ed 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -289,6 +291,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()); diff --git a/src/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index 9d3f22cb..21602578 100644 --- a/src/Public/D3D12/GPUDevice.h +++ b/src/Public/D3D12/GPUDevice.h @@ -215,6 +215,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; From 95525cebb3e415ab3bcfe18341d7116fb41adc03 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 12:55:12 +0000 Subject: [PATCH 37/76] GPU Resource manager. No tests yet and largely unimplemented. --- src/CMakeLists.txt | 2 + src/Private/D3D12/GPUResourceManager.cpp | 119 +++++++++++++++++++++++ src/Public/D3D12/GPUResourceManager.h | 103 ++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 src/Private/D3D12/GPUResourceManager.cpp create mode 100644 src/Public/D3D12/GPUResourceManager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fdc663ed..8fcfeeb2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ set(SOURCES 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 @@ -98,6 +99,7 @@ set(SOURCES 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 diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp new file mode 100644 index 00000000..d773b9dc --- /dev/null +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -0,0 +1,119 @@ + +#include "D3D12/GPUResourceManager.h" + +#include "Utility/Math.h" + +#include + +namespace stf +{ + 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::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 }; + } + + GPUResourceManager::ConstantBufferViewHandle GPUResourceManager::CreateCBV(const ConstantBufferHandle InBufferHandle) + { + auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() + .or_else( + [&](const DescriptorManager::EErrorType InErrorType) -> DescriptorManager::Expected + { + switch (InErrorType) + { + case DescriptorManager::EErrorType::AllocatorFull: + { + auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); + ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); + return m_DescriptorManager->Acquire(); + } + default: + { + 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}; + } + + void GPUResourceManager::Release(const ConstantBufferHandle InHandle) + { + ThrowIfUnexpected(m_Resources.Release(InHandle.GetHandle())); + } + + void GPUResourceManager::Release(const ConstantBufferViewHandle InHandle) + { + ThrowIfUnexpected(m_Descriptors.Release(InHandle.GetCBVHandle())); + } +} diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h new file mode 100644 index 00000000..11b0a33f --- /dev/null +++ b/src/Public/D3D12/GPUResourceManager.h @@ -0,0 +1,103 @@ + +#pragma once + +#include "D3D12/CommandQueue.h" +#include "D3D12/DescriptorManager.h" +#include "D3D12/FencedResourceFreeList.h" +#include "D3D12/GPUDevice.h" +#include "D3D12/GPUResource.h" +#include "Utility/Object.h" +#include "Utility/Pointer.h" +#include "Utility/VersionedIndex.h" + +#include +#include + +namespace stf +{ + + class CommandList; + + 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_view 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; + }; + + GPUResourceManager(ObjectToken InToken, const CreationParams& InParams); + + [[nodiscard]] ConstantBufferHandle Acquire(const ConstantBufferDesc InDesc); + [[nodiscard]] ConstantBufferViewHandle CreateCBV(const ConstantBufferHandle InHandle); + + template + void UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) + { + UploadData(std::as_bytes(std::span{ &InData })); + } + + void UploadData(const std::span InBytes, const ConstantBufferHandle InBufferHandle); + + void Release(ConstantBufferHandle InHandle); + void Release(ConstantBufferViewHandle InHandle); + + void SetCBV(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); + + private: + + SharedPtr m_Device; + SharedPtr m_Queue; + SharedPtr m_DescriptorManager; + + ResourceManager m_Resources; + DescriptorFreeList m_Descriptors; + DescriptorHeapReleaseManager m_HeapReleaseManager; + }; +} \ No newline at end of file From 69d390abe78d1d8ad1ef4aca27049c0064fbcfed Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 12:55:40 +0000 Subject: [PATCH 38/76] Incorporating the gpu resource manager into the command engine. Also no tests just yet :) --- src/Private/D3D12/CommandEngine.cpp | 11 ++- src/Private/Framework/ShaderTestDriver.cpp | 2 +- src/Private/Framework/ShaderTestShader.cpp | 4 +- src/Public/D3D12/CommandEngine.h | 87 +++++++++++++++++++--- src/Public/Framework/ShaderTestShader.h | 3 +- 5 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 8707a9c4..1163d2ef 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -2,7 +2,7 @@ namespace stf { - CommandEngine::CommandEngine(ObjectToken InToken, CreationParams InParams) + CommandEngine::CommandEngine(ObjectToken InToken, const CreationParams& InParams) : Object(InToken) , m_Device(InParams.Device) , m_Queue(InParams.Device->CreateCommandQueue( @@ -17,6 +17,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/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index bb2537b6..d697574c 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -141,7 +141,7 @@ namespace stf InContext->SetBufferUAV(*assertBuffer); InContext->SetBufferUAV(*allocationBuffer); - InTestDesc.Shader.SetConstantBufferData(*InContext); + InTestDesc.Shader.SetConstantBufferData(InContext); } ); diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp index ccf99de3..29e385bd 100644 --- a/src/Private/Framework/ShaderTestShader.cpp +++ b/src/Private/Framework/ShaderTestShader.cpp @@ -63,11 +63,11 @@ namespace stf }); } - void ShaderTestShader::SetConstantBufferData(CommandList& InList) const + void ShaderTestShader::SetConstantBufferData(ScopedCommandContext& InCommandContext) const { for (const auto& [paramIndex, buffer] : m_RootParamBuffers) { - InList.SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); + InCommandContext->SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); } } diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 97fa8ef3..99ad8d20 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -4,6 +4,7 @@ #include "D3D12/CommandAllocator.h" #include "D3D12/CommandQueue.h" #include "D3D12/GPUDevice.h" +#include "D3D12/GPUResourceManager.h" #include "Utility/FunctionTraits.h" #include "Utility/Lambda.h" @@ -17,9 +18,11 @@ namespace stf class CommandEngineToken { friend class CommandEngine; + friend class ScopedCommandContext; CommandEngineToken() = default; }; + class CommandEngine; class ScopedCommandContext; template @@ -35,22 +38,70 @@ namespace stf TFuncTraits::ParamTypes::Size == 1 && std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&>; + + class ScopedGPUResourceManager + { + public: + + ScopedGPUResourceManager(const SharedPtr& InResourceManager) + : m_ResourceManager(InResourceManager) + { + } + + ~ScopedGPUResourceManager() noexcept + { + for (const auto& cb : m_ConstantBuffers) + { + m_ResourceManager->Release(cb); + } + + for (const auto& cbv : m_CBVs) + { + m_ResourceManager->Release(cbv); + } + } + + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle 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); + + m_ResourceManager->UploadData(InData, buffer); + + m_ConstantBuffers.push_back(buffer); + m_CBVs.push_back(cbv); + + return cbv; + } + + private: + + SharedPtr m_ResourceManager; + std::vector m_ConstantBuffers; + std::vector m_CBVs; + }; + class ScopedCommandContext { public: - ScopedCommandContext(CommandEngineToken, CommandList* InList) + ScopedCommandContext(CommandEngineToken, + const SharedPtr& InList, + const SharedPtr& InResourceManager + ) : m_List(InList) - {} + , m_ResourceManager(MakeUnique(InResourceManager)) + { + } CommandList* operator->() const { - return m_List; + return GetList(); } CommandList& operator*() const { - return *m_List; + return *GetList(); } template @@ -60,11 +111,24 @@ namespace stf InFunc(*this); } + CommandList* GetList() const + { + return m_List.get(); + } + + + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData) + { + return m_ResourceManager->CreateCBV(InData); + } + private: - CommandList* m_List = nullptr; + + SharedPtr m_List = nullptr; + UniquePtr m_ResourceManager = nullptr; }; - class CommandEngine + class CommandEngine : public Object { public: @@ -74,7 +138,7 @@ namespace stf SharedPtr Device; }; - CommandEngine(ObjectToken, CreationParams InParams); + CommandEngine(ObjectToken, const CreationParams& InParams); template void Execute(const InLambdaType& InFunc) @@ -94,7 +158,9 @@ namespace stf }(); m_List->Reset(allocator); - ScopedCommandContext context(CommandEngineToken{}, m_List.get()); + ScopedCommandContext context(CommandEngineToken{}, m_List + , m_ResourceManager + ); InFunc(context); m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); @@ -119,7 +185,9 @@ namespace stf }(); m_List->Reset(*allocator); - ScopedCommandContext context(CommandEngineToken{}, m_List.get()); + ScopedCommandContext context(CommandEngineToken{}, m_List + , m_ResourceManager + ); InFunc(context); m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); @@ -146,6 +214,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/Framework/ShaderTestShader.h b/src/Public/Framework/ShaderTestShader.h index 9394c7e6..7bac5679 100644 --- a/src/Public/Framework/ShaderTestShader.h +++ b/src/Public/Framework/ShaderTestShader.h @@ -3,6 +3,7 @@ #include "Platform.h" +#include "D3D12/CommandEngine.h" #include "D3D12/CommandList.h" #include "D3D12/GPUDevice.h" #include "D3D12/Shader/CompiledShaderData.h" @@ -35,7 +36,7 @@ namespace stf Expected Init(); Expected BindConstantBufferData(const std::span InBindings); - void SetConstantBufferData(CommandList& InList) const; + void SetConstantBufferData(ScopedCommandContext& InList) const; uint3 GetThreadGroupSize() const; From 0e673d5ee82504311bdfe956c1dba268fcd3c74b Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 30 Dec 2025 13:00:14 +0000 Subject: [PATCH 39/76] Fixing compilation errors --- src/Private/D3D12/GPUResourceManager.cpp | 4 ++++ src/Public/D3D12/GPUResourceManager.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index d773b9dc..75e35a41 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -107,6 +107,10 @@ namespace stf return ConstantBufferViewHandle{ Private{}, InBufferHandle.GetHandle(), managedHandle}; } + void GPUResourceManager::UploadData(const std::span, const ConstantBufferHandle) + { + } + void GPUResourceManager::Release(const ConstantBufferHandle InHandle) { ThrowIfUnexpected(m_Resources.Release(InHandle.GetHandle())); diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 11b0a33f..6716156c 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -80,7 +80,7 @@ namespace stf template void UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) { - UploadData(std::as_bytes(std::span{ &InData })); + UploadData(std::as_bytes(std::span{ &InData }), InBufferHandle); } void UploadData(const std::span InBytes, const ConstantBufferHandle InBufferHandle); From 3ac49d7fc684b0c93303d4a4d2f2ed517082a8aa Mon Sep 17 00:00:00 2001 From: KStocky Date: Fri, 2 Jan 2026 18:00:07 +0000 Subject: [PATCH 40/76] Enabling writes to mapped resources --- src/Private/D3D12/GPUResource.cpp | 6 +++--- src/Public/D3D12/GPUResource.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Private/D3D12/GPUResource.cpp b/src/Private/D3D12/GPUResource.cpp index 9e964fad..87ed0dba 100644 --- a/src/Private/D3D12/GPUResource.cpp +++ b/src/Private/D3D12/GPUResource.cpp @@ -52,8 +52,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) @@ -61,7 +61,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/Public/D3D12/GPUResource.h b/src/Public/D3D12/GPUResource.h index 698dd022..7242af3a 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,7 +42,7 @@ namespace stf private: ComPtr m_Resource; - std::span m_MappedData; + std::span m_MappedData; }; class GPUResource From e9f81a482666c14f892029334b7ccc3c5e75b7ec Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 3 Jan 2026 12:14:53 +0000 Subject: [PATCH 41/76] Allow querying of max descriptor heap size --- src/Private/D3D12/GPUDevice.cpp | 185 +++++++++++++++++--------------- src/Public/D3D12/GPUDevice.h | 45 ++++---- 2 files changed, 124 insertions(+), 106 deletions(-) diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 1325a5ed..59ee8556 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -127,10 +127,6 @@ namespace stf : 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; @@ -152,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()); } @@ -386,8 +377,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)))); @@ -418,92 +409,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/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index 21602578..34a812ca 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 @@ -231,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 From 1d29e6b35ae391079c7d3ad0836824ae3f7f694e Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 4 Jan 2026 13:04:39 +0000 Subject: [PATCH 42/76] Move HLSLTypes to Utility --- examples/Ex8_ConstantBuffers/ConstantBuffers.cpp | 4 +++- src/CMakeLists.txt | 2 +- src/Public/Framework/ShaderTestCommon.h | 3 ++- src/Public/Framework/ShaderTestDriver.h | 2 +- src/Public/Framework/ShaderTestFixture.h | 2 +- src/Public/Framework/ShaderTestShader.h | 2 +- src/Public/Framework/TestDataBufferProcessor.h | 2 +- src/Public/{Framework => Utility}/HLSLTypes.h | 0 test/CMakeLists.txt | 2 +- test/Private/D3D12/Shader/ShaderBindingTests.cpp | 2 +- .../Framework/HLSLFramework/Binding/ValueBindingsTests.cpp | 2 +- test/Private/{Framework => Utility}/HLSLTypesTests.cpp | 2 +- 12 files changed, 14 insertions(+), 11 deletions(-) rename src/Public/{Framework => Utility}/HLSLTypes.h (100%) rename test/Private/{Framework => Utility}/HLSLTypesTests.cpp (99%) 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 8fcfeeb2..581b22a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,7 +51,6 @@ set(SOURCES 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 @@ -74,6 +73,7 @@ set(SOURCES 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 diff --git a/src/Public/Framework/ShaderTestCommon.h b/src/Public/Framework/ShaderTestCommon.h index 0565d1b5..ffbc8177 100644 --- a/src/Public/Framework/ShaderTestCommon.h +++ b/src/Public/Framework/ShaderTestCommon.h @@ -2,10 +2,11 @@ #include "Platform.h" -#include "Framework/HLSLTypes.h" #include "Framework/TypeByteReader.h" #include "Framework/TestDataBufferLayout.h" +#include "Utility/HLSLTypes.h" + #include #include #include diff --git a/src/Public/Framework/ShaderTestDriver.h b/src/Public/Framework/ShaderTestDriver.h index 43e01e12..92dd0566 100644 --- a/src/Public/Framework/ShaderTestDriver.h +++ b/src/Public/Framework/ShaderTestDriver.h @@ -5,13 +5,13 @@ #include "D3D12/Shader/PipelineState.h" #include "D3D12/Shader/RootSignature.h" -#include "Framework/HLSLTypes.h" #include "Framework/ShaderTestDescriptorManager.h" #include "Framework/ShaderTestShader.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" diff --git a/src/Public/Framework/ShaderTestFixture.h b/src/Public/Framework/ShaderTestFixture.h index 0307f689..3a22974f 100644 --- a/src/Public/Framework/ShaderTestFixture.h +++ b/src/Public/Framework/ShaderTestFixture.h @@ -3,12 +3,12 @@ #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/HLSLTypes.h" #include "Utility/Pointer.h" #include "Utility/TransparentStringHash.h" #include diff --git a/src/Public/Framework/ShaderTestShader.h b/src/Public/Framework/ShaderTestShader.h index 7bac5679..c4b3c313 100644 --- a/src/Public/Framework/ShaderTestShader.h +++ b/src/Public/Framework/ShaderTestShader.h @@ -10,9 +10,9 @@ #include "D3D12/Shader/RootSignature.h" #include "D3D12/Shader/ShaderBinding.h" -#include "Framework/HLSLTypes.h" #include "Framework/ShaderTestCommon.h" #include "Utility/Expected.h" +#include "Utility/HLSLTypes.h" #include "Utility/Object.h" #include "Utility/TransparentStringHash.h" 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/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/test/CMakeLists.txt b/test/CMakeLists.txt index 4756e390..cef274ba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -198,7 +198,6 @@ 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 @@ -214,6 +213,7 @@ set(SOURCES 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 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/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp index 648a8232..9ab06c24 100644 --- a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp +++ b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp @@ -1,9 +1,9 @@ #include "Framework/HLSLFramework/HLSLFrameworkTestsCommon.h" -#include #include #include +#include #include #include 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 From 0819f5773f9b3cecceaf975b019bf9ea7b5eda3e Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 5 Jan 2026 09:27:30 +0000 Subject: [PATCH 43/76] Adding a function to FixedString to return a StringLiteral --- src/Public/Utility/FixedString.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Public/Utility/FixedString.h b/src/Public/Utility/FixedString.h index b30461e1..25ed9847 100644 --- a/src/Public/Utility/FixedString.h +++ b/src/Public/Utility/FixedString.h @@ -1,6 +1,8 @@ #pragma once #include "Platform.h" + +#include "Utility/StringLiteral.h" #include #include @@ -21,6 +23,11 @@ namespace stf { return std::string_view{ Data }; } + + consteval StringLiteral Literal() const + { + return StringLiteral{ View() }; + } }; template From 8d314c93d16107bd36304f7db868e197d607f724 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 5 Jan 2026 09:44:09 +0000 Subject: [PATCH 44/76] Adding convenience factory function to make an Error from an initial fragment. Also adding equality operators to error to check for equality --- src/Public/Utility/Error.h | 16 +++++ test/Private/Utility/ErrorTests.cpp | 95 +++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index 9e4282b3..ebad3f18 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -95,6 +95,19 @@ namespace stf 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)); @@ -140,6 +153,9 @@ namespace stf 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; + private: std::vector m_Fragments{}; diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp index 2c010c74..bfaa1af7 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -252,4 +253,98 @@ SCENARIO("Error Tests") } } } + + GIVEN("Constructed from a fragment") + { + static constexpr FixedString expectedFormat{ "Error 1" }; + const auto error = Error::FromFragment(); + + THEN("contains expected fragment") + { + REQUIRE(error.HasFragmentWithFormat(expectedFormat.Literal())); + } + } + + 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 From 894d7bc226bfa4fb574cfd178a235c9559be5950 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 5 Jan 2026 09:44:41 +0000 Subject: [PATCH 45/76] Fixing value binding test so that it is actually testing the thing it claims to test --- .../GlobalBindingsTooLarge.hlsl | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 From 2eeb437647cf89d20b6ccabdfe8126c0d3a99277 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 6 Jan 2026 08:28:54 +0000 Subject: [PATCH 46/76] Shadering binding map that abstracts out the binding logic from shaders. It also allows us to generate the root signature using reflection and returning an error if we need to. --- src/CMakeLists.txt | 2 + src/Private/D3D12/Shader/ShaderBindingMap.cpp | 184 ++++++++++++++++++ src/Public/D3D12/Shader/ShaderBindingMap.h | 61 ++++++ 3 files changed, 247 insertions(+) create mode 100644 src/Private/D3D12/Shader/ShaderBindingMap.cpp create mode 100644 src/Public/D3D12/Shader/ShaderBindingMap.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 581b22a5..817aa380 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,7 @@ set(SOURCES Public/D3D12/Shader/PipelineState.h Public/D3D12/Shader/RootSignature.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 @@ -105,6 +106,7 @@ set(SOURCES Private/D3D12/Shader/PipelineState.cpp Private/D3D12/Shader/RootSignature.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 diff --git a/src/Private/D3D12/Shader/ShaderBindingMap.cpp b/src/Private/D3D12/Shader/ShaderBindingMap.cpp new file mode 100644 index 00000000..5a0050fa --- /dev/null +++ b/src/Private/D3D12/Shader/ShaderBindingMap.cpp @@ -0,0 +1,184 @@ + +#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 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)); + + if (!ConstantBufferCanBeBoundToRootConstants(*constantBuffer)) + { + return Unexpected{ Errors::ConstantBufferCantBeInRootConstants(bufferDesc.Name) }; + } + + const u32 numValues = bufferDesc.Size / sizeof(u32); + totalNumValues += numValues; + + if (totalNumValues > 64) + { + return Unexpected(Errors::RootSignatureDWORDLimitReached()); + } + + 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 + }); + } + } + else + { + nameToBindingMap.emplace( + std::string{ bindDesc.Name }, + BindingInfo{ + .RootParamIndex = static_cast(parameters.size()), + .OffsetIntoBuffer = 0, + .BindingSize = bufferDesc.Size + }); + } + + stagingBuffers[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 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.size() > 0, "Shader binding buffer has a size of zero. It should have been created and initialized when processing the reflection data"); + + const u32 uintIndex = bindingIter->second.OffsetIntoBuffer / sizeof(u32); + std::memcpy(bindingBuffer.data() + uintIndex, bindingData.data(), bindingData.size_bytes()); + } + else + { + return Unexpected{ Errors::BindingDoesNotExist(InBinding.GetName()) }; + } + + return {}; + } + + void ShaderBindingMap::CommitBindings(ScopedCommandContext& InContext) const + { + for (const auto& [paramIndex, buffer] : m_RootParamBuffers) + { + InContext->SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); + } + } + + 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/Public/D3D12/Shader/ShaderBindingMap.h b/src/Public/D3D12/Shader/ShaderBindingMap.h new file mode 100644 index 00000000..63a05022 --- /dev/null +++ b/src/Public/D3D12/Shader/ShaderBindingMap.h @@ -0,0 +1,61 @@ +#pragma once + +#include "D3D12/CommandEngine.h" +#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 BindingIsSmallerThanBindingData(const std::string_view InBindingName, const u32 InConstantBufferSize, const u64 InBindingDataSize); + Error BindingDoesNotExist(const std::string_view InBindingName); + } + + class ShaderBindingMap + { + public: + + static ExpectedError Make(ID3D12ShaderReflection& InReflection, GPUDevice& InDevice); + + const RootSignature& GetRootSig() const; + + ExpectedError StageBindingData(const ShaderBinding& InBinding); + void CommitBindings(ScopedCommandContext& InContext) const; + + private: + + struct BindingInfo + { + u32 RootParamIndex = 0; + u32 OffsetIntoBuffer = 0; + u32 BindingSize = 0; + }; + + using BindingMapType = std::unordered_map>; + using StagingBufferMap = 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 From 7e4cd1a99e0a4b08507ae4055e4d92d2d0a767ca Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 6 Jan 2026 08:30:24 +0000 Subject: [PATCH 47/76] Shader class. This is a generic shader class that takes most of its logic from ShaderTestShader. --- src/CMakeLists.txt | 2 + src/Private/D3D12/Shader/Shader.cpp | 74 +++++++++++++++++++++++++++++ src/Public/D3D12/Shader/Shader.h | 55 +++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 src/Private/D3D12/Shader/Shader.cpp create mode 100644 src/Public/D3D12/Shader/Shader.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 817aa380..c6ca0ea0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set(SOURCES 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 @@ -105,6 +106,7 @@ set(SOURCES 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 diff --git a/src/Private/D3D12/Shader/Shader.cpp b/src/Private/D3D12/Shader/Shader.cpp new file mode 100644 index 00000000..52dfd6b5 --- /dev/null +++ b/src/Private/D3D12/Shader/Shader.cpp @@ -0,0 +1,74 @@ + +#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, ShaderToken, 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( + [&](const ShaderBindingMap& InShaderBindingMap) -> ExpectedError + { + return Shader::CreationParams + { + .ShaderData = InShaderData, + .BindingMap = InShaderBindingMap + }; + }) + .and_then( + [](const Shader::CreationParams& InCreationParams) -> ExpectedError> + { + return Object::New(ShaderToken{}, InCreationParams); + }); + } + + ExpectedError Shader::StageBindingData(const ShaderBinding& InBinding) + { + return m_BindingMap.StageBindingData(InBinding); + } + + void Shader::CommitBindings(ScopedCommandContext& InContext) const + { + m_BindingMap.CommitBindings(InContext); + } + + 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/Public/D3D12/Shader/Shader.h b/src/Public/D3D12/Shader/Shader.h new file mode 100644 index 00000000..5b7be909 --- /dev/null +++ b/src/Public/D3D12/Shader/Shader.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "D3D12/CommandEngine.h" +#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 + { + CompiledShaderData ShaderData; + ShaderBindingMap BindingMap; + }; + + Shader(ObjectToken, ShaderToken, const CreationParams& InParams); + + static ExpectedError> Make(const CompiledShaderData& InShaderData, GPUDevice& InDevice); + + ExpectedError StageBindingData(const ShaderBinding& InBindings); + void CommitBindings(ScopedCommandContext& InContext) const; + + uint3 GetThreadGroupSize() const; + + const RootSignature& GetRootSig() const; + + IDxcBlob* GetCompiledShader() const; + + private: + + CompiledShaderData m_ShaderData; + ShaderBindingMap m_BindingMap; + }; +} \ No newline at end of file From a61d06058e6169de29ec9271fdb342870744b544 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 6 Jan 2026 08:33:00 +0000 Subject: [PATCH 48/76] Adding missing header to shader reflection utils.cpp --- src/Private/D3D12/Shader/ShaderReflectionUtils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp index 9c030af6..8104fd12 100644 --- a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp +++ b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp @@ -1,7 +1,9 @@ +#include "Platform.h" #include "D3D12/Shader/ShaderReflectionUtils.h" #include "Utility/Exception.h" + namespace stf { bool IsOrContainsArray(ID3D12ShaderReflectionType& InType) From bdbe10daa60ca4748ce679b3d15367e10fd729c1 Mon Sep 17 00:00:00 2001 From: KStocky Date: Tue, 6 Jan 2026 08:52:43 +0000 Subject: [PATCH 49/76] Major Shader refactor. This also involved converting the test fixture to using ExpectedError over ErrorTypeAndDescription. --- src/Private/D3D12/Shader/ShaderCompiler.cpp | 61 ++++-- src/Private/Framework/ShaderTestCommon.cpp | 16 +- src/Private/Framework/ShaderTestDriver.cpp | 41 ++-- src/Private/Framework/ShaderTestFixture.cpp | 52 ++--- src/Private/Framework/ShaderTestShader.cpp | 202 +++--------------- src/Public/D3D12/Shader/ShaderCompiler.h | 19 +- src/Public/Framework/ShaderTestCommon.h | 15 +- src/Public/Framework/ShaderTestDriver.h | 2 +- src/Public/Framework/ShaderTestFixture.h | 5 +- src/Public/Framework/ShaderTestShader.h | 36 ++-- test/Private/D3D12/Shader/HLSLTests.cpp | 2 +- .../Shader/ShaderIncludeHandlerTests.cpp | 2 +- .../Private/D3D12/Shader/ShaderModelTests.cpp | 2 +- .../Binding/ValueBindingsTests.cpp | 13 +- .../TestDataBufferProcessorTests.cpp | 4 +- 15 files changed, 152 insertions(+), 320 deletions(-) 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/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/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index d697574c..4f01fdbf 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -94,9 +94,9 @@ 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)); @@ -107,17 +107,16 @@ namespace stf const auto assertUAV = CreateUAV(assertBuffer, CreateRawUAVDesc(bufferSizeInBytes)); const auto allocationUAV = CreateUAV(allocationBuffer, CreateRawUAVDesc(allocationBufferSizeInBytes)); - 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) + return InTestDesc.Shader.StageConstantBufferData( + ShaderTestShader::TestBindings + { + .DispatchConfig = InTestDesc.DispatchConfig, + .AllocationBufferIndex = allocationUAV.Handle.GetIndex(), + .TestDataBufferIndex = assertUAV.Handle.GetIndex(), + .TestDataLayout = InTestDesc.TestBufferLayout + }, + InTestDesc.Bindings + ) .and_then( [&, this]() { @@ -137,11 +136,11 @@ namespace stf { InContext->SetPipelineState(*pipelineState); m_DescriptorManager->SetDescriptorHeap(*InContext); - InContext->SetComputeRootSignature(*InTestDesc.Shader.GetRootSig()); + InContext->SetComputeRootSignature(InTestDesc.Shader.GetRootSig()); InContext->SetBufferUAV(*assertBuffer); InContext->SetBufferUAV(*allocationBuffer); - InTestDesc.Shader.SetConstantBufferData(InContext); + InTestDesc.Shader.CommitBindings(InContext); } ); @@ -187,19 +186,11 @@ namespace stf { case DescriptorAlreadyFree: { - return ErrorTypeAndDescription - { - .Type = ETestRunErrorType::DescriptorManagement, - .Error = "Attempted to free an already freed descriptor." - }; + return Error::FromFragment<"Attempted to free an already freed descriptor.">(); } default: { - return ErrorTypeAndDescription - { - .Type = ETestRunErrorType::Unknown, - .Error = "Unknown descriptor management error." - }; + return Error::FromFragment<"Unknown descriptor management error.">(); } } } diff --git a/src/Private/Framework/ShaderTestFixture.cpp b/src/Private/Framework/ShaderTestFixture.cpp index 4d92e483..9ca901e8 100644 --- a/src/Private/Framework/ShaderTestFixture.cpp +++ b/src/Private/Framework/ShaderTestFixture.cpp @@ -1,8 +1,10 @@ #include "Framework/ShaderTestFixture.h" +#include "D3D12/GPUDevice.h" +#include "D3D12/Shader/Shader.h" + #include "Framework/PIXCapturer.h" #include "Utility/EnumReflection.h" -#include "D3D12/GPUDevice.h" #include #include @@ -85,12 +87,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,19 +118,12 @@ 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 CreateTestShader(InCompilationResult); }) .and_then( - [this, &InTestDesc, takeCapture](SharedPtr InShader) + [&](const SharedPtr& InShader) { const auto capturer = PIXCapturer(InTestDesc.TestName, takeCapture); return m_TestDriver->RunShaderTest( @@ -141,14 +136,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,21 +167,20 @@ namespace stf job.Flags = Enum::MakeFlags(EShaderCompileFlags::SkipOptimization, EShaderCompileFlags::O0); } - return m_Compiler - .CompileShader(job) - .transform( - [this](CompiledShaderData InData) - { - return Object::New(ShaderTestShader::CreationParams{ .ShaderData = std::move(InData), .Device = m_Device }); - }) - .transform_error( - [](std::string InError) + return m_Compiler.CompileShader(job); + } + + ExpectedError> ShaderTestFixture::CreateTestShader(const CompiledShaderData& InCompiledShaderData) const + { + return Shader::Make(InCompiledShaderData, *m_Device) + .and_then( + [](const SharedPtr& InShader) -> ExpectedError> { - return ErrorTypeAndDescription - { - .Type = ETestRunErrorType::ShaderCompilation, - .Error = std::move(InError) - }; + return Object::New( + ShaderTestShader::CreationParams + { + .Shader = InShader + }); }); } diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp index 29e385bd..e28b458b 100644 --- a/src/Private/Framework/ShaderTestShader.cpp +++ b/src/Private/Framework/ShaderTestShader.cpp @@ -4,203 +4,49 @@ namespace stf { - ShaderTestShader::ShaderTestShader(ObjectToken InToken, CreationParams InParams) + ShaderTestShader::ShaderTestShader(ObjectToken InToken, const CreationParams& InParams) : Object(InToken) - , m_ShaderData(std::move(InParams.ShaderData)) - , m_Device(std::move(InParams.Device)) - , m_RootSignature() - , m_NameToBindingInfo() - , m_RootParamBuffers() + , m_Shader(InParams.Shader) { } - Expected ShaderTestShader::BindConstantBufferData(const std::span InBindings) + ExpectedError ShaderTestShader::StageConstantBufferData(const TestBindings& InTestBindings, 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()); - } + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::DispatchDimensions", InTestBindings.DispatchConfig * GetThreadGroupSize() }); + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::AllocationBufferIndex", InTestBindings.AllocationBufferIndex }); + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::TestDataBufferIndex", InTestBindings.TestDataBufferIndex }); + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Asserts", InTestBindings.TestDataLayout.GetAssertSection() }); + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Strings", InTestBindings.TestDataLayout.GetStringSection() }); + std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Sections", InTestBindings.TestDataLayout.GetSectionInfoSection() }); + + for (const auto& binding : InBindings) + { + if (auto result = m_Shader->StageBindingData(binding); !result) + { + return result; + } + } - return {}; - }); + return {}; } - void ShaderTestShader::SetConstantBufferData(ScopedCommandContext& InCommandContext) const + void ShaderTestShader::CommitBindings(ScopedCommandContext& InCommandContext) const { - for (const auto& [paramIndex, buffer] : m_RootParamBuffers) - { - InCommandContext->SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); - } + m_Shader->CommitBindings(InCommandContext); } uint3 ShaderTestShader::GetThreadGroupSize() const { - uint3 ret; - m_ShaderData.GetReflection()->GetThreadGroupSize(&ret.x, &ret.y, &ret.z); - - return ret; + return m_Shader->GetThreadGroupSize(); } - RootSignature* ShaderTestShader::GetRootSig() const + const RootSignature& ShaderTestShader::GetRootSig() const { - return m_RootSignature.get(); + return m_Shader->GetRootSig(); } 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 {}; + return m_Shader->GetCompiledShader(); } } \ 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/Framework/ShaderTestCommon.h b/src/Public/Framework/ShaderTestCommon.h index ffbc8177..857f9db6 100644 --- a/src/Public/Framework/ShaderTestCommon.h +++ b/src/Public/Framework/ShaderTestCommon.h @@ -5,6 +5,7 @@ #include "Framework/TypeByteReader.h" #include "Framework/TestDataBufferLayout.h" +#include "Utility/Error.h" #include "Utility/HLSLTypes.h" #include @@ -64,30 +65,22 @@ namespace stf 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/ShaderTestDriver.h b/src/Public/Framework/ShaderTestDriver.h index 92dd0566..f39adb06 100644 --- a/src/Public/Framework/ShaderTestDriver.h +++ b/src/Public/Framework/ShaderTestDriver.h @@ -48,7 +48,7 @@ namespace stf TypeReaderIndex RegisterByteReader(std::string InTypeIDName, MultiTypeByteReader InByteReader); TypeReaderIndex RegisterByteReader(std::string InTypeIDName, SingleTypeByteReader InByteReader); - Expected RunShaderTest(TestDesc InTestDesc); + ExpectedError RunShaderTest(TestDesc&& InTestDesc); private: diff --git a/src/Public/Framework/ShaderTestFixture.h b/src/Public/Framework/ShaderTestFixture.h index 3a22974f..6a642c42 100644 --- a/src/Public/Framework/ShaderTestFixture.h +++ b/src/Public/Framework/ShaderTestFixture.h @@ -7,7 +7,7 @@ #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" @@ -118,7 +118,8 @@ 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; + ExpectedError> CreateTestShader(const CompiledShaderData& InCompiledShaderData) 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 index c4b3c313..df3a0219 100644 --- a/src/Public/Framework/ShaderTestShader.h +++ b/src/Public/Framework/ShaderTestShader.h @@ -8,7 +8,7 @@ #include "D3D12/GPUDevice.h" #include "D3D12/Shader/CompiledShaderData.h" #include "D3D12/Shader/RootSignature.h" -#include "D3D12/Shader/ShaderBinding.h" +#include "D3D12/Shader/Shader.h" #include "Framework/ShaderTestCommon.h" #include "Utility/Expected.h" @@ -28,36 +28,30 @@ namespace stf public: struct CreationParams { - CompiledShaderData ShaderData; - SharedPtr Device; + SharedPtr Shader; }; - ShaderTestShader(ObjectToken, CreationParams InParams); + struct TestBindings + { + uint3 DispatchConfig{0}; + u32 AllocationBufferIndex = 0; + u32 TestDataBufferIndex = 0; + TestDataBufferLayout TestDataLayout{}; + }; - Expected Init(); - Expected BindConstantBufferData(const std::span InBindings); - void SetConstantBufferData(ScopedCommandContext& InList) const; + ShaderTestShader(ObjectToken, const CreationParams& InParams); + + ExpectedError StageConstantBufferData(const TestBindings& InTestBindings, const std::span InBindings); + void CommitBindings(ScopedCommandContext& InList) const; uint3 GetThreadGroupSize() const; - RootSignature* GetRootSig() const; + 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; + SharedPtr m_Shader; }; } \ 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/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 9ab06c24..f31555c7 100644 --- a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp +++ b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp @@ -1,6 +1,7 @@ #include "Framework/HLSLFramework/HLSLFrameworkTestsCommon.h" +#include #include #include #include @@ -22,7 +23,7 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - auto [testName, testFile, bindings, expectedResult] = GENERATE ( - table, Expected> + table, ExpectedError> ( { std::tuple @@ -59,7 +60,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,7 +73,7 @@ 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 { @@ -87,7 +88,7 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { "G", std::array{}}, { "H", std::array{}} }, - Unexpected{ ErrorTypeAndDescription {.Type = ETestRunErrorType::RootSignatureGeneration } } + Unexpected{ Errors::RootSignatureDWORDLimitReached() } }, std::tuple { @@ -96,7 +97,7 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - std::vector { }, - Unexpected{ ErrorTypeAndDescription {.Type = ETestRunErrorType::RootSignatureGeneration } } + Unexpected{ Errors::ConstantBufferCantBeInRootConstants("$Globals")} }, std::tuple { @@ -153,7 +154,7 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - { 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/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 } } From 2fe2107186127ef01382ffaff02dacce0c048341 Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 8 Jan 2026 08:36:19 +0000 Subject: [PATCH 50/76] Slight refactor to make the creation params inherit from the shader token to streamline shader creation --- src/Private/D3D12/Shader/Shader.cpp | 8 ++++---- src/Public/D3D12/Shader/Shader.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Private/D3D12/Shader/Shader.cpp b/src/Private/D3D12/Shader/Shader.cpp index 52dfd6b5..d51880d5 100644 --- a/src/Private/D3D12/Shader/Shader.cpp +++ b/src/Private/D3D12/Shader/Shader.cpp @@ -11,7 +11,7 @@ namespace stf } } - Shader::Shader(ObjectToken InToken, ShaderToken, const CreationParams& InParams) + Shader::Shader(ObjectToken InToken, const CreationParams& InParams) : Object(InToken) , m_ShaderData(InParams.ShaderData) , m_BindingMap(InParams.BindingMap) @@ -29,18 +29,18 @@ namespace stf return ShaderBindingMap::Make(*reflection, InDevice) .and_then( - [&](const ShaderBindingMap& InShaderBindingMap) -> ExpectedError + [&](ShaderBindingMap&& InShaderBindingMap) -> ExpectedError { return Shader::CreationParams { .ShaderData = InShaderData, - .BindingMap = InShaderBindingMap + .BindingMap = std::move(InShaderBindingMap) }; }) .and_then( [](const Shader::CreationParams& InCreationParams) -> ExpectedError> { - return Object::New(ShaderToken{}, InCreationParams); + return Object::New(InCreationParams); }); } diff --git a/src/Public/D3D12/Shader/Shader.h b/src/Public/D3D12/Shader/Shader.h index 5b7be909..6147b3b9 100644 --- a/src/Public/D3D12/Shader/Shader.h +++ b/src/Public/D3D12/Shader/Shader.h @@ -28,13 +28,13 @@ namespace stf : public Object { public: - struct CreationParams + struct CreationParams : ShaderToken { CompiledShaderData ShaderData; ShaderBindingMap BindingMap; }; - Shader(ObjectToken, ShaderToken, const CreationParams& InParams); + Shader(ObjectToken, const CreationParams& InParams); static ExpectedError> Make(const CompiledShaderData& InShaderData, GPUDevice& InDevice); From bec3fd95c61136fd1760ac8e823617f6678bd440 Mon Sep 17 00:00:00 2001 From: KStocky Date: Fri, 9 Jan 2026 21:57:17 +0000 Subject: [PATCH 51/76] Support constant buffers being bound as root descriptors --- src/Private/D3D12/CommandEngine.cpp | 18 +-- src/Private/D3D12/CommandList.cpp | 31 ++++++ src/Private/D3D12/GPUResourceManager.cpp | 47 +++++++- src/Private/D3D12/Shader/ShaderBindingMap.cpp | 96 ++++++++++++---- .../D3D12/Shader/ShaderReflectionUtils.cpp | 33 ++++++ src/Private/Framework/ShaderTestDriver.cpp | 2 +- src/Public/D3D12/CommandEngine.h | 22 +++- src/Public/D3D12/CommandList.h | 7 +- src/Public/D3D12/GPUResourceManager.h | 12 +- src/Public/D3D12/Shader/ShaderBindingMap.h | 18 ++- .../D3D12/Shader/ShaderReflectionUtils.h | 4 +- test/CMakeLists.txt | 2 + .../Binding/ValueBindingsTests.cpp | 103 ++++++++++++++---- .../BindingsHave2ByteAlignment.hlsl | 15 +++ .../ConstantBufferArray.hlsl | 14 +++ .../Binding/ValueBindingsTests/WithArray.hlsl | 3 +- 16 files changed, 356 insertions(+), 71 deletions(-) create mode 100644 test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/BindingsHave2ByteAlignment.hlsl create mode 100644 test/Shader/HLSLFrameworkTests/Binding/ValueBindingsTests/ConstantBufferArray.hlsl diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 1163d2ef..67e1861f 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -17,15 +17,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_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 94c4951e..aac6da21 100644 --- a/src/Private/D3D12/CommandList.cpp +++ b/src/Private/D3D12/CommandList.cpp @@ -82,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/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index 75e35a41..b18590e1 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -107,17 +107,54 @@ namespace stf return ConstantBufferViewHandle{ Private{}, InBufferHandle.GetHandle(), managedHandle}; } - void GPUResourceManager::UploadData(const std::span, const ConstantBufferHandle) + 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 {}; + } + ); } - void GPUResourceManager::Release(const ConstantBufferHandle InHandle) + ExpectedError GPUResourceManager::Release(const ConstantBufferHandle InHandle) { - ThrowIfUnexpected(m_Resources.Release(InHandle.GetHandle())); + return m_Resources.Release(InHandle.GetHandle()); } - void GPUResourceManager::Release(const ConstantBufferViewHandle InHandle) + ExpectedError GPUResourceManager::Release(const ConstantBufferViewHandle InHandle) { - ThrowIfUnexpected(m_Descriptors.Release(InHandle.GetCBVHandle())); + return m_Descriptors.Release(InHandle.GetCBVHandle()); + } + + 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 {}; + } + ); } } diff --git a/src/Private/D3D12/Shader/ShaderBindingMap.cpp b/src/Private/D3D12/Shader/ShaderBindingMap.cpp index 5a0050fa..5c3e19e6 100644 --- a/src/Private/D3D12/Shader/ShaderBindingMap.cpp +++ b/src/Private/D3D12/Shader/ShaderBindingMap.cpp @@ -21,6 +21,11 @@ namespace stf 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: {}"> @@ -63,17 +68,31 @@ namespace stf D3D12_SHADER_BUFFER_DESC bufferDesc{}; ThrowIfFailed(constantBuffer->GetDesc(&bufferDesc)); - if (!ConstantBufferCanBeBoundToRootConstants(*constantBuffer)) - { - return Unexpected{ Errors::ConstantBufferCantBeInRootConstants(bufferDesc.Name) }; - } - - const u32 numValues = bufferDesc.Size / sizeof(u32); - totalNumValues += numValues; - - if (totalNumValues > 64) + 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::RootSignatureDWORDLimitReached()); + return Unexpected{ Errors::ConstantBufferMustBeBoundToDecriptorTable(bufferDesc.Name) }; } if (bufferDesc.Name != nullptr && std::string_view{ bufferDesc.Name } == std::string_view{ "$Globals" }) @@ -89,7 +108,8 @@ namespace stf BindingInfo{ .RootParamIndex = static_cast(parameters.size()), .OffsetIntoBuffer = varDesc.StartOffset, - .BindingSize = varDesc.Size + .BindingSize = varDesc.Size, + .Type = bindingType }); } } @@ -100,14 +120,35 @@ namespace stf BindingInfo{ .RootParamIndex = static_cast(parameters.size()), .OffsetIntoBuffer = 0, - .BindingSize = bufferDesc.Size + .BindingSize = bufferDesc.Size, + .Type = bindingType }); } - stagingBuffers[static_cast(parameters.size())].resize(bufferDesc.Size / sizeof(u32)); + auto& stagingBuffer = stagingBuffers[static_cast(parameters.size())]; + stagingBuffer.Buffer.resize(bufferDesc.Size); + stagingBuffer.Type = bindingType; auto& parameter = parameters.emplace_back(); - parameter.InitAsConstants(numValues, bindDesc.BindPoint, bindDesc.Space); + + 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{}; @@ -150,10 +191,10 @@ namespace stf auto& bindingBuffer = m_RootParamBuffers[bindingIter->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"); + 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 uintIndex = bindingIter->second.OffsetIntoBuffer / sizeof(u32); - std::memcpy(bindingBuffer.data() + uintIndex, bindingData.data(), bindingData.size_bytes()); + const u32 offset = bindingIter->second.OffsetIntoBuffer; + std::memcpy(bindingBuffer.Buffer.data() + offset, bindingData.data(), bindingData.size_bytes()); } else { @@ -165,9 +206,26 @@ namespace stf void ShaderBindingMap::CommitBindings(ScopedCommandContext& InContext) const { - for (const auto& [paramIndex, buffer] : m_RootParamBuffers) + for (const auto& [rootParamIndex, buffer] : m_RootParamBuffers) { - InContext->SetComputeRoot32BitConstants(paramIndex, std::span{ buffer }, 0); + switch (buffer.Type) + { + case EBindType::RootConstants: + { + InContext->SetComputeRoot32BitConstants(rootParamIndex, std::span{ buffer.Buffer }, 0); + break; + } + case EBindType::RootDescriptor: + { + const auto cbv = InContext.CreateCBV(std::as_bytes( std::span{buffer.Buffer} )); + InContext.SetRootDescriptor(rootParamIndex, cbv); + break; + } + default: + { + std::unreachable(); + } + } } } diff --git a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp index 8104fd12..993cc4b7 100644 --- a/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp +++ b/src/Private/D3D12/Shader/ShaderReflectionUtils.cpp @@ -6,6 +6,14 @@ 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{}; @@ -54,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/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index 4f01fdbf..b9362956 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -147,7 +147,7 @@ namespace stf InContext.Section("Test Dispatch", [&](ScopedCommandContext& InContext) { - InContext->Dispatch(InTestDesc.DispatchConfig.x, InTestDesc.DispatchConfig.y, InTestDesc.DispatchConfig.z); + InContext.Dispatch(InTestDesc.DispatchConfig); } ); diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 99ad8d20..ab0f91bd 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -7,6 +7,7 @@ #include "D3D12/GPUResourceManager.h" #include "Utility/FunctionTraits.h" +#include "Utility/HLSLTypes.h" #include "Utility/Lambda.h" #include "Utility/Object.h" #include "Utility/Pointer.h" @@ -52,12 +53,12 @@ namespace stf { for (const auto& cb : m_ConstantBuffers) { - m_ResourceManager->Release(cb); + ThrowIfUnexpected(m_ResourceManager->Release(cb)); } for (const auto& cbv : m_CBVs) { - m_ResourceManager->Release(cbv); + ThrowIfUnexpected(m_ResourceManager->Release(cbv)); } } @@ -66,13 +67,18 @@ namespace stf const auto buffer = m_ResourceManager->Acquire(GPUResourceManager::ConstantBufferDesc{ .RequestedSize = static_cast(InData.size_bytes()) }); const auto cbv = m_ResourceManager->CreateCBV(buffer); - m_ResourceManager->UploadData(InData, buffer); + ThrowIfUnexpected(m_ResourceManager->UploadData(InData, buffer)); m_ConstantBuffers.push_back(buffer); m_CBVs.push_back(cbv); return cbv; } + + void SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) + { + ThrowIfUnexpected(m_ResourceManager->SetRootDescriptor(InList, InRootParamIndex, InHandle)); + } private: @@ -122,6 +128,16 @@ namespace stf return m_ResourceManager->CreateCBV(InData); } + void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) + { + m_ResourceManager->SetRootDescriptor(*m_List, InRootParamIndex, InHandle); + } + + void Dispatch(const uint3 InDispatchConfig) + { + m_List->Dispatch(InDispatchConfig.x, InDispatchConfig.y, InDispatchConfig.z); + } + private: SharedPtr m_List = nullptr; diff --git a/src/Public/D3D12/CommandList.h b/src/Public/D3D12/CommandList.h index f6eb4d51..dde7fe7c 100644 --- a/src/Public/D3D12/CommandList.h +++ b/src/Public/D3D12/CommandList.h @@ -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/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 6716156c..fd220070 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -78,17 +78,17 @@ namespace stf [[nodiscard]] ConstantBufferViewHandle CreateCBV(const ConstantBufferHandle InHandle); template - void UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) + ExpectedError UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) { - UploadData(std::as_bytes(std::span{ &InData }), InBufferHandle); + return UploadData(std::as_bytes(std::span{ &InData }), InBufferHandle); } - void UploadData(const std::span InBytes, const ConstantBufferHandle InBufferHandle); + ExpectedError UploadData(const std::span InBytes, const ConstantBufferHandle InBufferHandle); - void Release(ConstantBufferHandle InHandle); - void Release(ConstantBufferViewHandle InHandle); + ExpectedError Release(const ConstantBufferHandle InHandle); + ExpectedError Release(const ConstantBufferViewHandle InHandle); - void SetCBV(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); + ExpectedError SetRootDescriptor(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); private: diff --git a/src/Public/D3D12/Shader/ShaderBindingMap.h b/src/Public/D3D12/Shader/ShaderBindingMap.h index 63a05022..c0b6fd8b 100644 --- a/src/Public/D3D12/Shader/ShaderBindingMap.h +++ b/src/Public/D3D12/Shader/ShaderBindingMap.h @@ -22,6 +22,8 @@ namespace stf 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); } @@ -39,15 +41,29 @@ namespace stf private: + enum class EBindType + { + RootConstants, + RootDescriptor, + DescriptorTable + }; + struct BindingInfo { u32 RootParamIndex = 0; u32 OffsetIntoBuffer = 0; u32 BindingSize = 0; + EBindType Type = EBindType::RootConstants; + }; + + struct StagingInfo + { + std::vector Buffer; + EBindType Type = EBindType::RootConstants; }; using BindingMapType = std::unordered_map>; - using StagingBufferMap = std::unordered_map>; + using StagingBufferMap = std::unordered_map; ShaderBindingMap( SharedPtr&& InRootSignature, 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/test/CMakeLists.txt b/test/CMakeLists.txt index cef274ba..140218e0 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 diff --git a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp index f31555c7..5a760d11 100644 --- a/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp +++ b/test/Private/Framework/HLSLFramework/Binding/ValueBindingsTests.cpp @@ -9,7 +9,29 @@ #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; @@ -17,10 +39,21 @@ 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, ExpectedError> @@ -77,18 +110,14 @@ TEST_CASE_PERSISTENT_FIXTURE(ShaderTestFixtureBaseFixture, "HLSLFrameworkTests - }, 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{ Errors::RootSignatureDWORDLimitReached() } + true }, std::tuple { @@ -96,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{ Errors::ConstantBufferCantBeInRootConstants("$Globals")} + true }, std::tuple { @@ -120,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 } } ) @@ -130,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) @@ -146,9 +204,10 @@ 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 { 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/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 From 86ac1824caf85d880a0a50120068cc0786044c2e Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 10 Jan 2026 14:26:30 +0000 Subject: [PATCH 52/76] Implementing buffer creation through the command engine Also making CommandEngine expected friendly so that if errors are returned during commandlist creation, we can respond correctly --- src/Private/D3D12/GPUResourceManager.cpp | 109 ++++++++++++++++++- src/Private/Framework/ShaderTestDriver.cpp | 112 +++++++++---------- src/Public/D3D12/CommandEngine.h | 104 +++++++++++++++--- src/Public/D3D12/GPUResourceManager.h | 46 +++++++- test/Private/D3D12/CommandEngineTests.cpp | 119 +++++++++++---------- 5 files changed, 363 insertions(+), 127 deletions(-) diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index b18590e1..16a75ee8 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -53,7 +53,49 @@ namespace stf return m_CBVHandle; } - GPUResourceManager::ConstantBufferHandle GPUResourceManager::Acquire(const ConstantBufferDesc InDesc) + 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::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( @@ -70,6 +112,43 @@ namespace stf return ConstantBufferHandle{ Private{}, bufferHandle }; } + GPUResourceManager::BufferUAVHandle GPUResourceManager::CreateUAV(const BufferHandle InHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() + .or_else( + [&](const DescriptorManager::EErrorType InErrorType) -> DescriptorManager::Expected + { + switch (InErrorType) + { + case DescriptorManager::EErrorType::AllocatorFull: + { + auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); + ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); + return m_DescriptorManager->Acquire(); + } + default: + { + 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() @@ -135,11 +214,21 @@ namespace stf ); } + 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()); @@ -157,4 +246,22 @@ namespace stf } ); } + + 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/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index b9362956..a932dd20 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -118,20 +118,12 @@ namespace stf InTestDesc.Bindings ) .and_then( - [&, this]() + [&]() { - m_CommandEngine->Execute(InTestDesc.TestName, - [this, - &pipelineState, - &assertBuffer, - &allocationBuffer, - &readBackBuffer, - &readBackAllocationBuffer, - &InTestDesc - ] - (ScopedCommandContext& InContext) + return m_CommandEngine->Execute(InTestDesc.TestName, + [&](ScopedCommandContext& InContext) { - InContext.Section("Test Setup", + return InContext.Section("Test Setup", [&](ScopedCommandContext& InContext) { InContext->SetPipelineState(*pipelineState); @@ -141,62 +133,74 @@ namespace stf InContext->SetBufferUAV(*allocationBuffer); InTestDesc.Shader.CommitBindings(InContext); + return ExpectedError{}; } - ); - - InContext.Section("Test Dispatch", - [&](ScopedCommandContext& InContext) + ).and_then( + [&]() { - InContext.Dispatch(InTestDesc.DispatchConfig); + return InContext.Section("Test Dispatch", + [&](ScopedCommandContext& InContext) + { + InContext.Dispatch(InTestDesc.DispatchConfig); + return ExpectedError{}; + } + ); } - ); - - InContext.Section("Results readback", - [&](ScopedCommandContext& InContext) + ).and_then( + [&]() { - InContext->CopyBufferResource(*readBackBuffer, *assertBuffer); - InContext->CopyBufferResource(*readBackAllocationBuffer, *allocationBuffer); + return InContext.Section("Results readback", + [&](ScopedCommandContext& InContext) + { + InContext->CopyBufferResource(*readBackBuffer, *assertBuffer); + InContext->CopyBufferResource(*readBackAllocationBuffer, *allocationBuffer); + return ExpectedError{}; + } + ); } ); } ); + }) + .and_then( + [&]() + { + m_CommandEngine->Flush(); + m_DeferredDeletedDescriptorHeaps.clear(); - m_CommandEngine->Flush(); - m_DeferredDeletedDescriptorHeaps.clear(); + return m_DescriptorManager->ReleaseUAV(assertUAV) + .and_then( + [this, &allocationUAV]() + { + return m_DescriptorManager->ReleaseUAV(allocationUAV); + } + ) + .transform( + [this, &readBackAllocationBuffer, &readBackBuffer, &InTestDesc]() + { + return ReadbackResults(*readBackAllocationBuffer, *readBackBuffer, InTestDesc.Shader.GetThreadGroupSize() * InTestDesc.DispatchConfig, InTestDesc.TestBufferLayout); + } + ) + .transform_error( + [](const ShaderTestDescriptorManager::EErrorType InErrorType) -> Error + { + using enum ShaderTestDescriptorManager::EErrorType; - return m_DescriptorManager->ReleaseUAV(assertUAV) - .and_then( - [this, &allocationUAV]() + switch (InErrorType) { - return m_DescriptorManager->ReleaseUAV(allocationUAV); - } - ) - .transform( - [this, &readBackAllocationBuffer, &readBackBuffer, &InTestDesc]() - { - return ReadbackResults(*readBackAllocationBuffer, *readBackBuffer, InTestDesc.Shader.GetThreadGroupSize() * InTestDesc.DispatchConfig, InTestDesc.TestBufferLayout); - } - ) - .transform_error( - [](const ShaderTestDescriptorManager::EErrorType InErrorType) - { - using enum ShaderTestDescriptorManager::EErrorType; - - switch (InErrorType) + case DescriptorAlreadyFree: + { + return Error::FromFragment<"Attempted to free an already freed descriptor.">(); + } + default: { - case DescriptorAlreadyFree: - { - return Error::FromFragment<"Attempted to free an already freed descriptor.">(); - } - default: - { - return Error::FromFragment<"Unknown descriptor management error.">(); - } + return Error::FromFragment<"Unknown descriptor management error.">(); } } - ); - } - ); + } + ); + } + ); } SharedPtr ShaderTestDriver::CreatePipelineState(const RootSignature& InRootSig, IDxcBlob* InShader) const diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index ab0f91bd..d408b9cd 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -5,6 +5,7 @@ #include "D3D12/CommandQueue.h" #include "D3D12/GPUDevice.h" #include "D3D12/GPUResourceManager.h" +#include "Utility/Error.h" #include "Utility/FunctionTraits.h" #include "Utility/HLSLTypes.h" @@ -27,17 +28,19 @@ namespace stf class ScopedCommandContext; 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 && TFuncTraits::ParamTypes::Size == 1 && - std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&>; + std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && + std::is_same_v::ReturnType, ExpectedError>; class ScopedGPUResourceManager @@ -60,6 +63,16 @@ namespace stf { 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)); + } } [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData) @@ -75,16 +88,38 @@ namespace stf return cbv; } + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) + { + return m_ResourceManager->Acquire(InBufferDesc); + } + + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + } + + void SetUAV(CommandList& InList, const GPUResourceManager::BufferUAVHandle InHandle) + { + ThrowIfUnexpected(m_ResourceManager->SetUAV(InList, InHandle)); + } + void SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) { ThrowIfUnexpected(m_ResourceManager->SetRootDescriptor(InList, InRootParamIndex, InHandle)); } + + void SetDescriptorHeap(CommandList& InList) + { + m_ResourceManager->SetDescriptorHeap(InList); + } private: SharedPtr m_ResourceManager; std::vector m_ConstantBuffers; std::vector m_CBVs; + std::vector m_Buffers; + std::vector m_BufferUAVs; }; class ScopedCommandContext @@ -111,10 +146,10 @@ namespace stf } template - void Section(const std::string_view InName, InLambdaType&& InFunc) + ExpectedError 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 @@ -122,6 +157,20 @@ namespace stf return m_List.get(); } + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) + { + return m_ResourceManager->CreateBuffer(InDesc); + } + + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + } + + void SetUAV(const GPUResourceManager::BufferUAVHandle InHandle) + { + m_ResourceManager->SetUAV(*m_List, InHandle); + } [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData) { @@ -157,7 +206,7 @@ namespace stf CommandEngine(ObjectToken, const CreationParams& InParams); template - void Execute(const InLambdaType& InFunc) + ExpectedError Execute(const InLambdaType& InFunc) { auto allocator = [this]() { @@ -177,14 +226,26 @@ namespace stf ScopedCommandContext context(CommandEngineToken{}, m_List , m_ResourceManager ); - InFunc(context); - - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - m_Queue->ExecuteCommandList(*m_List); + return InFunc(context) + .and_then( + [&]() -> ExpectedError + { + m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); + m_Queue->ExecuteCommandList(*m_List); + return {}; + } + ) + .or_else( + [&](Error&& InError) -> ExpectedError + { + m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); + return Unexpected{ InError }; + } + ); } template - void Execute(InLambdaType&& InFunc) + ExpectedError Execute(InLambdaType&& InFunc) { auto allocator = [this]() { @@ -204,17 +265,30 @@ namespace stf ScopedCommandContext context(CommandEngineToken{}, m_List , m_ResourceManager ); - InFunc(context); - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - m_Queue->ExecuteCommandList(*m_List); + return InFunc(context) + .and_then( + [&]() -> ExpectedError + { + m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); + m_Queue->ExecuteCommandList(*m_List); + return {}; + } + ) + .or_else( + [&](Error&& InError) -> ExpectedError + { + m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); + return Unexpected{ InError }; + } + ); } template - void Execute(const std::string_view InName, InLambdaType&& InFunc) + ExpectedError 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)); } void Flush(); diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index fd220070..289765c3 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -72,9 +72,47 @@ namespace stf DescriptorOpaqueHandle m_CBVHandle; }; + struct BufferDesc + { + std::string_view Name = "DefaultConstantBuffer"; + 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; + }; + GPUResourceManager(ObjectToken InToken, const CreationParams& InParams); - [[nodiscard]] ConstantBufferHandle Acquire(const ConstantBufferDesc InDesc); + [[nodiscard]] BufferHandle Acquire(const BufferDesc& InDesc); + [[nodiscard]] ConstantBufferHandle Acquire(const ConstantBufferDesc& InDesc); + + [[nodiscard]] BufferUAVHandle CreateUAV(const BufferHandle InHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); [[nodiscard]] ConstantBufferViewHandle CreateCBV(const ConstantBufferHandle InHandle); template @@ -85,11 +123,17 @@ namespace stf 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 SetRootDescriptor(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); + ExpectedError SetUAV(CommandList& InCommandList, const BufferUAVHandle InHandle); + + void SetDescriptorHeap(CommandList& InCommandList); + private: SharedPtr m_Device; 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 From 208adf6c90902ea2d9ba64835790e59637c8a72c Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 10 Jan 2026 15:38:08 +0000 Subject: [PATCH 53/76] Remove dependency on command engine from Shader. Command Engine is higher abstraction than Shader. Shader should never have depended on it --- src/Private/D3D12/Shader/Shader.cpp | 5 -- src/Private/D3D12/Shader/ShaderBindingMap.cpp | 3 +- src/Private/Framework/ShaderTestShader.cpp | 25 +++++++++- src/Public/D3D12/Shader/Shader.h | 13 ++++-- src/Public/D3D12/Shader/ShaderBindingMap.h | 46 ++++++++++++------- 5 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/Private/D3D12/Shader/Shader.cpp b/src/Private/D3D12/Shader/Shader.cpp index d51880d5..c59ff855 100644 --- a/src/Private/D3D12/Shader/Shader.cpp +++ b/src/Private/D3D12/Shader/Shader.cpp @@ -49,11 +49,6 @@ namespace stf return m_BindingMap.StageBindingData(InBinding); } - void Shader::CommitBindings(ScopedCommandContext& InContext) const - { - m_BindingMap.CommitBindings(InContext); - } - uint3 Shader::GetThreadGroupSize() const { uint3 ret; diff --git a/src/Private/D3D12/Shader/ShaderBindingMap.cpp b/src/Private/D3D12/Shader/ShaderBindingMap.cpp index 5c3e19e6..926d0fdc 100644 --- a/src/Private/D3D12/Shader/ShaderBindingMap.cpp +++ b/src/Private/D3D12/Shader/ShaderBindingMap.cpp @@ -204,6 +204,7 @@ namespace stf return {}; } + /* void ShaderBindingMap::CommitBindings(ScopedCommandContext& InContext) const { for (const auto& [rootParamIndex, buffer] : m_RootParamBuffers) @@ -228,7 +229,7 @@ namespace stf } } } - + */ ShaderBindingMap::ShaderBindingMap( SharedPtr&& InRootSignature, BindingMapType&& InNameToBindingsMap, diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp index e28b458b..c1b1aa8a 100644 --- a/src/Private/Framework/ShaderTestShader.cpp +++ b/src/Private/Framework/ShaderTestShader.cpp @@ -1,6 +1,5 @@ #include "Framework/ShaderTestShader.h" -#include "D3D12/Shader/ShaderReflectionUtils.h" namespace stf { @@ -32,7 +31,29 @@ namespace stf void ShaderTestShader::CommitBindings(ScopedCommandContext& InCommandContext) const { - m_Shader->CommitBindings(InCommandContext); + m_Shader->ForEachStagingBuffer( + [&](const u32 InRootParamIndex, const ShaderBindingMap::StagingInfo& InStagingInfo) + { + switch (InStagingInfo.Type) + { + case ShaderBindingMap::EBindType::RootConstants: + { + InCommandContext->SetComputeRoot32BitConstants(InRootParamIndex, std::span{ InStagingInfo.Buffer }, 0); + break; + } + case ShaderBindingMap::EBindType::RootDescriptor: + { + const auto cbv = InCommandContext.CreateCBV(std::as_bytes(std::span{ InStagingInfo.Buffer })); + InCommandContext.SetRootDescriptor(InRootParamIndex, cbv); + break; + } + default: + { + std::unreachable(); + } + } + } + ); } uint3 ShaderTestShader::GetThreadGroupSize() const diff --git a/src/Public/D3D12/Shader/Shader.h b/src/Public/D3D12/Shader/Shader.h index 6147b3b9..fd46783d 100644 --- a/src/Public/D3D12/Shader/Shader.h +++ b/src/Public/D3D12/Shader/Shader.h @@ -1,7 +1,5 @@ #pragma once - -#include "D3D12/CommandEngine.h" #include "D3D12/GPUDevice.h" #include "D3D12/Shader/CompiledShaderData.h" #include "D3D12/Shader/RootSignature.h" @@ -39,7 +37,16 @@ namespace stf static ExpectedError> Make(const CompiledShaderData& InShaderData, GPUDevice& InDevice); ExpectedError StageBindingData(const ShaderBinding& InBindings); - void CommitBindings(ScopedCommandContext& InContext) const; + + 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; diff --git a/src/Public/D3D12/Shader/ShaderBindingMap.h b/src/Public/D3D12/Shader/ShaderBindingMap.h index c0b6fd8b..7de4590b 100644 --- a/src/Public/D3D12/Shader/ShaderBindingMap.h +++ b/src/Public/D3D12/Shader/ShaderBindingMap.h @@ -1,6 +1,4 @@ #pragma once - -#include "D3D12/CommandEngine.h" #include "D3D12/GPUDevice.h" #include "D3D12/Shader/RootSignature.h" #include "D3D12/Shader/ShaderBinding.h" @@ -32,21 +30,42 @@ namespace stf { 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); - void CommitBindings(ScopedCommandContext& InContext) const; - private: - - enum class EBindType + template + requires requires(T InFunc, u32 InRootParamIndex, StagingInfo InStagingInfo) + { + { InFunc(InRootParamIndex, InStagingInfo) } -> std::same_as; + } + void ForEachStagingBuffer(T&& InFunc) { - RootConstants, - RootDescriptor, - DescriptorTable - }; + for (const auto& [rootParamIndex, stagingInfo] : m_RootParamBuffers) + { + InFunc(rootParamIndex, stagingInfo); + } + } + + private: struct BindingInfo { @@ -56,14 +75,7 @@ namespace stf EBindType Type = EBindType::RootConstants; }; - struct StagingInfo - { - std::vector Buffer; - EBindType Type = EBindType::RootConstants; - }; - using BindingMapType = std::unordered_map>; - using StagingBufferMap = std::unordered_map; ShaderBindingMap( SharedPtr&& InRootSignature, From c73ded19866811a6a95f72f723f00ba249632b3f Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 10 Jan 2026 18:35:45 +0000 Subject: [PATCH 54/76] Allowing more appending of errors --- src/Public/Utility/Error.h | 58 ++++++++++++++++++++++++++++- test/Private/Utility/ErrorTests.cpp | 48 ++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index ebad3f18..b1071ae3 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace stf @@ -128,6 +129,16 @@ namespace stf }); } + 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 { @@ -151,16 +162,61 @@ namespace stf 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; } @@ -186,4 +242,4 @@ namespace stf std::print(InOutStream, "{}", InError); return InOutStream; } -} \ No newline at end of file +} diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp index bfaa1af7..33bca3af 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -209,12 +209,12 @@ SCENARIO("Error Tests") 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()))); @@ -239,7 +239,7 @@ SCENARIO("Error Tests") stringBuffer << error; return stringBuffer.str(); }(); - + REQUIRE(errorMessage == streamError); const auto error1Range = std::ranges::search(errorMessage, errorFrag1.Error()); @@ -265,6 +265,48 @@ SCENARIO("Error Tests") } } + 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())); + + 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())); + } + } + } + } +} + +SCENARIO("Error comparison tests") +{ + using namespace stf; const auto [given, left, right, expected] = GENERATE( table ( From dfae8cfff973235bf7c996cdaa3ec2fbbba661e3 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 10 Jan 2026 18:37:15 +0000 Subject: [PATCH 55/76] Converting descriptor allocators and managers to use ExpectedError --- .../D3D12/BindlessFreeListAllocator.cpp | 65 +++++++++------ src/Private/D3D12/DescriptorManager.cpp | 82 ++++++++++--------- src/Private/D3D12/GPUResourceManager.cpp | 55 +++++++------ .../Framework/ShaderTestDescriptorManager.cpp | 25 +----- src/Public/D3D12/BindlessFreeListAllocator.h | 32 ++++---- src/Public/D3D12/DescriptorManager.h | 25 ++++-- src/Public/D3D12/GPUResourceManager.h | 2 + .../D3D12/BindlessFreeListAllocatorTests.cpp | 10 +-- test/Private/D3D12/DescriptorManagerTests.cpp | 6 +- 9 files changed, 159 insertions(+), 143 deletions(-) diff --git a/src/Private/D3D12/BindlessFreeListAllocator.cpp b/src/Private/D3D12/BindlessFreeListAllocator.cpp index d705ecfa..ae4e877e 100644 --- a/src/Private/D3D12/BindlessFreeListAllocator.cpp +++ b/src/Private/D3D12/BindlessFreeListAllocator.cpp @@ -5,6 +5,34 @@ namespace stf { + namespace Errors + { + ErrorFragment BindlessFreeListAllocatorIsEmpty() + { + return ErrorFragment::Make<"Bindless allocator is empty">(); + } + + ErrorFragment UnknownBindlessFreeListAllocatorError() + { + return ErrorFragment::Make<"Unknown bindless allocator error">(); + } + + ErrorFragment InvalidBindlessIndex(const u32 InIndex) + { + return ErrorFragment::Make<"Bindless index {} is invalid">(InIndex); + } + + ErrorFragment BindlessIndexAlreadyReleased(const u32 InIndex) + { + return ErrorFragment::Make<"Bindless index {} has already been released">(InIndex); + } + + ErrorFragment ShrinkAttemptedOnBindlessAllocator(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 +41,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 +51,35 @@ namespace stf return BindlessIndex{ Private{}, InIndex }; }) .transform_error( - [](const EBufferError InError) + [](const EBufferError InError) -> Error { switch (InError) { case EBufferError::EmptyBuffer: { - return EErrorType::EmptyError; + return Error{ Errors::BindlessFreeListAllocatorIsEmpty() }; } default: { - return EErrorType::UnknownError; + return Error{ Errors::UnknownBindlessFreeListAllocatorError() }; } } } ); } - 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::InvalidBindlessIndex(index) } }; } if (m_FreeSet[index]) { - return Unexpected(EErrorType::IndexAlreadyReleased); + return Unexpected{ Error{ Errors::BindlessIndexAlreadyReleased(index) } }; } m_FreeList.push_back(index); @@ -60,11 +88,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::ShrinkAttemptedOnBindlessAllocator(m_NumDescriptors, InNewSize) } }; } if (InNewSize == m_NumDescriptors) @@ -84,20 +112,9 @@ namespace stf m_NumDescriptors = InNewSize; } ).transform_error( - [](const stf::RingBuffer::EErrorType InError) + [](const stf::RingBuffer::EErrorType) { - using enum stf::RingBuffer::EErrorType; - switch (InError) - { - case AttemptedShrink: - { - return EErrorType::ShrinkAttempted; - } - default: - { - return EErrorType::UnknownError; - } - } + return Error{ Errors::UnknownBindlessFreeListAllocatorError() }; } ); } @@ -127,12 +144,12 @@ namespace stf return m_Index; } - BindlessFreeListAllocator::Expected BindlessFreeListAllocator::IsAllocated(const BindlessIndex InIndex) const + ExpectedError BindlessFreeListAllocator::IsAllocated(const BindlessIndex InIndex) const { const u32 index = InIndex; if (index >= m_NumDescriptors) { - return Unexpected(EErrorType::InvalidIndex); + return Unexpected{ Error{ Errors::InvalidBindlessIndex(InIndex) } }; } return !m_FreeSet[InIndex]; diff --git a/src/Private/D3D12/DescriptorManager.cpp b/src/Private/D3D12/DescriptorManager.cpp index ff16f9bc..58a866b9 100644 --- a/src/Private/D3D12/DescriptorManager.cpp +++ b/src/Private/D3D12/DescriptorManager.cpp @@ -3,13 +3,41 @@ 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) { } - DescriptorManager::Expected DescriptorManager::Descriptor::Resolve() const + ExpectedError DescriptorManager::Descriptor::Resolve() const { return m_Owner->ResolveDescriptor(m_Index); } @@ -47,7 +75,7 @@ namespace stf { } - DescriptorManager::Expected DescriptorManager::Acquire() + ExpectedError DescriptorManager::Acquire() { return m_Allocator.Allocate() .transform( @@ -55,33 +83,23 @@ namespace stf { return Descriptor{ Token{}, SharedFromThis(), InHandle }; }) - .transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) + .or_else( + [](Error&& InError) -> ExpectedError { - ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::EmptyError); - - return EErrorType::AllocatorFull; + return Unexpected{ InError + Errors::DescriptorManagerIsFull() }; }); } - DescriptorManager::Expected DescriptorManager::Release(const Descriptor& InDescriptor) + ExpectedError DescriptorManager::Release(const Descriptor& InDescriptor) { - return - m_Allocator.Release(InDescriptor.GetIndex(Token{})) - .transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) - { - ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); - return EErrorType::DescriptorAlreadyFree; - } - ); + return m_Allocator.Release(InDescriptor.GetIndex(Token{})); } - DescriptorManager::Expected> DescriptorManager::Resize(const u32 InNewSize) + ExpectedError> DescriptorManager::Resize(const u32 InNewSize) { if (m_Allocator.GetCapacity() >= InNewSize) { - return Unexpected(EErrorType::AttemptedShrink); + return Unexpected{ Error{ Errors::ShrinkAttemptedOnDescriptorManager(m_Allocator.GetCapacity(), InNewSize) } }; } auto copyDescriptorsToNewHeap = @@ -111,12 +129,6 @@ namespace stf { return std::move(oldHeap); } - ).transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) - { - ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::ShrinkAttempted); - return EErrorType::AttemptedShrink; - } ); } @@ -136,20 +148,12 @@ namespace stf InCommandList.SetDescriptorHeaps(*m_GPUHeap); } - DescriptorManager::Expected DescriptorManager::ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const + ExpectedError DescriptorManager::ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const { return m_Allocator .IsAllocated(InIndex) - .transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) - { - ThrowIfFalse(InError == BindlessFreeListAllocator::EErrorType::InvalidIndex); - - return EErrorType::DescriptorInvalid; - } - ) .and_then( - [this, InIndex](const bool InIsAllocated) -> Expected + [this, InIndex](const bool InIsAllocated) -> ExpectedError { if (InIsAllocated) { @@ -157,17 +161,15 @@ namespace stf return descriptorRange[InIndex.GetIndex()] .transform_error( - [](const DescriptorRange::EErrorType InError) + [&](const DescriptorRange::EErrorType) { - ThrowIfFalse(InError == DescriptorRange::EErrorType::InvalidIndex); - - return EErrorType::DescriptorInvalid; + return Error{ Errors::InvalidDescriptorManagerDescriptor(InIndex.GetIndex()) }; } ); } else { - return Unexpected{ EErrorType::DescriptorNotAllocated }; + return Unexpected{ Error{ Errors::DescriptorManagerDescriptorNotAllocated(InIndex.GetIndex()) } }; } } ); diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index 16a75ee8..41e4029d 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -116,21 +116,16 @@ namespace stf { auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() .or_else( - [&](const DescriptorManager::EErrorType InErrorType) -> DescriptorManager::Expected + [&](const Error& InErrorType) -> ExpectedError { - switch (InErrorType) + if (InErrorType.HasFragment(Errors::DescriptorManagerIsFull())) { - case DescriptorManager::EErrorType::AllocatorFull: - { - auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); - ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); - return m_DescriptorManager->Acquire(); - } - default: - { - return Unexpected{ InErrorType }; - } + 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 }; } )); @@ -153,21 +148,16 @@ namespace stf { auto descriptor = ThrowIfUnexpected(m_DescriptorManager->Acquire() .or_else( - [&](const DescriptorManager::EErrorType InErrorType) -> DescriptorManager::Expected + [&](const Error& InErrorType) -> ExpectedError { - switch (InErrorType) + if (InErrorType.HasFragment(Errors::DescriptorManagerIsFull())) { - case DescriptorManager::EErrorType::AllocatorFull: - { - auto oldHeap = ThrowIfUnexpected(m_DescriptorManager->Resize(m_DescriptorManager->GetCapacity() * 2)); - ThrowIfUnexpected(m_HeapReleaseManager.Release(m_HeapReleaseManager.Manage(std::move(oldHeap)))); - return m_DescriptorManager->Acquire(); - } - default: - { - return Unexpected{ InErrorType }; - } + 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 }; } )); @@ -186,6 +176,23 @@ namespace stf 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()) diff --git a/src/Private/Framework/ShaderTestDescriptorManager.cpp b/src/Private/Framework/ShaderTestDescriptorManager.cpp index d7a4b723..d7cd9e2d 100644 --- a/src/Private/Framework/ShaderTestDescriptorManager.cpp +++ b/src/Private/Framework/ShaderTestDescriptorManager.cpp @@ -32,7 +32,6 @@ namespace stf 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 @@ -46,23 +45,19 @@ namespace stf }; }) .transform_error( - [](const ErrorType InError) + [](const Error&) { - 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) + [](const Error&) { - ThrowIfFalse(InError == ErrorType::IndexAlreadyReleased); return EErrorType::DescriptorAlreadyFree; } ); @@ -103,21 +98,9 @@ namespace stf return oldGPUHeap; } ).transform_error( - [](const BindlessFreeListAllocator::EErrorType InError) + [](const Error&) { - using enum BindlessFreeListAllocator::EErrorType; - - switch (InError) - { - case ShrinkAttempted: - { - return EErrorType::AttemptedShrink; - } - default: - { - return EErrorType::Unknown; - } - } + return EErrorType::AttemptedShrink; } ); } diff --git a/src/Public/D3D12/BindlessFreeListAllocator.h b/src/Public/D3D12/BindlessFreeListAllocator.h index 5cbb65ef..c01e736c 100644 --- a/src/Public/D3D12/BindlessFreeListAllocator.h +++ b/src/Public/D3D12/BindlessFreeListAllocator.h @@ -3,30 +3,28 @@ #include "Platform.h" #include "Container/RingBuffer.h" -#include "Utility/Expected.h" +#include "Utility/Error.h" #include #include namespace stf { + namespace Errors + { + ErrorFragment BindlessFreeListAllocatorIsEmpty(); + ErrorFragment UnknownBindlessFreeListAllocatorError(); + + ErrorFragment InvalidBindlessIndex(const u32 InIndex); + ErrorFragment BindlessIndexAlreadyReleased(const u32 InIndex); + ErrorFragment ShrinkAttemptedOnBindlessAllocator(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,14 +49,14 @@ 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; - Expected IsAllocated(const BindlessIndex InIndex) const; + ExpectedError IsAllocated(const BindlessIndex InIndex) const; private: diff --git a/src/Public/D3D12/DescriptorManager.h b/src/Public/D3D12/DescriptorManager.h index 957f53ff..607b0131 100644 --- a/src/Public/D3D12/DescriptorManager.h +++ b/src/Public/D3D12/DescriptorManager.h @@ -7,12 +7,22 @@ #include "D3D12/CommandList.h" #include "D3D12/GPUDevice.h" -#include "Utility/Expected.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 { @@ -37,16 +47,13 @@ namespace stf DescriptorInvalid }; - template - using Expected = Expected; - class Descriptor { public: Descriptor(Token, const SharedPtr& InOwner, BindlessFreeListAllocator::BindlessIndex InIndex); - Expected Resolve() const; + ExpectedError Resolve() const; DescriptorManager* GetOwner(Token) const; @@ -61,17 +68,17 @@ namespace stf DescriptorManager(ObjectToken, const CreationParams& InParams); - Expected Acquire(); - Expected Release(const Descriptor& InDescriptor); + ExpectedError Acquire(); + ExpectedError Release(const Descriptor& InDescriptor); - Expected> Resize(const u32 InNewSize); + ExpectedError> Resize(const u32 InNewSize); u32 GetSize() const; u32 GetCapacity() const; void SetDescriptorHeap(CommandList& InCommandList); - Expected ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const; + ExpectedError ResolveDescriptor(const BindlessFreeListAllocator::BindlessIndex InIndex) const; private: diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 289765c3..7f337e64 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -114,6 +114,8 @@ namespace stf [[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) diff --git a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp index 9d19d89b..bc32a6f5 100644 --- a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp +++ b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp @@ -29,8 +29,8 @@ SCENARIO("BindlessFreeListAllocatorTests") const auto allocation = allocator.Allocate(); THEN("return expected error") { - REQUIRE(!allocation); - REQUIRE(allocation.error() == BindlessFreeListAllocator::EErrorType::EmptyError); + REQUIRE_FALSE(allocation); + REQUIRE(allocation.error().HasFragment(Errors::BindlessFreeListAllocatorIsEmpty())); } } } @@ -80,7 +80,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("release failed") { REQUIRE_FALSE(releaseResult.has_value()); - REQUIRE(releaseResult.error() == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); + REQUIRE(releaseResult.error().HasFragment(Errors::BindlessIndexAlreadyReleased(invalidAllocation.value().GetIndex()))); } } } @@ -109,7 +109,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("release failed") { REQUIRE_FALSE(finalReleaseOnInitialAllocatorResult.has_value()); - REQUIRE(finalReleaseOnInitialAllocatorResult.error() == BindlessFreeListAllocator::EErrorType::InvalidIndex); + REQUIRE(finalReleaseOnInitialAllocatorResult.error().HasFragment(Errors::InvalidBindlessIndex(finalAllocation.value()))); } } @@ -155,7 +155,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("Release fails") { REQUIRE_FALSE(secondReleaseResult.has_value()); - REQUIRE(secondReleaseResult.error() == BindlessFreeListAllocator::EErrorType::IndexAlreadyReleased); + REQUIRE(secondReleaseResult.error().HasFragment(Errors::BindlessIndexAlreadyReleased(bindlessIndex1.value()))); REQUIRE(initialCapacity == allocator.GetCapacity()); REQUIRE(0 == allocator.GetSize()); } diff --git a/test/Private/D3D12/DescriptorManagerTests.cpp b/test/Private/D3D12/DescriptorManagerTests.cpp index 00840c21..0dbf54ee 100644 --- a/test/Private/D3D12/DescriptorManagerTests.cpp +++ b/test/Private/D3D12/DescriptorManagerTests.cpp @@ -81,7 +81,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor THEN("Fails") { REQUIRE_FALSE(secondReleaseResult.has_value()); - REQUIRE(secondReleaseResult.error() == DescriptorManager::EErrorType::DescriptorAlreadyFree); + REQUIRE(secondReleaseResult.error().HasFragmentWithFormat(Errors::BindlessIndexAlreadyReleased(0u).Format())); REQUIRE(1 == manager->GetCapacity()); REQUIRE(0 == manager->GetSize()); } @@ -95,7 +95,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor THEN("Fails") { REQUIRE_FALSE(secondAllocationResult.has_value()); - REQUIRE(secondAllocationResult.error() == DescriptorManager::EErrorType::AllocatorFull); + REQUIRE(secondAllocationResult.error().HasFragment(Errors::DescriptorManagerIsFull())); REQUIRE(1 == manager->GetCapacity()); REQUIRE(1 == manager->GetSize()); } @@ -189,7 +189,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor THEN("Fails") { REQUIRE_FALSE(allocationResult.has_value()); - REQUIRE(allocationResult.error() == DescriptorManager::EErrorType::AllocatorFull); + REQUIRE(allocationResult.error().HasFragment(Errors::DescriptorManagerIsFull())); REQUIRE(4 == manager->GetCapacity()); REQUIRE(4 == manager->GetSize()); } From 5d3cc52ed5d8f2955266f3f4a2a9b6ded705b13a Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 10 Jan 2026 18:43:33 +0000 Subject: [PATCH 56/76] Fix expensive copy warnings --- test/Private/D3D12/BindlessFreeListAllocatorTests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp index bc32a6f5..0da7a037 100644 --- a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp +++ b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp @@ -174,7 +174,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("State is as expected") { - for (const auto allocation : allocations) + for (const auto& allocation : allocations) { REQUIRE(allocation.has_value()); } @@ -234,14 +234,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 +275,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("allocation is unique") { - for (const auto oldAllocation : allocations) + for (const auto& oldAllocation : allocations) { REQUIRE(allocation != oldAllocation); } From 68966e87f4c5594b0b5cd40b3683bcce67e6cb68 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 14:11:29 +0000 Subject: [PATCH 57/76] Moving towards supporting binding shaders in the command engine context. Not quite done yet --- src/Private/D3D12/CommandEngine.cpp | 211 +++++++++++++++++++++ src/Private/D3D12/GPUResourceManager.cpp | 67 +++++++ src/Private/Framework/ShaderTestDriver.cpp | 3 +- src/Public/D3D12/CommandEngine.h | 178 ++++++++--------- src/Public/D3D12/GPUResourceManager.h | 39 +++- 5 files changed, 395 insertions(+), 103 deletions(-) diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 67e1861f..0a28deae 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -2,6 +2,217 @@ namespace stf { + + 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)); + } + } + + [[nodiscard]] 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; + } + + [[nodiscard]] GPUResourceManager::BufferHandle ScopedGPUResourceManager::CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) + { + return m_ResourceManager->Acquire(InBufferDesc); + } + + [[nodiscard]] GPUResourceManager::BufferUAVHandle ScopedGPUResourceManager::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + } + + 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) + : m_Shader(InShader) + { + } + + ExpectedError ScopedCommandShader::StageBindingData(const ShaderBinding& InBinding) + { + return m_Shader->StageBindingData(InBinding); + } + + void ScopedCommandShader::StageBindlessResource(ScopedCommandContext& InContext, std::string InName, const GPUResourceManager::BufferUAVHandle InHandle) + { + InContext.StageBindlessResource(CommandShaderToken{}, std::move(InName), InHandle); + } + + ScopedCommandContext::ScopedCommandContext(CommandEngineToken, + const SharedPtr& InList, + const SharedPtr& InResourceManager + ) + : m_List(InList) + , m_ResourceManager(MakeUnique(InResourceManager)) + { + } + + CommandList* ScopedCommandContext::operator->() const + { + return GetList(); + } + + CommandList& ScopedCommandContext::operator*() const + { + return *GetList(); + } + + CommandList* ScopedCommandContext::GetList() const + { + return m_List.get(); + } + + [[nodiscard]] GPUResourceManager::BufferHandle ScopedCommandContext::CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) + { + return m_ResourceManager->CreateBuffer(InDesc); + } + + [[nodiscard]] GPUResourceManager::BufferUAVHandle ScopedCommandContext::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + { + return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); + } + + void ScopedCommandContext::SetUAV(const GPUResourceManager::BufferUAVHandle InHandle) + { + m_ResourceManager->SetUAV(*m_List, InHandle); + } + + [[nodiscard]] 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::StageBindlessResource(CommandShaderToken, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle) + { + SetUAV(InHandle); + + m_BindlessResourcesToResolve.emplace_back( + StagedBindlessResource + { + .Name = std::move(InBindingName), + .Descriptor = InHandle.GetUAVHandle() + }); + } + + ExpectedError ScopedCommandContext::Dispatch(const uint3 InDispatchConfig) + { + m_ResourceManager->SetDescriptorHeap(*m_List); + return ResolveAndStageBindlessResources() + .and_then( + [&]() -> ExpectedError + { + m_BoundShader->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); + return {}; + } + ); + } + + ExpectedError ScopedCommandContext::ResolveAndStageBindlessResources() + { + if (!m_BindlessResourcesToResolve.empty() && !m_BoundShader) + { + return Unexpected{ Error::FromFragment<"THIS SHOULD NOT HAPPEN: There are bindless resources to resolve, with no bound shader">()}; + } + + for (const auto& [bindingName, descriptor] : m_BindlessResourcesToResolve) + { + const auto stageResult = m_ResourceManager->GetDescriptorIndex(descriptor) + .and_then( + [&](const u32 InIndex) + { + return m_BoundShader->StageBindingData(ShaderBinding{ bindingName, InIndex }); + } + ); + + if (!stageResult) + { + return stageResult; + } + } + + return {}; + } + CommandEngine::CommandEngine(ObjectToken InToken, const CreationParams& InParams) : Object(InToken) , m_Device(InParams.Device) diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index 41e4029d..f6358e73 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -79,6 +79,32 @@ namespace stf 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 ReadbackBufferHandle InHandle) + : m_Handle(InHandle) + { + } + + GPUResourceManager::ReadbackBufferHandle GPUResourceManager::ReadbackResultHandle::GetReadbackHandle() const + { + return m_Handle; + } + GPUResourceManager::BufferHandle GPUResourceManager::Acquire(const BufferDesc& InDesc) { const auto bufferHandle = m_Resources.Manage( @@ -112,6 +138,28 @@ namespace stf 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 = InDesc.Name + } + ) + ); + + 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() @@ -241,6 +289,25 @@ namespace stf 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); + + return ReadbackResultHandle{ Private{}, InHandle }; + } + ); + } + ); + } + ExpectedError GPUResourceManager::SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const ConstantBufferViewHandle InHandle) { return m_Resources.Get(InHandle.GetBufferHandle()) diff --git a/src/Private/Framework/ShaderTestDriver.cpp b/src/Private/Framework/ShaderTestDriver.cpp index a932dd20..9b8432fc 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -141,8 +141,7 @@ namespace stf return InContext.Section("Test Dispatch", [&](ScopedCommandContext& InContext) { - InContext.Dispatch(InTestDesc.DispatchConfig); - return ExpectedError{}; + return InContext.Dispatch(InTestDesc.DispatchConfig); } ); } diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index d408b9cd..55aaf990 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -5,6 +5,7 @@ #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" @@ -20,12 +21,12 @@ namespace stf class CommandEngineToken { friend class CommandEngine; - friend class ScopedCommandContext; CommandEngineToken() = default; }; class CommandEngine; class ScopedCommandContext; + class ScopedCommandShader; template concept CommandEngineFuncType = LambdaType && requires @@ -42,76 +43,34 @@ namespace stf std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && std::is_same_v::ReturnType, ExpectedError>; + template + concept BindShaderLambdaType = !LambdaType && + TFuncTraits::ParamTypes::Size == 2 && + std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && + std::is_same_v::ParamTypes::template Type<1>, ScopedCommandShader&> && + std::is_same_v::ReturnType, ExpectedError>; class ScopedGPUResourceManager { public: - ScopedGPUResourceManager(const SharedPtr& InResourceManager) - : m_ResourceManager(InResourceManager) - { - } - - ~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)); - } - } - - [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle 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)); + ScopedGPUResourceManager(const SharedPtr& InResourceManager); - m_ConstantBuffers.push_back(buffer); - m_CBVs.push_back(cbv); + ~ScopedGPUResourceManager() noexcept; - return cbv; - } + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData); - [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) - { - return m_ResourceManager->Acquire(InBufferDesc); - } + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc); - [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) - { - return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); - } + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); - void SetUAV(CommandList& InList, const GPUResourceManager::BufferUAVHandle InHandle) - { - ThrowIfUnexpected(m_ResourceManager->SetUAV(InList, InHandle)); - } + void SetUAV(CommandList& InList, const GPUResourceManager::BufferUAVHandle InHandle); - void SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) - { - ThrowIfUnexpected(m_ResourceManager->SetRootDescriptor(InList, InRootParamIndex, InHandle)); - } + void SetRootDescriptor(CommandList& InList, const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); - void SetDescriptorHeap(CommandList& InList) - { - m_ResourceManager->SetDescriptorHeap(InList); - } + void SetDescriptorHeap(CommandList& InList); + + ExpectedError GetDescriptorIndex(const GPUResourceManager::DescriptorOpaqueHandle InHandle) const; private: @@ -122,28 +81,48 @@ namespace stf std::vector m_BufferUAVs; }; + class CommandShaderToken + { + friend class ScopedCommandShader; + CommandShaderToken() = default; + }; + + class ScopedCommandShader + { + public: + + ScopedCommandShader(const SharedPtr& InShader); + ScopedCommandShader(const ScopedCommandShader&) = delete; + ScopedCommandShader(ScopedCommandShader&&) = delete; + ScopedCommandShader& operator=(const ScopedCommandShader&) = delete; + ScopedCommandShader& operator=(ScopedCommandShader&&) = delete; + + ExpectedError StageBindingData(const ShaderBinding& InBinding); + + void StageBindlessResource(ScopedCommandContext& InContext, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); + + private: + + SharedPtr m_Shader; + }; + class ScopedCommandContext { public: - ScopedCommandContext(CommandEngineToken, + ScopedCommandContext(CommandEngineToken, const SharedPtr& InList, const SharedPtr& InResourceManager - ) - : m_List(InList) - , m_ResourceManager(MakeUnique(InResourceManager)) - { - } + ); - CommandList* operator->() const - { - return GetList(); - } + ScopedCommandContext(const ScopedCommandContext&) = delete; + ScopedCommandContext(ScopedCommandContext&&) = delete; + ScopedCommandContext& operator=(const ScopedCommandContext&) = delete; + ScopedCommandContext& operator=(ScopedCommandContext&&) = delete; - CommandList& operator*() const - { - return *GetList(); - } + CommandList* operator->() const; + + CommandList& operator*() const; template ExpectedError Section(const std::string_view InName, InLambdaType&& InFunc) @@ -152,45 +131,46 @@ namespace stf return InFunc(*this); } - CommandList* GetList() const - { - return m_List.get(); - } + CommandList* GetList() const; - [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) - { - return m_ResourceManager->CreateBuffer(InDesc); - } + [[nodiscard]] GPUResourceManager::BufferHandle CreateBuffer(const GPUResourceManager::BufferDesc& InDesc); - [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) - { - return m_ResourceManager->CreateUAV(InBufferHandle, InDesc); - } + [[nodiscard]] GPUResourceManager::BufferUAVHandle CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc); - void SetUAV(const GPUResourceManager::BufferUAVHandle InHandle) - { - m_ResourceManager->SetUAV(*m_List, InHandle); - } + void SetUAV(const GPUResourceManager::BufferUAVHandle InHandle); - [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData) - { - return m_ResourceManager->CreateCBV(InData); - } + [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData); - void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle) - { - m_ResourceManager->SetRootDescriptor(*m_List, InRootParamIndex, InHandle); - } + void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); - void Dispatch(const uint3 InDispatchConfig) + template + ExpectedError BindComputeShader(const SharedPtr& InShader, BindFunc&& InFunc) { - m_List->Dispatch(InDispatchConfig.x, InDispatchConfig.y, InDispatchConfig.z); + m_BoundShader = InShader; + m_BindlessResourcesToResolve.clear(); + ScopedCommandShader shader(m_BoundShader); + return InFunc(*this, shader); } + void StageBindlessResource(CommandShaderToken, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); + + ExpectedError Dispatch(const uint3 InDispatchConfig); + private: + struct StagedBindlessResource + { + std::string Name; + GPUResourceManager::DescriptorOpaqueHandle Descriptor; + }; + + ExpectedError ResolveAndStageBindlessResources(); + SharedPtr m_List = nullptr; + SharedPtr m_BoundShader = nullptr; UniquePtr m_ResourceManager = nullptr; + + std::vector m_BindlessResourcesToResolve; }; class CommandEngine diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 7f337e64..ca2a0849 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -29,7 +29,6 @@ namespace stf using DescriptorFreeList = FencedResourceFreeList; using ResourceHandle = typename ResourceManager::Handle; using DescriptorOpaqueHandle = typename DescriptorFreeList::Handle; - using DescriptorHeapReleaseManager = FencedResourceFreeList>; struct CreationParams @@ -74,7 +73,7 @@ namespace stf struct BufferDesc { - std::string_view Name = "DefaultConstantBuffer"; + std::string_view Name = "DefaultBuffer"; u32 RequestedSize = 0u; D3D12_RESOURCE_FLAGS Flags = D3D12_RESOURCE_FLAG_NONE; }; @@ -107,10 +106,44 @@ namespace stf DescriptorOpaqueHandle m_UAVHandle; }; + struct ReadbackBufferDesc + { + std::string_view Name = "DefaultReadbackBuffer"; + 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; + }; + + class ReadbackResultHandle + { + public: + + ReadbackResultHandle(Private, const ReadbackBufferHandle InHandle); + + ReadbackBufferHandle GetReadbackHandle() const; + + private: + ReadbackBufferHandle 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); @@ -130,6 +163,8 @@ namespace stf ExpectedError Release(const BufferUAVHandle InHandle); ExpectedError Release(const ConstantBufferViewHandle InHandle); + ExpectedError QueueReadback(CommandList& InCommandList, const ReadbackBufferHandle InHandle); + ExpectedError SetRootDescriptor(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); ExpectedError SetUAV(CommandList& InCommandList, const BufferUAVHandle InHandle); From ec6f9c9e24a94f3fa913b2474f4aedab91566e79 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 15:43:58 +0000 Subject: [PATCH 58/76] Comment out some parts of the command engine for now until everything is full hooked up. This should fix the previous failure --- src/Private/D3D12/CommandEngine.cpp | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 0a28deae..49b1e3c3 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -152,34 +152,34 @@ namespace stf ExpectedError ScopedCommandContext::Dispatch(const uint3 InDispatchConfig) { - m_ResourceManager->SetDescriptorHeap(*m_List); + //m_ResourceManager->SetDescriptorHeap(*m_List); return ResolveAndStageBindlessResources() .and_then( [&]() -> ExpectedError { - m_BoundShader->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_BoundShader->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); return {}; From f5bce5453bd32272af9dca094091918f31e044a7 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 15:49:11 +0000 Subject: [PATCH 59/76] Adding matchers for error to make nicer assertions for Error --- test/CMakeLists.txt | 1 + test/Private/Utility/ErrorTests.cpp | 24 +++++-- test/Public/TestUtilities/ErrorMatchers.h | 85 +++++++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 test/Public/TestUtilities/ErrorMatchers.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 140218e0..507c4df2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -225,6 +225,7 @@ set(SOURCES 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/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp index 33bca3af..829f535f 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include #include @@ -205,7 +206,8 @@ SCENARIO("Error Tests") THEN("Error contains error fragment") { - REQUIRE(error.HasFragmentWithFormat(errorFrag1.Format())); + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag1.Format())); + REQUIRE_THAT(error, ErrorContains(errorFrag1)); const auto formattedError = std::format("{}", error); const auto streamError = [&]() @@ -228,8 +230,8 @@ SCENARIO("Error Tests") THEN("Both error fragments exist and the latest fragment is before the first") { - REQUIRE(error.HasFragmentWithFormat(errorFrag1.Format())); - REQUIRE(error.HasFragmentWithFormat(errorFrag2.Format())); + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag1.Format())); + REQUIRE_THAT(error, ErrorContainsFormat(errorFrag2.Format())); const auto errorMessage = std::format("{}", error); @@ -261,7 +263,7 @@ SCENARIO("Error Tests") THEN("contains expected fragment") { - REQUIRE(error.HasFragmentWithFormat(expectedFormat.Literal())); + REQUIRE_THAT(error, ErrorContainsFormat(expectedFormat.Literal())); } } @@ -288,6 +290,15 @@ SCENARIO("Error Tests") 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; @@ -298,6 +309,11 @@ SCENARIO("Error Tests") 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())); } } } 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 From ead69137d1abfcc29a64186e3d7769424e4c4095 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 15:50:25 +0000 Subject: [PATCH 60/76] Refactoring RingBuffer to use ExpectedError --- .../D3D12/BindlessFreeListAllocator.cpp | 43 +++++-------------- src/Public/Container/RingBuffer.h | 40 +++++++++-------- src/Public/D3D12/BindlessFreeListAllocator.h | 14 +++--- test/Private/Container/RingBufferTests.cpp | 11 +++-- .../D3D12/BindlessFreeListAllocatorTests.cpp | 9 ++-- test/Private/D3D12/DescriptorManagerTests.cpp | 3 +- 6 files changed, 53 insertions(+), 67 deletions(-) diff --git a/src/Private/D3D12/BindlessFreeListAllocator.cpp b/src/Private/D3D12/BindlessFreeListAllocator.cpp index ae4e877e..028a71c1 100644 --- a/src/Private/D3D12/BindlessFreeListAllocator.cpp +++ b/src/Private/D3D12/BindlessFreeListAllocator.cpp @@ -5,29 +5,24 @@ namespace stf { - namespace Errors + namespace Errors::BindlessFreeListAllocator { - ErrorFragment BindlessFreeListAllocatorIsEmpty() + ErrorFragment Empty() { return ErrorFragment::Make<"Bindless allocator is empty">(); } - ErrorFragment UnknownBindlessFreeListAllocatorError() - { - return ErrorFragment::Make<"Unknown bindless allocator error">(); - } - - ErrorFragment InvalidBindlessIndex(const u32 InIndex) + ErrorFragment InvalidIndex(const u32 InIndex) { return ErrorFragment::Make<"Bindless index {} is invalid">(InIndex); } - ErrorFragment BindlessIndexAlreadyReleased(const u32 InIndex) + ErrorFragment IndexAlreadyReleased(const u32 InIndex) { return ErrorFragment::Make<"Bindless index {} has already been released">(InIndex); } - ErrorFragment ShrinkAttemptedOnBindlessAllocator(const u32 InCurrentSize, const u32 InRequestedSize) + ErrorFragment ShrinkAttempted(const u32 InCurrentSize, const u32 InRequestedSize) { return ErrorFragment::Make<"Attempted shrink which is unsupported. Current size: {}, Requested size: {}">(InCurrentSize, InRequestedSize); } @@ -51,20 +46,9 @@ namespace stf return BindlessIndex{ Private{}, InIndex }; }) .transform_error( - [](const EBufferError InError) -> Error + [](const Error& InError) -> Error { - switch (InError) - { - case EBufferError::EmptyBuffer: - { - return Error{ Errors::BindlessFreeListAllocatorIsEmpty() }; - } - - default: - { - return Error{ Errors::UnknownBindlessFreeListAllocatorError() }; - } - } + return InError + Errors::BindlessFreeListAllocator::Empty(); } ); } @@ -74,12 +58,12 @@ namespace stf const u32 index = InIndex; if (index >= m_NumDescriptors) { - return Unexpected{ Error{Errors::InvalidBindlessIndex(index) } }; + return Unexpected{ Error{Errors::BindlessFreeListAllocator::InvalidIndex(index) } }; } if (m_FreeSet[index]) { - return Unexpected{ Error{ Errors::BindlessIndexAlreadyReleased(index) } }; + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::IndexAlreadyReleased(index) } }; } m_FreeList.push_back(index); @@ -92,7 +76,7 @@ namespace stf { if (InNewSize < m_NumDescriptors) { - return Unexpected{ Error{ Errors::ShrinkAttemptedOnBindlessAllocator(m_NumDescriptors, InNewSize) } }; + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::ShrinkAttempted(m_NumDescriptors, InNewSize) } }; } if (InNewSize == m_NumDescriptors) @@ -111,11 +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) - { - return Error{ Errors::UnknownBindlessFreeListAllocatorError() }; - } ); } @@ -149,7 +128,7 @@ namespace stf const u32 index = InIndex; if (index >= m_NumDescriptors) { - return Unexpected{ Error{ Errors::InvalidBindlessIndex(InIndex) } }; + return Unexpected{ Error{ Errors::BindlessFreeListAllocator::InvalidIndex(InIndex) } }; } return !m_FreeSet[InIndex]; diff --git a/src/Public/Container/RingBuffer.h b/src/Public/Container/RingBuffer.h index 197e8c82..cf78d5ac 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) @@ -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 c01e736c..85d02e2d 100644 --- a/src/Public/D3D12/BindlessFreeListAllocator.h +++ b/src/Public/D3D12/BindlessFreeListAllocator.h @@ -10,14 +10,12 @@ namespace stf { - namespace Errors + namespace Errors::BindlessFreeListAllocator { - ErrorFragment BindlessFreeListAllocatorIsEmpty(); - ErrorFragment UnknownBindlessFreeListAllocatorError(); - - ErrorFragment InvalidBindlessIndex(const u32 InIndex); - ErrorFragment BindlessIndexAlreadyReleased(const u32 InIndex); - ErrorFragment ShrinkAttemptedOnBindlessAllocator(const u32 InCurrentSize, const u32 InRequestedSize); + ErrorFragment Empty(); + ErrorFragment InvalidIndex(const u32 InIndex); + ErrorFragment IndexAlreadyReleased(const u32 InIndex); + ErrorFragment ShrinkAttempted(const u32 InCurrentSize, const u32 InRequestedSize); } class BindlessFreeListAllocator @@ -60,8 +58,6 @@ namespace stf private: - using EBufferError = RingBuffer::EErrorType; - RingBuffer m_FreeList; std::vector m_FreeSet; u32 m_NumDescriptors = 0; diff --git a/test/Private/Container/RingBufferTests.cpp b/test/Private/Container/RingBufferTests.cpp index a2bc5c6f..fbf4c437 100644 --- a/test/Private/Container/RingBufferTests.cpp +++ b/test/Private/Container/RingBufferTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include @@ -180,12 +181,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 +287,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 0da7a037..96c597ab 100644 --- a/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp +++ b/test/Private/D3D12/BindlessFreeListAllocatorTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include @@ -30,7 +31,7 @@ SCENARIO("BindlessFreeListAllocatorTests") THEN("return expected error") { REQUIRE_FALSE(allocation); - REQUIRE(allocation.error().HasFragment(Errors::BindlessFreeListAllocatorIsEmpty())); + 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().HasFragment(Errors::BindlessIndexAlreadyReleased(invalidAllocation.value().GetIndex()))); + 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().HasFragment(Errors::InvalidBindlessIndex(finalAllocation.value()))); + 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().HasFragment(Errors::BindlessIndexAlreadyReleased(bindlessIndex1.value()))); + REQUIRE_THAT(secondReleaseResult.error(), ErrorContains(Errors::BindlessFreeListAllocator::IndexAlreadyReleased(bindlessIndex1.value()))); REQUIRE(initialCapacity == allocator.GetCapacity()); REQUIRE(0 == allocator.GetSize()); } diff --git a/test/Private/D3D12/DescriptorManagerTests.cpp b/test/Private/D3D12/DescriptorManagerTests.cpp index 0dbf54ee..fad687b9 100644 --- a/test/Private/D3D12/DescriptorManagerTests.cpp +++ b/test/Private/D3D12/DescriptorManagerTests.cpp @@ -1,4 +1,5 @@ +#include "TestUtilities/ErrorMatchers.h" #include #include #include @@ -81,7 +82,7 @@ TEST_CASE_PERSISTENT_FIXTURE(DescriptorManagerTestPrivate::Fixture, "Descriptor THEN("Fails") { REQUIRE_FALSE(secondReleaseResult.has_value()); - REQUIRE(secondReleaseResult.error().HasFragmentWithFormat(Errors::BindlessIndexAlreadyReleased(0u).Format())); + REQUIRE_THAT(secondReleaseResult.error(), ErrorContainsFormat(Errors::BindlessFreeListAllocator::IndexAlreadyReleased(0u).Format())); REQUIRE(1 == manager->GetCapacity()); REQUIRE(0 == manager->GetSize()); } From c927db9cc84b3830671148ce0427ad02dff65210 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 16:39:02 +0000 Subject: [PATCH 61/76] Fix fenced resource free list so that it actually uses the free list. --- src/Public/D3D12/FencedResourceFreeList.h | 64 ++++++++++++------- .../D3D12/FencedResourceFreeListTests.cpp | 28 +++++--- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/Public/D3D12/FencedResourceFreeList.h b/src/Public/D3D12/FencedResourceFreeList.h index b4c5f6a3..1244ee3f 100644 --- a/src/Public/D3D12/FencedResourceFreeList.h +++ b/src/Public/D3D12/FencedResourceFreeList.h @@ -28,6 +28,19 @@ namespace stf 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 { @@ -69,23 +82,31 @@ namespace stf { TickDeferredReleases(); - const auto resourceIndex = - [&]() - { - if (m_FreeList.empty()) + return m_FreeList.pop_front() + .and_then( + [&](const u32VersionedIndex InVersionedIndex) -> ExpectedError { - return u32VersionedIndex{ static_cast(m_Resources.size()) }; + 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 }; } - else + ) + .or_else( + [&](const Error&) -> ExpectedError { - return ThrowIfUnexpected(m_FreeList.pop_front()); - } - }(); + const u32VersionedIndex versionedIndex{ static_cast(m_Resources.size()) }; - m_Resources.emplace_back(std::move(InResource), resourceIndex.GetVersion()); - m_DeferredResources.emplace_back(false); + m_Resources.emplace_back(std::move(InResource), versionedIndex.GetVersion()); + m_DeferredResources.emplace_back(false); - return Handle{ FencedResourceFreeListToken{}, resourceIndex }; + return Handle{ FencedResourceFreeListToken{}, versionedIndex }; + } + ).value(); } ExpectedError Release(const Handle InHandle) @@ -94,13 +115,14 @@ namespace stf .and_then( [this](const u32VersionedIndex InVersionedIndex) -> ExpectedError { - const auto index = InVersionedIndex.GetIndex(); - + const auto nextVersion = InVersionedIndex.Next(); + const auto index = nextVersion.GetIndex(); + m_Resources[index].Version = nextVersion.GetVersion(); m_DeferredResources[index] = true; m_DeferredReleasedHandles.push_back( FencedResource { - .VersionedIndex = InVersionedIndex.Next(), + .VersionedIndex = nextVersion, .FencePoint = m_Queue->Signal() }); @@ -137,17 +159,12 @@ namespace stf if (index >= static_cast(m_Resources.size())) { - return Unexpected{ Error{ErrorFragment::Make<"Handle index ({}) out of range">(index) } }; + return Unexpected{ Error{ Errors::FencedResourceFreeList::HandleOutOfRange(index) } }; } if (m_Resources[index].Version != version) { - return Unexpected{ Error{ErrorFragment::Make<"Handle version mismatch (Expected: {}, Actual Handle: {}). Likely a stale resource handle">(m_Resources[index].Version, version) } }; - } - - if (m_DeferredResources[index]) - { - return Unexpected{ Error{ErrorFragment::Make<"Handle index ({}) has already been released">(index) } }; + return Unexpected{ Error{ Errors::FencedResourceFreeList::StaleHandle(m_Resources[index].Version, version) } }; } return versionedIndex; @@ -160,8 +177,7 @@ namespace stf const auto& releasedResource = ThrowIfUnexpected(m_DeferredReleasedHandles.pop_front()); const auto versionedIndex = releasedResource.VersionedIndex; const u32 index = versionedIndex.GetIndex(); - - m_Resources[index].Version = versionedIndex.GetVersion(); + m_DeferredResources[index] = false; m_FreeList.push_back(versionedIndex); } diff --git a/test/Private/D3D12/FencedResourceFreeListTests.cpp b/test/Private/D3D12/FencedResourceFreeListTests.cpp index 18889616..f8e1762e 100644 --- a/test/Private/D3D12/FencedResourceFreeListTests.cpp +++ b/test/Private/D3D12/FencedResourceFreeListTests.cpp @@ -1,12 +1,14 @@ +#include "TestUtilities/ErrorMatchers.h" + #include #include #include #include #include -#include "Utility/EnumReflection.h" -#include "Utility/Object.h" +#include +#include #include #include @@ -117,8 +119,10 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence THEN("handle is no longer valid") { - REQUIRE_FALSE(freeList.ValidateHandle(firstHandle)); - REQUIRE_FALSE(freeList.Get(firstHandle)); + 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") @@ -127,9 +131,17 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence REQUIRE(freeList.ValidateHandle(secondHandle)); const auto secondResource = getResource(freeList, secondHandle); - THEN("second handle is to the same resource as the first") + THEN("first and second resource are different") + { + REQUIRE(firstResource != secondResource); + } + + THEN("trying to access first resource fails") { - REQUIRE(firstResource == secondResource); + const auto getResultForFirst = freeList.Get(firstHandle); + + REQUIRE_FALSE(getResultForFirst); + REQUIRE_THAT(getResultForFirst.error(), ErrorContainsFormat(Errors::FencedResourceFreeList::StaleHandle(0, 0).Format())); } } } @@ -186,9 +198,9 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence REQUIRE(freeList.ValidateHandle(secondHandle)); const auto secondResource = getResource(freeList, secondHandle); - THEN("second handle is to the same resource as the first") + THEN("second handle is to a different resource from the first") { - REQUIRE(firstResource == secondResource); + REQUIRE(firstResource != secondResource); } } } From 89d2d6a5454905c17cc65572d60430a4240ccccb Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 16:59:47 +0000 Subject: [PATCH 62/76] Free List and tests --- src/CMakeLists.txt | 1 + src/Public/Container/FreeList.h | 155 +++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/Private/Container/FreeListTests.cpp | 76 +++++++++++ 4 files changed, 233 insertions(+) create mode 100644 src/Public/Container/FreeList.h create mode 100644 test/Private/Container/FreeListTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6ca0ea0..2b729902 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 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/test/CMakeLists.txt b/test/CMakeLists.txt index 507c4df2..e0519920 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -141,6 +141,7 @@ set(SHADER_SOURCES ) set(SOURCES + Private/Container/FreeListTests.cpp Private/Container/RingBufferTests.cpp Private/D3D12/BindlessFreeListAllocatorTests.cpp Private/D3D12/CommandEngineTests.cpp 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 From ff4472c7e630bc043ea236ecefa7352e35150d72 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 18:37:38 +0000 Subject: [PATCH 63/76] Support readbacks through the gpu resource manager and command context --- src/Private/D3D12/CommandEngine.cpp | 40 ++++++++++--- src/Private/D3D12/GPUDevice.cpp | 10 +++- src/Private/D3D12/GPUResource.cpp | 10 +++- src/Private/D3D12/GPUResourceManager.cpp | 25 ++++++-- src/Public/D3D12/CommandEngine.h | 11 ++++ src/Public/D3D12/GPUDevice.h | 2 +- src/Public/D3D12/GPUResource.h | 6 +- src/Public/D3D12/GPUResourceManager.h | 73 +++++++++++++++++++++--- 8 files changed, 152 insertions(+), 25 deletions(-) diff --git a/src/Private/D3D12/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index 49b1e3c3..cf53a091 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -31,7 +31,7 @@ namespace stf } } - [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle ScopedGPUResourceManager::CreateCBV(const std::span InData) + 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); @@ -44,14 +44,33 @@ namespace stf return cbv; } - [[nodiscard]] GPUResourceManager::BufferHandle ScopedGPUResourceManager::CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) + GPUResourceManager::BufferHandle ScopedGPUResourceManager::CreateBuffer(const GPUResourceManager::BufferDesc& InBufferDesc) { - return m_ResourceManager->Acquire(InBufferDesc); + const auto handle = m_ResourceManager->Acquire(InBufferDesc); + m_Buffers.push_back(handle); + return handle; } - [[nodiscard]] GPUResourceManager::BufferUAVHandle ScopedGPUResourceManager::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) + GPUResourceManager::BufferUAVHandle ScopedGPUResourceManager::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) { - return m_ResourceManager->CreateUAV(InBufferHandle, 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) @@ -113,22 +132,27 @@ namespace stf return m_List.get(); } - [[nodiscard]] GPUResourceManager::BufferHandle ScopedCommandContext::CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) + GPUResourceManager::BufferHandle ScopedCommandContext::CreateBuffer(const GPUResourceManager::BufferDesc& InDesc) { return m_ResourceManager->CreateBuffer(InDesc); } - [[nodiscard]] GPUResourceManager::BufferUAVHandle ScopedCommandContext::CreateUAV(const GPUResourceManager::BufferHandle& InBufferHandle, const D3D12_UNORDERED_ACCESS_VIEW_DESC& 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); } - [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle ScopedCommandContext::CreateCBV(const std::span InData) + GPUResourceManager::ConstantBufferViewHandle ScopedCommandContext::CreateCBV(const std::span InData) { return m_ResourceManager->CreateCBV(InData); } diff --git a/src/Private/D3D12/GPUDevice.cpp b/src/Private/D3D12/GPUDevice.cpp index 59ee8556..b005baa0 100644 --- a/src/Private/D3D12/GPUDevice.cpp +++ b/src/Private/D3D12/GPUDevice.cpp @@ -220,8 +220,14 @@ namespace stf InDesc.CastableFormats.data(), IID_PPV_ARGS(raw.GetAddressOf())) ); - SetName(raw.Get(), InDesc.Name); - return Object::New(GPUResource::CreationParams{ std::move(raw), InDesc.ClearValue, {D3D12_BARRIER_SYNC_NONE, D3D12_BARRIER_ACCESS_NO_ACCESS, InDesc.BarrierLayout} }); + 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 diff --git a/src/Private/D3D12/GPUResource.cpp b/src/Private/D3D12/GPUResource.cpp index 87ed0dba..3920edb1 100644 --- a/src/Private/D3D12/GPUResource.cpp +++ b/src/Private/D3D12/GPUResource.cpp @@ -4,9 +4,10 @@ namespace stf { - GPUResource::GPUResource(ObjectToken InToken, CreationParams InParams) noexcept + GPUResource::GPUResource(ObjectToken InToken, const CreationParams& InParams) noexcept : Object(InToken) - , m_Resource(std::move(InParams.Resource)) + , m_Name{InParams.Name} + , m_Resource(InParams.Resource) , m_ClearValue(InParams.ClearValue) , m_CurrentBarrier(InParams.InitialBarrier) { @@ -42,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(); diff --git a/src/Private/D3D12/GPUResourceManager.cpp b/src/Private/D3D12/GPUResourceManager.cpp index f6358e73..5b6abd6c 100644 --- a/src/Private/D3D12/GPUResourceManager.cpp +++ b/src/Private/D3D12/GPUResourceManager.cpp @@ -7,6 +7,14 @@ 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) @@ -95,12 +103,12 @@ namespace stf return m_SourceHandle; } - GPUResourceManager::ReadbackResultHandle::ReadbackResultHandle(Private, const ReadbackBufferHandle InHandle) + GPUResourceManager::ReadbackResultHandle::ReadbackResultHandle(Private, const InFlightReadbackHandle InHandle) : m_Handle(InHandle) { } - GPUResourceManager::ReadbackBufferHandle GPUResourceManager::ReadbackResultHandle::GetReadbackHandle() const + GPUResourceManager::InFlightReadbackHandle GPUResourceManager::ReadbackResultHandle::GetReadbackHandle() const { return m_Handle; } @@ -150,7 +158,7 @@ namespace stf { .HeapProps = CD3DX12_HEAP_PROPERTIES{ D3D12_HEAP_TYPE_READBACK }, .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(InSourceBuffer->GetDesc().Width), - .Name = InDesc.Name + .Name = std::format("Readback buffer for -> {}", InSourceBuffer->GetName()) } ) ); @@ -301,7 +309,16 @@ namespace stf { InCommandList.CopyBufferResource(*InReadback, *InSource); - return ReadbackResultHandle{ Private{}, InHandle }; + const auto handle = m_Readbacks.Manage( + InFlightReadback + { + .Handle = InHandle, + .FencePoint = m_Queue->Signal(), + .SourceBufferName = InSource->GetName() + } + ); + + return ReadbackResultHandle{ Private{}, handle }; } ); } diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 55aaf990..973b656f 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -64,6 +64,8 @@ namespace stf [[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); @@ -141,6 +143,8 @@ namespace stf [[nodiscard]] GPUResourceManager::ConstantBufferViewHandle CreateCBV(const std::span InData); + ExpectedError QueueReadback(const GPUResourceManager::BufferHandle InBufferHandle); + void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); template @@ -271,6 +275,13 @@ namespace stf return Execute(std::forward(InFunc)); } + template + ExpectedError ExecuteReadback(const std::string_view InName, const GPUResourceManager::ReadbackResultHandle InReadbackHandle, InFuncType&& InFunc) + { + PIXScopedEvent(m_Queue->GetRaw(), 0ull, "%s", InName.data()); + return m_ResourceManager->ExecuteReadback(InReadbackHandle, std::forward(InFunc)); + } + void Flush(); private: diff --git a/src/Public/D3D12/GPUDevice.h b/src/Public/D3D12/GPUDevice.h index 34a812ca..7db0134b 100644 --- a/src/Public/D3D12/GPUDevice.h +++ b/src/Public/D3D12/GPUDevice.h @@ -187,7 +187,7 @@ namespace stf std::span CastableFormats = {}; - std::string_view Name = "DefaultResource"; + std::string Name = "DefaultResource"; }; GPUDevice(ObjectToken, const CreationParams InDesc); diff --git a/src/Public/D3D12/GPUResource.h b/src/Public/D3D12/GPUResource.h index 7242af3a..18f10ab4 100644 --- a/src/Public/D3D12/GPUResource.h +++ b/src/Public/D3D12/GPUResource.h @@ -55,9 +55,10 @@ namespace stf ComPtr Resource; std::optional ClearValue{}; GPUEnhancedBarrier InitialBarrier{}; + std::string Name; }; - GPUResource(ObjectToken, 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 index ca2a0849..7c36d036 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -1,23 +1,36 @@ #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 +#include namespace stf { + template + concept ExecuteReadbackType = + TFuncTraits::ParamTypes::Size == 1 && + std::is_same_v::ParamTypes::template Type<0>, const MappedResource&> && + std::is_same_v::ReturnType, ExpectedError>; class CommandList; + namespace Errors::GPUResourceManager + { + ErrorFragment ReadbackHasNotBeenCompleted(const std::string_view InSourceName); + } + class GPUResourceManager : public Object { @@ -39,7 +52,7 @@ namespace stf struct ConstantBufferDesc { - std::string_view Name = "DefaultConstantBuffer"; + std::string Name = "DefaultConstantBuffer"; u32 RequestedSize = 0u; }; @@ -73,7 +86,7 @@ namespace stf struct BufferDesc { - std::string_view Name = "DefaultBuffer"; + std::string Name = "DefaultBuffer"; u32 RequestedSize = 0u; D3D12_RESOURCE_FLAGS Flags = D3D12_RESOURCE_FLAG_NONE; }; @@ -108,7 +121,6 @@ namespace stf struct ReadbackBufferDesc { - std::string_view Name = "DefaultReadbackBuffer"; BufferHandle Source; }; @@ -127,16 +139,30 @@ namespace stf 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 ReadbackBufferHandle InHandle); + ReadbackResultHandle(Private, const InFlightReadbackHandle InHandle); - ReadbackBufferHandle GetReadbackHandle() const; + InFlightReadbackHandle GetReadbackHandle() const; private: - ReadbackBufferHandle m_Handle; + InFlightReadbackHandle m_Handle; }; GPUResourceManager(ObjectToken InToken, const CreationParams& InParams); @@ -165,6 +191,37 @@ namespace stf ExpectedError QueueReadback(CommandList& InCommandList, const ReadbackBufferHandle InHandle); + template + ExpectedError ExecuteReadback(const ReadbackResultHandle InHandle, FuncType&& InFunc) + { + return m_Readbacks.Get(InHandle.GetReadbackHandle()) + .and_then( + [&](const InFlightReadback& InReadback) + { + if (!m_Queue->HasFencePointBeenReached(InReadback.FencePoint)) + { + return Unexpected{ Errors::GPUResourceManager::ReadbackHasNotBeenCompleted(InReadback.SourceBufferName) }; + } + + return m_Resources.Get(InReadback.Handle.GetReadbackHandle()) + .and_then( + [&](const SharedPtr& InReadbackBuffer) + { + return InFunc(InReadbackBuffer->Map()); + }) + .and_then( + [&]() -> ExpectedError + { + ThrowIfUnexpected(m_Resources.Release(InReadback.Handle.GetReadbackHandle())); + ThrowIfUnexpected(m_Readbacks.Release(InHandle.GetReadbackHandle())); + + return {}; + } + ); + } + ); + } + ExpectedError SetRootDescriptor(CommandList& InCommandList, const u32 InRootParamIndex, const ConstantBufferViewHandle InCBV); ExpectedError SetUAV(CommandList& InCommandList, const BufferUAVHandle InHandle); @@ -180,5 +237,7 @@ namespace stf ResourceManager m_Resources; DescriptorFreeList m_Descriptors; DescriptorHeapReleaseManager m_HeapReleaseManager; + + InFlightReadbackList m_Readbacks; }; } \ No newline at end of file From bf07354753cd44dbf33aaf2e566b7ef4d82b8e83 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 19:54:57 +0000 Subject: [PATCH 64/76] Adding new concept for determining if a type is an instantiation of a template --- src/Public/Utility/Concepts.h | 5 +++- src/Public/Utility/TypeTraits.h | 4 +-- .../AssertInfoWithDataTests.cpp | 2 +- test/Private/Utility/ConceptsTests.cpp | 28 +++++++++++++++++++ test/Private/Utility/TypeTraitsTests.cpp | 6 ++-- 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Public/Utility/Concepts.h b/src/Public/Utility/Concepts.h index c988b0dc..f10f2ef2 100644 --- a/src/Public/Utility/Concepts.h +++ b/src/Public/Utility/Concepts.h @@ -78,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) 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/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/Utility/ConceptsTests.cpp b/test/Private/Utility/ConceptsTests.cpp index 04825079..37ae6caa 100644 --- a/test/Private/Utility/ConceptsTests.cpp +++ b/test/Private/Utility/ConceptsTests.cpp @@ -405,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; 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); From 0ff3542102f3965c7f524a394cb6394ea2bcaec7 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 20:59:42 +0000 Subject: [PATCH 65/76] Adding a concept for what an ExpectedError is --- src/Public/Utility/Error.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index b1071ae3..c1cf44af 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -219,6 +219,16 @@ namespace stf template using ExpectedError = Expected; + + template + struct TIsExpectedError : std::integral_constant {}; + + template + struct TIsExpectedError> : std::integral_constant {}; + + template + concept ExpectedErrorType = TIsExpectedError::value; + } template<> From 928d2c63de6111cac0b02484a2a9ba286c509df0 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sun, 11 Jan 2026 21:00:32 +0000 Subject: [PATCH 66/76] Allowing Command contexts to return something. This is to support read back buffers. --- src/Public/D3D12/CommandEngine.h | 46 ++++---------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 973b656f..70c1f536 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -38,10 +38,13 @@ namespace stf template concept ExecuteLambdaType = - !CommandEngineFuncType && + !CommandEngineFuncType && TFuncTraits::ParamTypes::Size == 1 && std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && - std::is_same_v::ReturnType, ExpectedError>; + requires (T InFunc, ScopedCommandContext& InContext) + { + { InFunc(InContext) } -> ExpectedErrorType; + }; template concept BindShaderLambdaType = !LambdaType && @@ -189,45 +192,6 @@ namespace stf CommandEngine(ObjectToken, const CreationParams& InParams); - template - ExpectedError 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 - , m_ResourceManager - ); - return InFunc(context) - .and_then( - [&]() -> ExpectedError - { - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - m_Queue->ExecuteCommandList(*m_List); - return {}; - } - ) - .or_else( - [&](Error&& InError) -> ExpectedError - { - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); - return Unexpected{ InError }; - } - ); - } - template ExpectedError Execute(InLambdaType&& InFunc) { From 2c9a6a3e4b898fc1ed28048bef48a7f995b208d9 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 12 Jan 2026 07:51:47 +0000 Subject: [PATCH 67/76] Fix compilation error due to not fixing up a site of TIsInstantiationOf --- src/Public/Utility/TypeList.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4958ced6dcfb4a9956bf8ad162bde24c6edd17aa Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 12 Jan 2026 08:55:42 +0000 Subject: [PATCH 68/76] Adding a concept for ExpectedErrors with a specific value type --- src/Public/Utility/Error.h | 9 +++++++++ test/Private/Utility/ErrorTests.cpp | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Public/Utility/Error.h b/src/Public/Utility/Error.h index c1cf44af..3d55020c 100644 --- a/src/Public/Utility/Error.h +++ b/src/Public/Utility/Error.h @@ -226,9 +226,18 @@ namespace stf 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<> diff --git a/test/Private/Utility/ErrorTests.cpp b/test/Private/Utility/ErrorTests.cpp index 829f535f..dcba94d0 100644 --- a/test/Private/Utility/ErrorTests.cpp +++ b/test/Private/Utility/ErrorTests.cpp @@ -49,11 +49,27 @@ namespace stf::ErrorFragmentCompileTests::ConstructionTests static_assert(!TestConstructError, "Expected a single unformattable argument, among formattable arguments to be not be valid"); } -namespace stf::ErrorCompileTests +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; From 53c36c12b9139bd923416568dec817259b93eb3f Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 12 Jan 2026 09:15:58 +0000 Subject: [PATCH 69/76] Support Readbacks and Command Execute functions returns values --- src/Public/D3D12/CommandEngine.h | 72 ++++++++++++++++++++------- src/Public/D3D12/GPUResourceManager.h | 5 +- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 70c1f536..53f481a3 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -46,6 +46,13 @@ namespace stf { InFunc(InContext) } -> ExpectedErrorType; }; + template + concept VoidExecuteLambdaType = ExecuteLambdaType && + requires (T InFunc, ScopedCommandContext& InContext) + { + { InFunc(InContext) } -> ExpectedErrorWithValueType; + }; + template concept BindShaderLambdaType = !LambdaType && TFuncTraits::ParamTypes::Size == 2 && @@ -130,7 +137,7 @@ namespace stf CommandList& operator*() const; template - ExpectedError 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()); return InFunc(*this); @@ -193,7 +200,7 @@ namespace stf CommandEngine(ObjectToken, const CreationParams& InParams); template - ExpectedError Execute(InLambdaType&& InFunc) + auto Execute(InLambdaType&& InFunc) { auto allocator = [this]() { @@ -214,11 +221,34 @@ namespace stf , m_ResourceManager ); - return InFunc(context) + return ExecuteImpl(context, std::move(allocator), std::forward(InFunc)); + } + + template + auto Execute(const std::string_view InName, InLambdaType&& InFunc) + { + PIXScopedEvent(m_Queue->GetRaw(), 0ull, "%s", InName.data()); + return Execute(std::forward(InFunc)); + } + + template + ExpectedError 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(allocator), m_Queue->Signal() }); + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); m_Queue->ExecuteCommandList(*m_List); return {}; } @@ -226,30 +256,34 @@ namespace stf .or_else( [&](Error&& InError) -> ExpectedError { - m_Allocators.push_back(FencedAllocator{ std::move(allocator), m_Queue->Signal() }); + m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); return Unexpected{ InError }; } ); } template - ExpectedError Execute(const std::string_view InName, InLambdaType&& InFunc) + requires (!VoidExecuteLambdaType) + auto ExecuteImpl(ScopedCommandContext& InContext, SharedPtr&& InCommandAllocator, InLambdaType&& InFunc) { - PIXScopedEvent(m_Queue->GetRaw(), 0ull, "%s", InName.data()); - return Execute(std::forward(InFunc)); - } - - template - ExpectedError ExecuteReadback(const std::string_view InName, const GPUResourceManager::ReadbackResultHandle InReadbackHandle, InFuncType&& InFunc) - { - PIXScopedEvent(m_Queue->GetRaw(), 0ull, "%s", InName.data()); - return m_ResourceManager->ExecuteReadback(InReadbackHandle, std::forward(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_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); + return InError; + } + ); } - void Flush(); - - private: - struct FencedAllocator { SharedPtr Allocator; diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 7c36d036..5803e164 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -22,7 +22,10 @@ namespace stf concept ExecuteReadbackType = TFuncTraits::ParamTypes::Size == 1 && std::is_same_v::ParamTypes::template Type<0>, const MappedResource&> && - std::is_same_v::ReturnType, ExpectedError>; + requires (T InFunc, const MappedResource& InResource) + { + { InFunc(InResource) } -> ExpectedErrorType; + }; class CommandList; From 0655fabf7252358b66862b18786e3b9cdbe4b580 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 12 Jan 2026 09:36:52 +0000 Subject: [PATCH 70/76] Supporting returning values from readback functions --- src/Public/D3D12/GPUResourceManager.h | 81 +++++++++++++++++++-------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/src/Public/D3D12/GPUResourceManager.h b/src/Public/D3D12/GPUResourceManager.h index 5803e164..3a969094 100644 --- a/src/Public/D3D12/GPUResourceManager.h +++ b/src/Public/D3D12/GPUResourceManager.h @@ -21,11 +21,18 @@ namespace stf template concept ExecuteReadbackType = TFuncTraits::ParamTypes::Size == 1 && - std::is_same_v::ParamTypes::template Type<0>, const MappedResource&> && + std::is_same_v::ParamTypes::template Type<0>, const MappedResource&>&& requires (T InFunc, const MappedResource& InResource) - { - { InFunc(InResource) } -> ExpectedErrorType; - }; + { + { InFunc(InResource) } -> ExpectedErrorType; + }; + + template + concept VoidExecuteReadbackType = ExecuteReadbackType && + requires (T InFunc, const MappedResource & InResource) + { + { InFunc(InResource) } -> ExpectedErrorWithValueType; + }; class CommandList; @@ -178,7 +185,7 @@ namespace stf [[nodiscard]] ConstantBufferViewHandle CreateCBV(const ConstantBufferHandle InHandle); [[nodiscard]] ExpectedError GetDescriptorIndex(const DescriptorOpaqueHandle InHandle) const; - + template ExpectedError UploadData(const T& InData, const ConstantBufferHandle InBufferHandle) { @@ -195,32 +202,19 @@ namespace stf ExpectedError QueueReadback(CommandList& InCommandList, const ReadbackBufferHandle InHandle); template - ExpectedError ExecuteReadback(const ReadbackResultHandle InHandle, FuncType&& InFunc) + auto ExecuteReadback(const ReadbackResultHandle InHandle, FuncType&& InFunc) { + using RetType = decltype(InFunc(std::declval())); return m_Readbacks.Get(InHandle.GetReadbackHandle()) .and_then( - [&](const InFlightReadback& InReadback) + [&](const InFlightReadback& InReadback) -> RetType { if (!m_Queue->HasFencePointBeenReached(InReadback.FencePoint)) { - return Unexpected{ Errors::GPUResourceManager::ReadbackHasNotBeenCompleted(InReadback.SourceBufferName) }; + return Unexpected{ Error{ Errors::GPUResourceManager::ReadbackHasNotBeenCompleted(InReadback.SourceBufferName) } }; } - return m_Resources.Get(InReadback.Handle.GetReadbackHandle()) - .and_then( - [&](const SharedPtr& InReadbackBuffer) - { - return InFunc(InReadbackBuffer->Map()); - }) - .and_then( - [&]() -> ExpectedError - { - ThrowIfUnexpected(m_Resources.Release(InReadback.Handle.GetReadbackHandle())); - ThrowIfUnexpected(m_Readbacks.Release(InHandle.GetReadbackHandle())); - - return {}; - } - ); + return ExecuteReadbackImpl(InReadback.Handle, InHandle.GetReadbackHandle(), std::forward(InFunc)); } ); } @@ -233,6 +227,47 @@ namespace stf 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; From 7fd6d65fe771d8094534adb7e7473566617debc3 Mon Sep 17 00:00:00 2001 From: KStocky Date: Mon, 12 Jan 2026 09:37:20 +0000 Subject: [PATCH 71/76] Support returning values from readbacks in the command engine --- src/Public/D3D12/CommandEngine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index 53f481a3..eb216cf1 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -232,7 +232,7 @@ namespace stf } template - ExpectedError ExecuteReadback(const GPUResourceManager::ReadbackResultHandle InReadbackHandle, InFuncType&& InFunc) + auto ExecuteReadback(const GPUResourceManager::ReadbackResultHandle InReadbackHandle, InFuncType&& InFunc) { return m_ResourceManager->ExecuteReadback(InReadbackHandle, std::forward(InFunc)); } From f92bc752f792bbc01e17abf134f68ea1f0ad49ca Mon Sep 17 00:00:00 2001 From: KStocky Date: Thu, 15 Jan 2026 22:33:06 +0000 Subject: [PATCH 72/76] Fix RingBuffer and added a new test case to cover the scenario where RingBuffer was broken (pushing back on to a default constructed ring buffer could never be resized through push_back) --- src/Public/Container/RingBuffer.h | 4 ++-- test/Private/Container/RingBufferTests.cpp | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Public/Container/RingBuffer.h b/src/Public/Container/RingBuffer.h index cf78d5ac..2c7a8d50 100644 --- a/src/Public/Container/RingBuffer.h +++ b/src/Public/Container/RingBuffer.h @@ -162,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; @@ -175,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); diff --git a/test/Private/Container/RingBufferTests.cpp b/test/Private/Container/RingBufferTests.cpp index fbf4c437..7ed5ce93 100644 --- a/test/Private/Container/RingBufferTests.cpp +++ b/test/Private/Container/RingBufferTests.cpp @@ -107,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") From 2223c539bdeed0ec8e11a2c1cb6f76dec2fd39b8 Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 17 Jan 2026 13:40:53 +0000 Subject: [PATCH 73/76] Refactor of shader test driver to use the new scoped shader pipeline. Also removing a lot of unnecessary code --- src/CMakeLists.txt | 4 - src/Private/D3D12/CommandEngine.cpp | 115 ++++---- src/Private/D3D12/Shader/ShaderBindingMap.cpp | 26 -- .../Framework/ShaderTestDescriptorManager.cpp | 123 --------- src/Private/Framework/ShaderTestDriver.cpp | 260 +++++++++--------- src/Private/Framework/ShaderTestFixture.cpp | 21 +- src/Private/Framework/ShaderTestShader.cpp | 73 ----- src/Public/D3D12/CommandEngine.h | 52 ++-- src/Public/D3D12/FencedResourceFreeList.h | 6 - src/Public/D3D12/Shader/ShaderBindingMap.h | 11 +- src/Public/Framework/ShaderTestCommon.h | 9 - .../Framework/ShaderTestDescriptorManager.h | 67 ----- src/Public/Framework/ShaderTestDriver.h | 10 +- src/Public/Framework/ShaderTestFixture.h | 2 - src/Public/Framework/ShaderTestShader.h | 57 ---- test/CMakeLists.txt | 1 - .../D3D12/FencedResourceFreeListTests.cpp | 37 +-- .../ShaderTestDescriptorManagerTests.cpp | 259 ----------------- 18 files changed, 233 insertions(+), 900 deletions(-) delete mode 100644 src/Private/Framework/ShaderTestDescriptorManager.cpp delete mode 100644 src/Private/Framework/ShaderTestShader.cpp delete mode 100644 src/Public/Framework/ShaderTestDescriptorManager.h delete mode 100644 src/Public/Framework/ShaderTestShader.h delete mode 100644 test/Private/Framework/ShaderTestDescriptorManagerTests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2b729902..16fe7faa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,10 +56,8 @@ set(SOURCES Public/D3D12/Shader/VirtualShaderDirectoryMappingManager.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 @@ -115,10 +113,8 @@ set(SOURCES 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/CommandEngine.cpp b/src/Private/D3D12/CommandEngine.cpp index cf53a091..c78bb8a7 100644 --- a/src/Private/D3D12/CommandEngine.cpp +++ b/src/Private/D3D12/CommandEngine.cpp @@ -93,8 +93,14 @@ namespace stf return m_ResourceManager->GetDescriptorIndex(InHandle); } - ScopedCommandShader::ScopedCommandShader(const SharedPtr& InShader) + ScopedCommandShader::ScopedCommandShader( + const SharedPtr& InShader, + const SharedPtr& InResourceManager, + const SharedPtr& InList + ) : m_Shader(InShader) + , m_ResourceManager(InResourceManager) + , m_List(InList) { } @@ -103,9 +109,20 @@ namespace stf return m_Shader->StageBindingData(InBinding); } - void ScopedCommandShader::StageBindlessResource(ScopedCommandContext& InContext, std::string InName, const GPUResourceManager::BufferUAVHandle InHandle) + ExpectedError ScopedCommandShader::StageBindlessResource(std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle) { - InContext.StageBindlessResource(CommandShaderToken{}, std::move(InName), 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, @@ -113,7 +130,7 @@ namespace stf const SharedPtr& InResourceManager ) : m_List(InList) - , m_ResourceManager(MakeUnique(InResourceManager)) + , m_ResourceManager(MakeShared(InResourceManager)) { } @@ -162,79 +179,39 @@ namespace stf m_ResourceManager->SetRootDescriptor(*m_List, InRootParamIndex, InHandle); } - void ScopedCommandContext::StageBindlessResource(CommandShaderToken, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle) + void ScopedCommandContext::PreBindShader(const SharedPtr& InShader) { - SetUAV(InHandle); - - m_BindlessResourcesToResolve.emplace_back( - StagedBindlessResource - { - .Name = std::move(InBindingName), - .Descriptor = InHandle.GetUAVHandle() - }); + m_ResourceManager->SetDescriptorHeap(*m_List); + m_List->SetComputeRootSignature(InShader->GetRootSig()); } - ExpectedError ScopedCommandContext::Dispatch(const uint3 InDispatchConfig) + void ScopedCommandContext::SetShaderStateAndDispatch(const SharedPtr& InShader, const uint3 InDispatchConfig) { - //m_ResourceManager->SetDescriptorHeap(*m_List); - return ResolveAndStageBindlessResources() - .and_then( - [&]() -> ExpectedError + InShader->ForEachStagingBuffer( + [&](const u32 InRootParamIndex, const ShaderBindingMap::StagingInfo& InStagingInfo) + { + switch (InStagingInfo.Type) { - //m_BoundShader->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); - return {}; + 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(); + } } - ); - } - - ExpectedError ScopedCommandContext::ResolveAndStageBindlessResources() - { - if (!m_BindlessResourcesToResolve.empty() && !m_BoundShader) - { - return Unexpected{ Error::FromFragment<"THIS SHOULD NOT HAPPEN: There are bindless resources to resolve, with no bound shader">()}; - } - - for (const auto& [bindingName, descriptor] : m_BindlessResourcesToResolve) - { - const auto stageResult = m_ResourceManager->GetDescriptorIndex(descriptor) - .and_then( - [&](const u32 InIndex) - { - return m_BoundShader->StageBindingData(ShaderBinding{ bindingName, InIndex }); - } - ); - - if (!stageResult) - { - return stageResult; } - } + ); - return {}; + m_List->Dispatch(InDispatchConfig.x, InDispatchConfig.y, InDispatchConfig.z); } CommandEngine::CommandEngine(ObjectToken InToken, const CreationParams& InParams) diff --git a/src/Private/D3D12/Shader/ShaderBindingMap.cpp b/src/Private/D3D12/Shader/ShaderBindingMap.cpp index 926d0fdc..b0a71f97 100644 --- a/src/Private/D3D12/Shader/ShaderBindingMap.cpp +++ b/src/Private/D3D12/Shader/ShaderBindingMap.cpp @@ -204,32 +204,6 @@ namespace stf return {}; } - /* - void ShaderBindingMap::CommitBindings(ScopedCommandContext& InContext) const - { - for (const auto& [rootParamIndex, buffer] : m_RootParamBuffers) - { - switch (buffer.Type) - { - case EBindType::RootConstants: - { - InContext->SetComputeRoot32BitConstants(rootParamIndex, std::span{ buffer.Buffer }, 0); - break; - } - case EBindType::RootDescriptor: - { - const auto cbv = InContext.CreateCBV(std::as_bytes( std::span{buffer.Buffer} )); - InContext.SetRootDescriptor(rootParamIndex, cbv); - break; - } - default: - { - std::unreachable(); - } - } - } - } - */ ShaderBindingMap::ShaderBindingMap( SharedPtr&& InRootSignature, BindingMapType&& InNameToBindingsMap, diff --git a/src/Private/Framework/ShaderTestDescriptorManager.cpp b/src/Private/Framework/ShaderTestDescriptorManager.cpp deleted file mode 100644 index d7cd9e2d..00000000 --- a/src/Private/Framework/ShaderTestDescriptorManager.cpp +++ /dev/null @@ -1,123 +0,0 @@ - -#include "Framework/ShaderTestDescriptorManager.h" - -#include "Utility/Tuple.h" - -namespace stf -{ - ShaderTestDescriptorManager::ShaderTestDescriptorManager(ObjectToken InToken, 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}) - , m_CPUDescriptors(m_CPUHeap->GetHeapRange()) - , m_GPUDescriptors(m_GPUHeap->GetHeapRange()) - { - } - - ShaderTestDescriptorManager::Expected ShaderTestDescriptorManager::CreateUAV(SharedPtr InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InDesc) - { - 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 Error&) - { - return EErrorType::AllocatorFull; - }); - } - - ShaderTestDescriptorManager::Expected ShaderTestDescriptorManager::ReleaseUAV(const ShaderTestUAV& InUAV) - { - return - m_Allocator.Release(InUAV.Handle) - .transform_error( - [](const Error&) - { - 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 Error&) - { - return EErrorType::AttemptedShrink; - } - ); - } - - 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 9b8432fc..fd4caff3 100644 --- a/src/Private/Framework/ShaderTestDriver.cpp +++ b/src/Private/Framework/ShaderTestDriver.cpp @@ -34,46 +34,7 @@ namespace stf .Device = m_Device } )) - , m_DescriptorManager(Object::New( - ShaderTestDescriptorManager::CreationParams{ - .Device = m_Device, - .InitialSize = 16 - } - )) - { - } - - SharedPtr ShaderTestDriver::CreateBuffer(const D3D12_HEAP_TYPE InType, const D3D12_RESOURCE_DESC1& InDesc) - { - return m_Device->CreateCommittedResource( - GPUDevice::CommittedResourceDesc - { - .HeapProps = CD3DX12_HEAP_PROPERTIES(InType), - .ResourceDesc = InDesc - }); - } - - 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) @@ -93,115 +54,148 @@ namespace stf } ); } - + 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 dispatchDimensions = InTestDesc.DispatchConfig * InTestDesc.Shader->GetThreadGroupSize(); - const auto assertUAV = CreateUAV(assertBuffer, CreateRawUAVDesc(bufferSizeInBytes)); - const auto allocationUAV = CreateUAV(allocationBuffer, CreateRawUAVDesc(allocationBufferSizeInBytes)); + struct Resources + { + GPUResourceManager::BufferHandle AssertBuffer; + GPUResourceManager::BufferHandle AllocationBuffer; + GPUResourceManager::BufferUAVHandle AssertUAV; + GPUResourceManager::BufferUAVHandle AllocationUAV; + }; - return InTestDesc.Shader.StageConstantBufferData( - ShaderTestShader::TestBindings - { - .DispatchConfig = InTestDesc.DispatchConfig, - .AllocationBufferIndex = allocationUAV.Handle.GetIndex(), - .TestDataBufferIndex = assertUAV.Handle.GetIndex(), - .TestDataLayout = InTestDesc.TestBufferLayout - }, - InTestDesc.Bindings - ) - .and_then( - [&]() - { - return m_CommandEngine->Execute(InTestDesc.TestName, - [&](ScopedCommandContext& InContext) + struct Readbacks + { + const GPUResourceManager::ReadbackResultHandle AssertReadback; + const GPUResourceManager::ReadbackResultHandle AllocationReadback; + }; + + return m_CommandEngine->Execute(InTestDesc.TestName, + [&](ScopedCommandContext& InContext) -> ExpectedError + { + return InContext.Section("Test Setup", + [&](ScopedCommandContext& InContext) -> ExpectedError + { + InContext->SetPipelineState(*pipelineState); + + const auto assertBuffer = InContext.CreateBuffer( + GPUResourceManager::BufferDesc + { + .Name = "Assert data buffer", + .RequestedSize = bufferSizeInBytes, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS + } + ); + + const auto allocationBuffer = InContext.CreateBuffer( + GPUResourceManager::BufferDesc + { + .Name = "Allocation data buffer", + .RequestedSize = allocationBufferSizeInBytes, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS + } + ); + + const auto assertBufferUAV = InContext.CreateUAV(assertBuffer, + CreateRawUAVDesc(bufferSizeInBytes)); + + const auto allocationBufferUAV = InContext.CreateUAV(allocationBuffer, + CreateRawUAVDesc(allocationBufferSizeInBytes)); + + return Resources { - return InContext.Section("Test Setup", + .AssertBuffer = assertBuffer, + .AllocationBuffer = allocationBuffer, + .AssertUAV = assertBufferUAV, + .AllocationUAV = allocationBufferUAV + }; + }) + .and_then( + [&](Resources&& InBuffers) -> ExpectedError + { + return InContext.Section("Test Dispatch", [&](ScopedCommandContext& InContext) { - InContext->SetPipelineState(*pipelineState); - m_DescriptorManager->SetDescriptorHeap(*InContext); - InContext->SetComputeRootSignature(InTestDesc.Shader.GetRootSig()); - InContext->SetBufferUAV(*assertBuffer); - InContext->SetBufferUAV(*allocationBuffer); - - InTestDesc.Shader.CommitBindings(InContext); - return ExpectedError{}; - } - ).and_then( - [&]() - { - return InContext.Section("Test Dispatch", - [&](ScopedCommandContext& InContext) + return InContext.Dispatch(InTestDesc.DispatchConfig, InTestDesc.Shader, + [&](ScopedCommandShader& InShader) -> ExpectedError { - return InContext.Dispatch(InTestDesc.DispatchConfig); - } - ); - } - ).and_then( - [&]() - { - return InContext.Section("Results readback", - [&](ScopedCommandContext& InContext) - { - InContext->CopyBufferResource(*readBackBuffer, *assertBuffer); - InContext->CopyBufferResource(*readBackAllocationBuffer, *allocationBuffer); - return ExpectedError{}; - } - ); - } - ); - } - ); - }) - .and_then( - [&]() - { - m_CommandEngine->Flush(); - m_DeferredDeletedDescriptorHeaps.clear(); - - return m_DescriptorManager->ReleaseUAV(assertUAV) + 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 InBuffers; + }); + }) .and_then( - [this, &allocationUAV]() - { - return m_DescriptorManager->ReleaseUAV(allocationUAV); - } - ) - .transform( - [this, &readBackAllocationBuffer, &readBackBuffer, &InTestDesc]() + [&](const Resources& InBuffers) { - return ReadbackResults(*readBackAllocationBuffer, *readBackBuffer, InTestDesc.Shader.GetThreadGroupSize() * InTestDesc.DispatchConfig, InTestDesc.TestBufferLayout); - } - ) - .transform_error( - [](const ShaderTestDescriptorManager::EErrorType InErrorType) -> Error - { - using enum ShaderTestDescriptorManager::EErrorType; - - switch (InErrorType) - { - case DescriptorAlreadyFree: + return InContext.Section("Results readback", + [&](ScopedCommandContext& InContext) { - return Error::FromFragment<"Attempted to free an already freed descriptor.">(); - } - default: + 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 { - return Error::FromFragment<"Unknown descriptor management error.">(); - } - } - } - ); - } - ); + 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 9ca901e8..b8e7f909 100644 --- a/src/Private/Framework/ShaderTestFixture.cpp +++ b/src/Private/Framework/ShaderTestFixture.cpp @@ -4,6 +4,7 @@ #include "D3D12/Shader/Shader.h" #include "Framework/PIXCapturer.h" +#include "Framework/ShaderTestCommon.h" #include "Utility/EnumReflection.h" #include @@ -120,15 +121,15 @@ namespace stf .and_then( [&](const CompiledShaderData& InCompilationResult) { - return CreateTestShader(InCompilationResult); + return Shader::Make(InCompilationResult, *m_Device); }) .and_then( - [&](const 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, @@ -170,20 +171,6 @@ namespace stf return m_Compiler.CompileShader(job); } - ExpectedError> ShaderTestFixture::CreateTestShader(const CompiledShaderData& InCompiledShaderData) const - { - return Shader::Make(InCompiledShaderData, *m_Device) - .and_then( - [](const SharedPtr& InShader) -> ExpectedError> - { - return Object::New( - ShaderTestShader::CreationParams - { - .Shader = InShader - }); - }); - } - void ShaderTestFixture::RegisterByteReader(std::string InTypeIDName, MultiTypeByteReader InByteReader) { const auto readerId = m_TestDriver->RegisterByteReader(InTypeIDName, std::move(InByteReader)); diff --git a/src/Private/Framework/ShaderTestShader.cpp b/src/Private/Framework/ShaderTestShader.cpp deleted file mode 100644 index c1b1aa8a..00000000 --- a/src/Private/Framework/ShaderTestShader.cpp +++ /dev/null @@ -1,73 +0,0 @@ - -#include "Framework/ShaderTestShader.h" - -namespace stf -{ - ShaderTestShader::ShaderTestShader(ObjectToken InToken, const CreationParams& InParams) - : Object(InToken) - , m_Shader(InParams.Shader) - { - } - - ExpectedError ShaderTestShader::StageConstantBufferData(const TestBindings& InTestBindings, const std::span InBindings) - { - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::DispatchDimensions", InTestBindings.DispatchConfig * GetThreadGroupSize() }); - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::AllocationBufferIndex", InTestBindings.AllocationBufferIndex }); - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::TestDataBufferIndex", InTestBindings.TestDataBufferIndex }); - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Asserts", InTestBindings.TestDataLayout.GetAssertSection() }); - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Strings", InTestBindings.TestDataLayout.GetStringSection() }); - std::ignore = m_Shader->StageBindingData(ShaderBinding{ "stf::detail::Sections", InTestBindings.TestDataLayout.GetSectionInfoSection() }); - - for (const auto& binding : InBindings) - { - if (auto result = m_Shader->StageBindingData(binding); !result) - { - return result; - } - } - - return {}; - } - - void ShaderTestShader::CommitBindings(ScopedCommandContext& InCommandContext) const - { - m_Shader->ForEachStagingBuffer( - [&](const u32 InRootParamIndex, const ShaderBindingMap::StagingInfo& InStagingInfo) - { - switch (InStagingInfo.Type) - { - case ShaderBindingMap::EBindType::RootConstants: - { - InCommandContext->SetComputeRoot32BitConstants(InRootParamIndex, std::span{ InStagingInfo.Buffer }, 0); - break; - } - case ShaderBindingMap::EBindType::RootDescriptor: - { - const auto cbv = InCommandContext.CreateCBV(std::as_bytes(std::span{ InStagingInfo.Buffer })); - InCommandContext.SetRootDescriptor(InRootParamIndex, cbv); - break; - } - default: - { - std::unreachable(); - } - } - } - ); - } - - uint3 ShaderTestShader::GetThreadGroupSize() const - { - return m_Shader->GetThreadGroupSize(); - } - - const RootSignature& ShaderTestShader::GetRootSig() const - { - return m_Shader->GetRootSig(); - } - - IDxcBlob* ShaderTestShader::GetCompiledShader() const - { - return m_Shader->GetCompiledShader(); - } -} \ No newline at end of file diff --git a/src/Public/D3D12/CommandEngine.h b/src/Public/D3D12/CommandEngine.h index eb216cf1..4df7b5cf 100644 --- a/src/Public/D3D12/CommandEngine.h +++ b/src/Public/D3D12/CommandEngine.h @@ -54,10 +54,9 @@ namespace stf }; template - concept BindShaderLambdaType = !LambdaType && - TFuncTraits::ParamTypes::Size == 2 && - std::is_same_v::ParamTypes::template Type<0>, ScopedCommandContext&> && - std::is_same_v::ParamTypes::template Type<1>, ScopedCommandShader&> && + concept BindShaderLambdaType = !LambdaType && + TFuncTraits::ParamTypes::Size == 1 && + std::is_same_v::ParamTypes::template Type<0>, ScopedCommandShader&>&& std::is_same_v::ReturnType, ExpectedError>; class ScopedGPUResourceManager @@ -103,19 +102,23 @@ namespace stf { public: - ScopedCommandShader(const SharedPtr& InShader); + 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); - - void StageBindlessResource(ScopedCommandContext& InContext, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); + ExpectedError StageBindlessResource(std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); private: SharedPtr m_Shader; + SharedPtr m_ResourceManager; + SharedPtr m_List; }; class ScopedCommandContext @@ -158,33 +161,28 @@ namespace stf void SetRootDescriptor(const u32 InRootParamIndex, const GPUResourceManager::ConstantBufferViewHandle InHandle); template - ExpectedError BindComputeShader(const SharedPtr& InShader, BindFunc&& InFunc) + ExpectedError Dispatch(const uint3 InDispatchConfig, const SharedPtr& InShader, BindFunc&& InFunc) { - m_BoundShader = InShader; - m_BindlessResourcesToResolve.clear(); - ScopedCommandShader shader(m_BoundShader); - return InFunc(*this, shader); - } - - void StageBindlessResource(CommandShaderToken, std::string InBindingName, const GPUResourceManager::BufferUAVHandle InHandle); + PreBindShader(InShader); + ScopedCommandShader shader(InShader, m_ResourceManager, m_List); - ExpectedError Dispatch(const uint3 InDispatchConfig); + return InFunc(shader) + .transform( + [&]() + { + SetShaderStateAndDispatch(InShader, InDispatchConfig); + } + ); + } private: - struct StagedBindlessResource - { - std::string Name; - GPUResourceManager::DescriptorOpaqueHandle Descriptor; - }; - - ExpectedError ResolveAndStageBindlessResources(); + void PreBindShader(const SharedPtr& InShader); + void SetShaderStateAndDispatch(const SharedPtr& InShader, const uint3 InDispatchConfig); SharedPtr m_List = nullptr; SharedPtr m_BoundShader = nullptr; - UniquePtr m_ResourceManager = nullptr; - - std::vector m_BindlessResourcesToResolve; + SharedPtr m_ResourceManager = nullptr; }; class CommandEngine @@ -256,6 +254,7 @@ namespace stf .or_else( [&](Error&& InError) -> ExpectedError { + m_List->Close(); m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); return Unexpected{ InError }; } @@ -278,6 +277,7 @@ namespace stf .transform_error( [&](Error&& InError) { + m_List->Close(); m_Allocators.push_back(FencedAllocator{ std::move(InCommandAllocator), m_Queue->Signal() }); return InError; } diff --git a/src/Public/D3D12/FencedResourceFreeList.h b/src/Public/D3D12/FencedResourceFreeList.h index 1244ee3f..6c72447b 100644 --- a/src/Public/D3D12/FencedResourceFreeList.h +++ b/src/Public/D3D12/FencedResourceFreeList.h @@ -102,7 +102,6 @@ namespace stf const u32VersionedIndex versionedIndex{ static_cast(m_Resources.size()) }; m_Resources.emplace_back(std::move(InResource), versionedIndex.GetVersion()); - m_DeferredResources.emplace_back(false); return Handle{ FencedResourceFreeListToken{}, versionedIndex }; } @@ -118,7 +117,6 @@ namespace stf const auto nextVersion = InVersionedIndex.Next(); const auto index = nextVersion.GetIndex(); m_Resources[index].Version = nextVersion.GetVersion(); - m_DeferredResources[index] = true; m_DeferredReleasedHandles.push_back( FencedResource { @@ -176,9 +174,6 @@ namespace stf { const auto& releasedResource = ThrowIfUnexpected(m_DeferredReleasedHandles.pop_front()); const auto versionedIndex = releasedResource.VersionedIndex; - const u32 index = versionedIndex.GetIndex(); - - m_DeferredResources[index] = false; m_FreeList.push_back(versionedIndex); } } @@ -196,7 +191,6 @@ namespace stf }; std::vector m_Resources; - std::vector m_DeferredResources; RingBuffer m_DeferredReleasedHandles; RingBuffer m_FreeList; diff --git a/src/Public/D3D12/Shader/ShaderBindingMap.h b/src/Public/D3D12/Shader/ShaderBindingMap.h index 7de4590b..7df269b6 100644 --- a/src/Public/D3D12/Shader/ShaderBindingMap.h +++ b/src/Public/D3D12/Shader/ShaderBindingMap.h @@ -26,6 +26,12 @@ namespace stf 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: @@ -53,10 +59,7 @@ namespace stf ExpectedError StageBindingData(const ShaderBinding& InBinding); template - requires requires(T InFunc, u32 InRootParamIndex, StagingInfo InStagingInfo) - { - { InFunc(InRootParamIndex, InStagingInfo) } -> std::same_as; - } + requires StagingBufferFunctionType void ForEachStagingBuffer(T&& InFunc) { for (const auto& [rootParamIndex, stagingInfo] : m_RootParamBuffers) diff --git a/src/Public/Framework/ShaderTestCommon.h b/src/Public/Framework/ShaderTestCommon.h index 857f9db6..e949adfb 100644 --- a/src/Public/Framework/ShaderTestCommon.h +++ b/src/Public/Framework/ShaderTestCommon.h @@ -56,15 +56,6 @@ namespace stf friend std::ostream& operator<<(std::ostream& InOs, const TestRunResults& In); }; - enum class ETestRunErrorType - { - Unknown, - DescriptorManagement, - ShaderCompilation, - Binding, - RootSignatureGeneration - }; - class Results { public: diff --git a/src/Public/Framework/ShaderTestDescriptorManager.h b/src/Public/Framework/ShaderTestDescriptorManager.h deleted file mode 100644 index 91912d63..00000000 --- a/src/Public/Framework/ShaderTestDescriptorManager.h +++ /dev/null @@ -1,67 +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 - : public Object - { - public: - - struct CreationParams - { - SharedPtr Device; - u32 InitialSize = 16u; - }; - - enum class EErrorType - { - Unknown, - AllocatorFull, - AttemptedShrink, - DescriptorAlreadyFree - }; - - template - using Expected = Expected; - - ShaderTestDescriptorManager(ObjectToken, 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 f39adb06..00c60d85 100644 --- a/src/Public/Framework/ShaderTestDriver.h +++ b/src/Public/Framework/ShaderTestDriver.h @@ -5,8 +5,7 @@ #include "D3D12/Shader/PipelineState.h" #include "D3D12/Shader/RootSignature.h" -#include "Framework/ShaderTestDescriptorManager.h" -#include "Framework/ShaderTestShader.h" +#include "Framework/ShaderTestCommon.h" #include "Framework/TestDataBufferLayout.h" #include "Framework/TypeByteReader.h" @@ -33,7 +32,7 @@ namespace stf struct TestDesc { - ShaderTestShader& Shader; + SharedPtr Shader; const TestDataBufferLayout& TestBufferLayout; std::vector Bindings; std::string_view TestName; @@ -42,9 +41,6 @@ namespace stf ShaderTestDriver(ObjectToken, 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); - TypeReaderIndex RegisterByteReader(std::string InTypeIDName, MultiTypeByteReader InByteReader); TypeReaderIndex RegisterByteReader(std::string InTypeIDName, SingleTypeByteReader InByteReader); @@ -57,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 6a642c42..80ee7e8a 100644 --- a/src/Public/Framework/ShaderTestFixture.h +++ b/src/Public/Framework/ShaderTestFixture.h @@ -4,7 +4,6 @@ #include "D3D12/Shader/ShaderBinding.h" #include "D3D12/Shader/ShaderCompiler.h" #include "Framework/ShaderTestDriver.h" -#include "Framework/ShaderTestShader.h" #include "Framework/TestDataBufferLayout.h" #include "Stats/StatSystem.h" #include "Utility/Error.h" @@ -119,7 +118,6 @@ namespace stf Results RunTestImpl(RuntimeTestDesc InTestDesc, const bool InIsFailureRetry); ExpectedError CompileShader(const std::string_view InName, const EShaderType InType, CompilationEnvDesc InCompileDesc, const bool InTakingCapture) const; - ExpectedError> CreateTestShader(const CompiledShaderData& InCompiledShaderData) 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 df3a0219..00000000 --- a/src/Public/Framework/ShaderTestShader.h +++ /dev/null @@ -1,57 +0,0 @@ - -#pragma once - -#include "Platform.h" - -#include "D3D12/CommandEngine.h" -#include "D3D12/CommandList.h" -#include "D3D12/GPUDevice.h" -#include "D3D12/Shader/CompiledShaderData.h" -#include "D3D12/Shader/RootSignature.h" -#include "D3D12/Shader/Shader.h" - -#include "Framework/ShaderTestCommon.h" -#include "Utility/Expected.h" -#include "Utility/HLSLTypes.h" -#include "Utility/Object.h" -#include "Utility/TransparentStringHash.h" - -#include -#include -#include - -namespace stf -{ - class ShaderTestShader - : public Object - { - public: - struct CreationParams - { - SharedPtr Shader; - }; - - struct TestBindings - { - uint3 DispatchConfig{0}; - u32 AllocationBufferIndex = 0; - u32 TestDataBufferIndex = 0; - TestDataBufferLayout TestDataLayout{}; - }; - - ShaderTestShader(ObjectToken, const CreationParams& InParams); - - ExpectedError StageConstantBufferData(const TestBindings& InTestBindings, const std::span InBindings); - void CommitBindings(ScopedCommandContext& InList) const; - - uint3 GetThreadGroupSize() const; - - const RootSignature& GetRootSig() const; - - IDxcBlob* GetCompiledShader() const; - - private: - - SharedPtr m_Shader; - }; -} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e0519920..51a061e2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -202,7 +202,6 @@ set(SOURCES Private/Framework/HLSLFramework/ThreadIdRegistrationTests.cpp Private/Framework/HLSLFramework/TTLTypeTraitsTests.cpp Private/Framework/SectionsInHLSLProofOfConceptTests.cpp - Private/Framework/ShaderTestDescriptorManagerTests.cpp Private/Framework/ShaderTestFixtureTests.cpp Private/Framework/TestDataBufferLayoutTests.cpp Private/Framework/TestDataBufferProcessorTests.cpp diff --git a/test/Private/D3D12/FencedResourceFreeListTests.cpp b/test/Private/D3D12/FencedResourceFreeListTests.cpp index f8e1762e..7b3ec7f2 100644 --- a/test/Private/D3D12/FencedResourceFreeListTests.cpp +++ b/test/Private/D3D12/FencedResourceFreeListTests.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -64,6 +65,21 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence 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") @@ -107,10 +123,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence WHEN("Resource requested") { - const auto firstHandle = freeList.Manage(resourceGenerator()); - - REQUIRE(freeList.ValidateHandle(firstHandle)); - const auto firstResource = getResource(freeList, firstHandle); + const auto [firstResource, firstHandle] = manageResource(freeList); AND_WHEN("resource is immediately released with no GPU work") { @@ -127,9 +140,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is acquired again") { - const auto secondHandle = freeList.Manage(resourceGenerator()); - REQUIRE(freeList.ValidateHandle(secondHandle)); - const auto secondResource = getResource(freeList, secondHandle); + const auto [secondResource, secondHandle] = manageResource(freeList); THEN("first and second resource are different") { @@ -160,9 +171,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is requested again") { - const auto secondHandle = freeList.Manage(resourceGenerator()); - REQUIRE(freeList.ValidateHandle(secondHandle)); - const auto secondResource = getResource(freeList, secondHandle); + const auto [secondResource, secondHandle] = manageResource(freeList); THEN("second handle is to a different resource from the first") { @@ -171,9 +180,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("yet another resource requested") { - const auto thirdHandle = freeList.Manage(resourceGenerator()); - REQUIRE(freeList.ValidateHandle(thirdHandle)); - const auto thirdResource = getResource(freeList, thirdHandle); + const auto [thirdResource, thirdHandle] = manageResource(freeList); THEN("third resource is different from the second") { @@ -194,9 +201,7 @@ TEST_CASE_PERSISTENT_FIXTURE(FencedResourceFreeListTestFixture, "Scenario: Fence AND_WHEN("resource is requested again") { - const auto secondHandle = freeList.Manage(resourceGenerator()); - REQUIRE(freeList.ValidateHandle(secondHandle)); - const auto secondResource = getResource(freeList, secondHandle); + const auto [secondResource, secondHandle] = manageResource(freeList); THEN("second handle is to a different resource from the first") { diff --git a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp b/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp deleted file mode 100644 index d9d4bc8d..00000000 --- a/test/Private/Framework/ShaderTestDescriptorManagerTests.cpp +++ /dev/null @@ -1,259 +0,0 @@ - -#include -#include -#include -#include - -#include -#include - -namespace ShaderTestDescriptorManagerTestPrivate -{ - class Fixture - { - public: - - Fixture() - : device(stf::Object::New( - stf::GPUDevice::CreationParams - { - })) - , resource(device->CreateCommittedResource( - stf::GPUDevice::CommittedResourceDesc - { - .HeapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), - .ResourceDesc = CD3DX12_RESOURCE_DESC1::Buffer(1024, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) - })) - , 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(ShaderTestDescriptorManagerTestPrivate::Fixture, "Shader Test Descriptor Manager Tests") -{ - using namespace stf; - GIVEN("An initial size of 1") - { - auto manager = Object::New( - 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 = Object::New( - 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 From fe5b4c117290d950bce49b225aa7263c7428095d Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 17 Jan 2026 13:43:22 +0000 Subject: [PATCH 74/76] Fixing typo in Bindings docs --- docs/STF/Bindings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 99f6cc33523e816ac7a9a5f6cbebe6d254a8807b Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 17 Jan 2026 14:07:50 +0000 Subject: [PATCH 75/76] Just make tuple use std::tuple for Clang 19 since clang 19 just can't handle tuples implemented as an aggregate --- src/Public/Utility/Tuple.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 From 7ab1ce5e48547140961e7f4507a556d231c3712a Mon Sep 17 00:00:00 2001 From: KStocky Date: Sat, 17 Jan 2026 14:24:22 +0000 Subject: [PATCH 76/76] Upping the minimum MSVC version to 14.41 because this code https://godbolt.org/z/Pc9PsGnsq, does not compile until 14.41. I think it is due to using deducing this in a lambda... --- .github/workflows/TestCompilerSupport.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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: