Add render graph proof of concept and refactor graphics

Implemented a transient render graph system as a proof of concept, including resource aliasing, pass culling, and typed pass data. Added new project `Ghost.RenderGraph.Concept` targeting `.NET 10.0`.

Refactored graphics-related components:
- Simplified resource state transitions in `RenderingContext`.
- Improved resize handling in `GraphicsTestWindow`.
- Updated `D3D12GraphicsEngine` to streamline frame rendering.
- Enhanced `D3D12ResourceDatabase` and `D3D12SwapChain` for better resource management.

Added detailed documentation:
- `ALIASING.md` explains resource aliasing techniques.
- `API_DESIGN.md` outlines the render graph API design.

Updated solution to include the new render graph project.
This commit is contained in:
2025-12-01 22:31:17 +09:00
parent 85280c746d
commit 676f8bb74c
31 changed files with 2167 additions and 142 deletions

View File

@@ -291,6 +291,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
ref var record = ref recordResult.Value;
if (record.state == stateAfter)
{
return;
}
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
record.state.ToD3D12States(), stateAfter.ToD3D12States());

View File

@@ -132,7 +132,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
ObjectDisposedException.ThrowIf(_disposed, this);
_fenceValue = value;
_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue);
ThrowIfFailed(_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue));
return _fenceValue;
}

View File

@@ -9,6 +9,8 @@ namespace Ghost.Graphics.D3D12;
internal class D3D12GraphicsEngine : IGraphicsEngine
{
private readonly IRenderSystem _renderSystem;
#if DEBUG
private readonly D3D12DebugLayer _debugLayer;
#endif
@@ -34,6 +36,8 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public D3D12GraphicsEngine(IRenderSystem renderSystem)
{
_renderSystem = renderSystem;
#if DEBUG
_debugLayer = new D3D12DebugLayer();
#endif
@@ -106,10 +110,10 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public ISwapChain CreateSwapChain(SwapChainDesc desc)
{
ThrowIfDisposed();
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc);
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency);
}
public void BeginFrame()
public void RenderFrame()
{
ThrowIfDisposed();
@@ -119,21 +123,11 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
}
_copyCommandBuffer.Begin();
}
public void RenderFrame()
{
ThrowIfDisposed();
foreach (var renderer in _renderers)
{
renderer.Render();
}
}
public void EndFrame()
{
ThrowIfDisposed();
_copyCommandBuffer.End().ThrowIfFailed();
_resourceAllocator.ReleaseTempResources();

View File

@@ -136,7 +136,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
resource = default!;
}
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string name = "")
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -144,14 +144,17 @@ internal class D3D12ResourceDatabase : IResourceDatabase
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
pResource->SetName(name);
_resourceName[handle] = name;
if (!string.IsNullOrEmpty(name))
{
pResource->SetName(name);
_resourceName[handle] = name;
}
#endif
return handle;
}
public unsafe Handle<GPUResource> AddResource(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name = "")
public unsafe Handle<GPUResource> AddResource(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -159,8 +162,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
allocation->SetName(name);
_resourceName[handle] = name;
if (!string.IsNullOrEmpty(name))
{
allocation->SetName(name);
_resourceName[handle] = name;
}
#endif
return handle;

View File

@@ -42,26 +42,20 @@ internal unsafe class D3D12SwapChain : ISwapChain
get; private set;
}
public uint BufferCount
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
{
get;
}
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc)
{
Debug.Assert(desc.BufferCount >= 2);
Debug.Assert(bufferCount >= 2);
_resourceDatabase = resourceDatabase;
_descriptorAllocator = descriptorAllocator;
_renderDevice = device;
_backBuffers = new UnsafeArray<Handle<Texture>>((int)desc.BufferCount, Allocator.Persistent);
_backBuffers = new UnsafeArray<Handle<Texture>>((int)bufferCount, Allocator.Persistent);
Width = desc.Width;
Height = desc.Height;
BufferCount = desc.BufferCount;
CreateSwapChain(desc);
CreateSwapChain(desc, bufferCount);
CreateBackBuffers();
_compositionSurface = desc.Target.CompositionSurface;
@@ -72,7 +66,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
Dispose();
}
private void CreateSwapChain(SwapChainDesc desc)
private void CreateSwapChain(SwapChainDesc desc, uint bufferCount)
{
var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1
{
@@ -81,7 +75,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
Format = desc.Format.ToDXGIFormat(),
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = desc.BufferCount,
BufferCount = bufferCount,
Scaling = DXGI_SCALING_STRETCH,
SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
AlphaMode = DXGI_ALPHA_MODE_IGNORE,
@@ -135,7 +129,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
private void CreateBackBuffers()
{
for (uint i = 0; i < BufferCount; i++)
for (uint i = 0; i < _backBuffers.Count; i++)
{
ID3D12Resource* pBackBuffer = default;
ThrowIfFailed(_swapChain.Get()->GetBuffer(i, __uuidof(pBackBuffer), (void**)&pBackBuffer));
@@ -186,12 +180,26 @@ internal unsafe class D3D12SwapChain : ISwapChain
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
}
ThrowIfFailed(_swapChain.Get()->ResizeBuffers(BufferCount, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING));
ThrowIfFailed(_swapChain.Get()->ResizeBuffers((uint)_backBuffers.Count, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING));
Width = width;
Height = height;
CreateBackBuffers();
//float inverseScale = 1.0f / scale;
//DXGI_MATRIX_3X2_F inverseScaleMatrix = new DXGI_MATRIX_3X2_F
//{
// _11 = inverseScale, // Scale X
// _22 = inverseScale, // Scale Y
// _12 = 0.0f,
// _21 = 0.0f,
// _31 = 0.0f, // Offset X
// _32 = 0.0f // Offset Y
//};
//_swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix);
}
public void Dispose()

View File

@@ -11,7 +11,7 @@ internal unsafe static class D3D12Utility
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
where T : unmanaged, ID3D12Object.Interface
{
if (name.Length == 0)
if (name.IsEmpty)
{
return;
}
@@ -24,6 +24,11 @@ internal unsafe static class D3D12Utility
public static void SetName(ref this D3D12MA_Allocation obj, ReadOnlySpan<char> name)
{
if (name.IsEmpty)
{
return;
}
fixed (char* pName = name)
{
obj.SetName(pName);
@@ -84,6 +89,9 @@ internal unsafe static class D3D12Utility
ResourceState.PixelShaderResource => D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState.CopyDest => D3D12_RESOURCE_STATE_COPY_DEST,
ResourceState.CopySource => D3D12_RESOURCE_STATE_COPY_SOURCE,
ResourceState.GenericRead => D3D12_RESOURCE_STATE_GENERIC_READ,
ResourceState.IndirectArgument => D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT,
ResourceState.NonPixelShaderResource => D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
}