feat: Implement D3D12 resource factory and improve swap chain management

- Added D3D12ResourceFactory for creating render targets, textures, and buffers.
- Enhanced D3D12SwapChain to manage back buffer render targets and provide access to them.
- Updated D3D12Texture to utilize resource handles for better resource management.
- Removed legacy ResourceAllocator and integrated improvements for resource handling.
- Introduced new interfaces for resource factory and swap chain to streamline resource creation.
- Added support for mip levels and texture dimensions in render target and texture descriptions.
- Created new markdown files to document allocator and swap chain improvements.
This commit is contained in:
2025-09-02 19:39:34 +09:00
parent 5385141f14
commit 78cc64b1d2
20 changed files with 848 additions and 680 deletions

View File

@@ -1,3 +1,4 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -5,11 +6,12 @@ using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of buffer interface
/// D3D12 implementation of buffer interface using resource handles
/// </summary>
internal unsafe class D3D12Buffer : IBuffer
{
private ComPtr<ID3D12Resource> _resource;
private readonly BufferHandle _handle;
private readonly ComPtr<ID3D12Resource> _externalResource; // For externally managed resources
private ResourceState _currentState;
private void* _mappedPtr;
private bool _disposed;
@@ -18,64 +20,56 @@ internal unsafe class D3D12Buffer : IBuffer
{
get;
}
public string Name { get; set; } = string.Empty;
public MemoryType MemoryType
{
get;
}
public string Name
{
get => field;
set
{
field = value;
NativeResource->SetName(field);
}
} = string.Empty;
public ulong Size
{
get;
}
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _resource.Get();
public ID3D12Resource* NativeResource => _handle.ResourceHandle.GetAllocation().Resource;
public D3D12Buffer(ComPtr<ID3D12Device14> device, BufferDesc desc)
/// <summary>
/// Constructor for wrapping existing D3D12 resources
/// </summary>
public D3D12Buffer(ComPtr<ID3D12Resource> resource, ulong size, BufferUsage usage, MemoryType memoryType)
{
Usage = desc.Usage;
Size = desc.Size;
_currentState = ResourceState.Common;
_handle = BufferHandle.Invalid;
_externalResource = resource.Move();
CreateBuffer(device, desc);
Size = size;
Usage = usage;
MemoryType = memoryType;
_currentState = ResourceState.Common;
}
private void CreateBuffer(ComPtr<ID3D12Device14> device, BufferDesc desc)
/// <summary>
/// Constructor for allocator-managed buffers
/// </summary>
public D3D12Buffer(BufferHandle handle, ref readonly BufferDesc desc)
{
var resourceDesc = new ResourceDescription
{
Dimension = ResourceDimension.Buffer,
Alignment = 0,
Width = desc.Size,
Height = 1,
DepthOrArraySize = 1,
MipLevels = 1,
Format = Win32.Graphics.Dxgi.Common.Format.Unknown,
SampleDesc = new Win32.Graphics.Dxgi.Common.SampleDescription(1, 0),
Layout = TextureLayout.RowMajor,
Flags = ConvertBufferUsage(desc.Usage)
};
_handle = handle;
var heapProps = new HeapProperties
{
Type = ConvertMemoryType(desc.MemoryType),
CPUPageProperty = CpuPageProperty.Unknown,
MemoryPoolPreference = MemoryPool.Unknown,
CreationNodeMask = 1,
VisibleNodeMask = 1
};
var initialState = desc.MemoryType switch
{
MemoryType.Upload => Win32.Graphics.Direct3D12.ResourceStates.GenericRead,
MemoryType.Readback => Win32.Graphics.Direct3D12.ResourceStates.CopyDest,
_ => Win32.Graphics.Direct3D12.ResourceStates.Common
};
device.Get()->CreateCommittedResource(
&heapProps,
HeapFlags.None,
&resourceDesc,
initialState,
null,
__uuidof<ID3D12Resource>(),
_resource.GetVoidAddressOf());
Size = desc.Size;
Usage = desc.Usage;
MemoryType = desc.MemoryType;
_currentState = ResourceState.Common;
}
public void* Map()
@@ -83,11 +77,15 @@ internal unsafe class D3D12Buffer : IBuffer
if (_mappedPtr != null)
return _mappedPtr;
var range = new Win32.Graphics.Direct3D12.Range { Begin = 0, End = 0 };
if (MemoryType != MemoryType.Upload && MemoryType != MemoryType.Readback)
{
throw new InvalidOperationException("Only upload and readback buffers can be mapped");
}
var range = new Win32.Graphics.Direct3D12.Range { Begin = 0, End = 0 };
fixed (void** ptr = &_mappedPtr)
{
_resource.Get()->Map(0, &range, ptr);
NativeResource->Map(0, &range, ptr);
}
return _mappedPtr;
}
@@ -96,30 +94,14 @@ internal unsafe class D3D12Buffer : IBuffer
{
if (_mappedPtr != null)
{
_resource.Get()->Unmap(0, null);
NativeResource->Unmap(0, null);
_mappedPtr = null;
}
}
private static HeapType ConvertMemoryType(MemoryType memoryType)
public void SetCurrentState(ResourceState state)
{
return memoryType switch
{
MemoryType.Default => HeapType.Default,
MemoryType.Upload => HeapType.Upload,
MemoryType.Readback => HeapType.Readback,
_ => throw new ArgumentException($"Unknown memory type: {memoryType}")
};
}
private static ResourceFlags ConvertBufferUsage(BufferUsage usage)
{
var flags = ResourceFlags.None;
if ((usage & BufferUsage.Raw) != 0)
flags |= ResourceFlags.AllowUnorderedAccess;
return flags;
_currentState = state;
}
public void Dispose()
@@ -128,7 +110,17 @@ internal unsafe class D3D12Buffer : IBuffer
return;
Unmap();
_resource.Dispose();
if (_handle.IsValid)
{
_handle.Dispose();
}
else
{
// Release external resource
_externalResource.Dispose();
}
_disposed = true;
}
}

View File

@@ -1,6 +1,5 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Numerics;
@@ -133,27 +132,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
};
}
private static Win32.Graphics.Direct3D12.ResourceStates ConvertResourceState(ResourceState state)
private static ResourceStates ConvertResourceState(ResourceState state)
{
return state switch
{
ResourceState.Common or ResourceState.Present => Win32.Graphics.Direct3D12.ResourceStates.Common,
ResourceState.VertexAndConstantBuffer => Win32.Graphics.Direct3D12.ResourceStates.VertexAndConstantBuffer,
ResourceState.IndexBuffer => Win32.Graphics.Direct3D12.ResourceStates.IndexBuffer,
ResourceState.RenderTarget => Win32.Graphics.Direct3D12.ResourceStates.RenderTarget,
ResourceState.UnorderedAccess => Win32.Graphics.Direct3D12.ResourceStates.UnorderedAccess,
ResourceState.DepthWrite => Win32.Graphics.Direct3D12.ResourceStates.DepthWrite,
ResourceState.DepthRead => Win32.Graphics.Direct3D12.ResourceStates.DepthRead,
ResourceState.PixelShaderResource => Win32.Graphics.Direct3D12.ResourceStates.PixelShaderResource,
ResourceState.CopyDest => Win32.Graphics.Direct3D12.ResourceStates.CopyDest,
ResourceState.CopySource => Win32.Graphics.Direct3D12.ResourceStates.CopySource,
ResourceState.Common or ResourceState.Present => ResourceStates.Common,
ResourceState.VertexAndConstantBuffer => ResourceStates.VertexAndConstantBuffer,
ResourceState.IndexBuffer => ResourceStates.IndexBuffer,
ResourceState.RenderTarget => ResourceStates.RenderTarget,
ResourceState.UnorderedAccess => ResourceStates.UnorderedAccess,
ResourceState.DepthWrite => ResourceStates.DepthWrite,
ResourceState.DepthRead => ResourceStates.DepthRead,
ResourceState.PixelShaderResource => ResourceStates.PixelShaderResource,
ResourceState.CopyDest => ResourceStates.CopyDest,
ResourceState.CopySource => ResourceStates.CopySource,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
}
public void Dispose()
{
if (_disposed) return;
if (_disposed)
return;
_commandList.Dispose();
_allocator.Dispose();

View File

@@ -1,5 +1,3 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D;
@@ -17,10 +15,10 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
private ComPtr<ID3D12Device14> _device;
private ComPtr<IDXGIAdapter1> _adapter;
private D3D12CommandQueue _graphicsQueue;
private D3D12CommandQueue _computeQueue;
private D3D12CommandQueue _copyQueue;
private D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12CommandQueue _graphicsQueue;
private readonly D3D12CommandQueue _computeQueue;
private readonly D3D12CommandQueue _copyQueue;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private bool _disposed;
@@ -29,9 +27,9 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
public ICommandQueue CopyQueue => _copyQueue;
public IDescriptorAllocator DescriptorAllocator => _descriptorAllocator;
public ConstPtr<ID3D12Device14> NativeDevice => new(_device.Get());
public ConstPtr<IDXGIFactory7> DXGIFactory => new(_dxgiFactory.Get());
public ConstPtr<IDXGIAdapter1> Adapter => new(_adapter.Get());
public ID3D12Device14* NativeDevice => _device.Get();
public IDXGIFactory7* DXGIFactory => _dxgiFactory.Get();
public IDXGIAdapter1* Adapter => _adapter.Get();
public D3D12RenderDevice()
{
@@ -90,24 +88,10 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
return new D3D12SwapChain(_dxgiFactory, _graphicsQueue.NativeQueue, desc);
}
public IRenderTarget CreateRenderTarget(RenderTargetDesc desc)
{
return new D3D12RenderTarget(_device, _descriptorAllocator, desc);
}
public ITexture CreateTexture(TextureDesc desc)
{
return new D3D12Texture(_device, desc);
}
public IBuffer CreateBuffer(BufferDesc desc)
{
return new D3D12Buffer(_device, desc);
}
public void Dispose()
{
if (_disposed) return;
if (_disposed)
return;
_descriptorAllocator?.Dispose();
_graphicsQueue?.Dispose();

View File

@@ -1,6 +1,5 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
@@ -13,12 +12,27 @@ internal unsafe class D3D12RenderTarget : IRenderTarget
private readonly D3D12Texture _target;
private bool _disposed;
public uint Width { get; }
public uint Height { get; }
public RenderTargetType Type { get; }
public uint Width
{
get;
}
public uint Height
{
get;
}
public RenderTargetType Type
{
get;
}
public ITexture Target => _target;
public D3D12RenderTarget(ComPtr<ID3D12Device14> device, D3D12DescriptorAllocator descriptorAllocator, RenderTargetDesc desc)
/// <summary>
/// Create a new render target with its own texture
/// </summary>
public D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc)
{
Width = desc.Width;
Height = desc.Height;
@@ -26,13 +40,27 @@ internal unsafe class D3D12RenderTarget : IRenderTarget
// Create the target texture based on type
var usage = Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
var textureDesc = new TextureDesc(desc.Width, desc.Height, desc.Format, 1, usage);
_target = new D3D12Texture(device, textureDesc);
var textureDesc = new TextureDesc(desc.Width, desc.Height, desc.Format, desc.Dimension, desc.MipLevels, usage);
_target = new D3D12Texture(handle, in textureDesc);
}
/// <summary>
/// Wrap an existing texture as a render target (for swap chain back buffers)
/// </summary>
public D3D12RenderTarget(D3D12Texture existingTexture, RenderTargetType type)
{
_target = existingTexture;
Width = existingTexture.Width;
Height = existingTexture.Height;
Type = type;
}
public void Dispose()
{
if (_disposed) return;
if (_disposed)
{
return;
}
_target?.Dispose();
_disposed = true;

View File

@@ -26,13 +26,13 @@ public unsafe class D3D12Renderer : IRenderer
}
}
private readonly IRenderDevice _device;
private readonly ICommandQueue _commandQueue;
private readonly FrameResource[] _frameResources;
private uint _frameIndex;
private IRenderTarget? _renderTarget;
private IRenderTarget? _destinationTarget; // Final destination (custom render target or swap chain back buffer)
private ISwapChain? _swapChain;
private IRenderTarget? _offScreenRenderTarget; // Always render to off-screen first
private readonly Lock _lock = new();
private uint _pendingWidth;
@@ -45,7 +45,6 @@ public unsafe class D3D12Renderer : IRenderer
public D3D12Renderer(IRenderDevice device)
{
_device = device;
_commandQueue = device.GraphicsQueue;
// Create frame resources for double buffering
@@ -58,16 +57,37 @@ public unsafe class D3D12Renderer : IRenderer
public void SetRenderTarget(IRenderTarget? renderTarget)
{
_renderTarget = renderTarget;
_swapChain = null; // Clear swap chain when using render target
_destinationTarget = renderTarget;
_swapChain = null; // Clear swap chain when using custom render target
// Create or update off-screen render target to match destination size
if (_destinationTarget != null)
{
CreateOrUpdateOffScreenRenderTarget(_destinationTarget.Width, _destinationTarget.Height);
}
else
{
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
}
}
public void SetSwapChain(ISwapChain? swapChain)
{
_swapChain = swapChain;
_renderTarget = null; // Clear render target when using swap chain
}
_destinationTarget = null; // Clear custom render target when using swap chain
// Create or update off-screen render target to match swap chain size
if (_swapChain != null)
{
CreateOrUpdateOffScreenRenderTarget(_swapChain.Width, _swapChain.Height);
}
else
{
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
}
}
public void RequestResize(uint width, uint height)
{
lock (_lock)
@@ -99,6 +119,12 @@ public unsafe class D3D12Renderer : IRenderer
// Resize swap chain if present
_swapChain?.Resize(newWidth, newHeight);
// Update off-screen render target size
if (_swapChain != null)
{
CreateOrUpdateOffScreenRenderTarget(newWidth, newHeight);
}
}
public void Render()
@@ -118,17 +144,31 @@ public unsafe class D3D12Renderer : IRenderer
// Begin command recording
frame.CommandBuffer.Begin();
if (_renderTarget != null)
// Determine the final destination target
IRenderTarget? finalDestination = null;
ITexture? swapChainBackBuffer = null;
if (_destinationTarget != null)
{
RenderToTarget(_renderTarget, frame.CommandBuffer);
// Rendering to custom render target
finalDestination = _destinationTarget;
}
else if (_swapChain != null)
{
RenderToSwapChain(_swapChain, frame.CommandBuffer);
// Rendering to swap chain - get back buffer as render target
finalDestination = _swapChain.GetCurrentBackBufferRenderTarget();
swapChainBackBuffer = _swapChain.GetCurrentBackBuffer();
}
if (finalDestination != null && _offScreenRenderTarget != null)
{
// Always render to off-screen first, then blit to final destination
RenderScene(_offScreenRenderTarget, frame.CommandBuffer);
BlitToDestination(_offScreenRenderTarget, finalDestination, swapChainBackBuffer, frame.CommandBuffer);
}
else
{
// No render target - skip rendering
// No destination - skip rendering
frame.CommandBuffer.End();
return;
}
@@ -146,7 +186,7 @@ public unsafe class D3D12Renderer : IRenderer
frame.FenceValue = _commandQueue.Signal(++_frameIndex);
}
private void RenderToTarget(IRenderTarget target, ICommandBuffer cmd)
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
{
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
@@ -167,31 +207,47 @@ public unsafe class D3D12Renderer : IRenderer
cmd.EndRenderPass();
}
private void RenderToSwapChain(ISwapChain swapChain, ICommandBuffer cmd)
private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ITexture? swapChainBackBuffer, ICommandBuffer cmd)
{
var backBuffer = swapChain.GetCurrentBackBuffer();
// Handle swap chain back buffer transitions if needed
if (swapChainBackBuffer != null)
{
// Transition back buffer to render target
cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.Present, ResourceState.RenderTarget);
}
// Transition back buffer to render target
cmd.ResourceBarrier(backBuffer, ResourceState.Present, ResourceState.RenderTarget);
// For now, we'll do a simple copy operation
// In a real implementation, you would use a blit shader for post-processing
// Create temporary render target for back buffer
// TODO: This should be cached/reused
var renderTarget = CreateBackBufferRenderTarget(backBuffer);
// TODO: Implement proper blit operation with shader
// This is a placeholder - in D3D12, you would typically:
// 1. Set render target to the destination
// 2. Use a full-screen quad/triangle with a shader that samples from the source
// 3. Apply post-processing effects (tone mapping, gamma correction, etc.)
RenderToTarget(renderTarget, cmd);
// For now, just clear the destination (this should be replaced with actual blit)
var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
cmd.BeginRenderPass(destination, clearColor);
cmd.EndRenderPass();
// Transition back buffer to present
cmd.ResourceBarrier(backBuffer, ResourceState.RenderTarget, ResourceState.Present);
renderTarget.Dispose();
// Handle swap chain back buffer transitions if needed
if (swapChainBackBuffer != null)
{
// Transition back buffer to present
cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.RenderTarget, ResourceState.Present);
}
}
private IRenderTarget CreateBackBufferRenderTarget(ITexture backBuffer)
private void CreateOrUpdateOffScreenRenderTarget(uint width, uint height)
{
// TODO: Create render target from back buffer texture
// This is a simplified implementation
var desc = RenderTargetDesc.Color(backBuffer.Width, backBuffer.Height, backBuffer.Format);
return _device.CreateRenderTarget(desc);
// Check if we need to recreate the off-screen render target
if (_offScreenRenderTarget == null || _offScreenRenderTarget.Width != width || _offScreenRenderTarget.Height != height)
{
_offScreenRenderTarget?.Dispose();
var desc = RenderTargetDesc.Color(width, height, TextureFormat.B8G8R8A8_UNorm);
_offScreenRenderTarget = _device.CreateRenderTarget(desc);
}
}
public void WaitIdle()
@@ -217,6 +273,8 @@ public unsafe class D3D12Renderer : IRenderer
frame.Dispose();
}
_offScreenRenderTarget?.Dispose();
_disposed = true;
}
}

View File

@@ -0,0 +1,342 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common;
using static Win32.Graphics.D3D12MemoryAllocator.Apis;
using ResourceHandle = Ghost.Graphics.Data.ResourceHandle;
namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12ResourceAllocator
{
private readonly struct AllocationInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public readonly uint generation;
public bool Allocated => allocation.IsNotNull;
public AllocationInfo(in Allocation allocation, uint cpuFenceValue, uint generation)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
this.generation = generation;
}
public void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
private readonly RenderSystem _renderSystem;
private readonly Allocator _allocator;
private UnsafeList<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<int> _freeSlots = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
{
get
{
fixed (Guid* pGuid = &Guid.Empty)
{
return pGuid;
}
}
}
public D3D12ResourceAllocator(IDXGIAdapter* pAdapter, ID3D12Device* pDevice, RenderSystem renderSystem)
{
_renderSystem = renderSystem;
var desc = new AllocatorDesc
{
pAdapter = pAdapter,
pDevice = pDevice,
Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted
};
CreateAllocator(in desc, out _allocator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckBufferSize(uint sizeInBytes)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckTexture2DSize(uint width, uint height)
{
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
{
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
}
}
private ResourceHandle TrackResource(in Allocation allocation, bool isTemp)
{
int id;
uint generation;
AllocationInfo allocInfo;
if (_freeSlots.Count > 0)
{
id = _freeSlots.Dequeue();
var info = _allocations[id];
if (info.Allocated)
{
throw new InvalidOperationException($"ERROR: Resource ID {id} registered as free but still allocated.");
}
generation = info.generation + 1;
allocInfo = new AllocationInfo(in allocation, _renderSystem.CPUFenceValue, generation);
_allocations[id] = allocInfo;
}
else
{
id = _allocations.Count;
generation = 0u;
allocInfo = new AllocationInfo(in allocation, _renderSystem.CPUFenceValue, generation);
_allocations.Add(allocInfo);
}
var handle = new ResourceHandle(id, generation);
if (isTemp)
{
_temResources.Enqueue(handle);
}
return handle;
}
public TextureHandle CreateTexture2D(in TextureDesc desc, bool tempResource = false)
{
CheckTexture2DSize(desc.Width, desc.Height);
var resourceDesc = ResourceDescription.Tex2D(
ConvertTextureFormat(desc.Format),
desc.Width,
desc.Height,
mipLevels: (ushort)desc.MipLevels,
arraySize: 1,
flags: ConvertTextureUsage(desc.Usage)
);
var allocationDesc = new AllocationDesc
{
HeapType = HeapType.Default,
Flags = AllocationFlags.None
};
var initialState = DetermineInitialTextureState(desc.Usage);
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource));
}
public BufferHandle CreateBuffer(in BufferDesc desc, bool tempResource = false)
{
CheckBufferSize((uint)desc.Size);
var resourceDescription = ResourceDescription.Buffer(desc.Size, ConvertBufferUsage(desc.Usage));
var allocationDesc = new AllocationDesc
{
HeapType = ConvertMemoryType(desc.MemoryType),
Flags = AllocationFlags.None
};
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource));
}
public BufferHandle CreateUploadBuffer(uint sizeInBytes, bool tempResource = false)
{
var desc = new BufferDesc(sizeInBytes, BufferUsage.Upload, MemoryType.Upload);
return CreateBuffer(in desc, tempResource);
}
#region Conversion Methods
private static Format ConvertTextureFormat(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => Format.R8G8B8A8Unorm,
TextureFormat.B8G8R8A8_UNorm => Format.B8G8R8A8Unorm,
TextureFormat.R16G16B16A16_Float => Format.R16G16B16A16Float,
TextureFormat.R32G32B32A32_Float => Format.R32G32B32A32Float,
TextureFormat.D24_UNorm_S8_UInt => Format.D24UnormS8Uint,
TextureFormat.D32_Float => Format.D32Float,
_ => throw new ArgumentException($"Unsupported texture format: {format}")
};
}
private static ResourceFlags ConvertTextureUsage(TextureUsage usage)
{
var flags = ResourceFlags.None;
if (usage.HasFlag(TextureUsage.RenderTarget))
flags |= ResourceFlags.AllowRenderTarget;
if (usage.HasFlag(TextureUsage.DepthStencil))
flags |= ResourceFlags.AllowDepthStencil;
if (usage.HasFlag(TextureUsage.UnorderedAccess))
flags |= ResourceFlags.AllowUnorderedAccess;
return flags;
}
private static ResourceFlags ConvertBufferUsage(BufferUsage usage)
{
var flags = ResourceFlags.None;
if (usage.HasFlag(BufferUsage.Raw))
flags |= ResourceFlags.AllowUnorderedAccess;
return flags;
}
private static HeapType ConvertMemoryType(MemoryType memoryType)
{
return memoryType switch
{
MemoryType.Default => HeapType.Default,
MemoryType.Upload => HeapType.Upload,
MemoryType.Readback => HeapType.Readback,
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
};
}
private static ResourceStates DetermineInitialTextureState(TextureUsage usage)
{
if (usage.HasFlag(TextureUsage.RenderTarget))
return ResourceStates.RenderTarget;
if (usage.HasFlag(TextureUsage.DepthStencil))
return ResourceStates.DepthWrite;
if (usage.HasFlag(TextureUsage.UnorderedAccess))
return ResourceStates.UnorderedAccess;
return ResourceStates.Common;
}
private static ResourceStates DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType)
{
if (memoryType == MemoryType.Upload)
return ResourceStates.GenericRead;
if (memoryType == MemoryType.Readback)
return ResourceStates.CopyDest;
if (usage.HasFlag(BufferUsage.Vertex))
return ResourceStates.VertexAndConstantBuffer;
if (usage.HasFlag(BufferUsage.Index))
return ResourceStates.IndexBuffer;
if (usage.HasFlag(BufferUsage.Constant))
return ResourceStates.VertexAndConstantBuffer;
return ResourceStates.Common;
}
#endregion
public void ReleaseTempResource()
{
while (_temResources.Count > 0)
{
ref var handle = ref _temResources.Peek();
ref var info = ref _allocations[handle.id];
if (info.Allocated && info.cpuFenceValue > _renderSystem.CPUFenceValue)
{
break;
}
ReleaseAllocation(in handle);
_temResources.Dequeue();
}
}
public Allocation GetAllocation(in ResourceHandle handle)
{
if (!handle.IsValid)
{
throw new InvalidOperationException("Invalid resource handle.");
}
ref var allocationInfo = ref _allocations[handle.id];
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
{
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
}
return allocationInfo.allocation;
}
public void ReleaseAllocation(in ResourceHandle handle)
{
if (!handle.IsValid)
{
return;
}
ref var allocationInfo = ref _allocations[handle.id];
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
{
return;
}
allocationInfo.Dispose();
_freeSlots.Enqueue(handle.id);
}
public void Dispose()
{
#if DEBUG
if (_allocations.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing.");
}
#endif
for (var i = 0; i < _allocations.Count; i++)
{
_allocations[i].Dispose();
}
_allocations.Dispose();
_temResources.Dispose();
_allocator.Release();
}
}

View File

@@ -0,0 +1,53 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceFactory : IResourceFactory
{
private readonly D3D12ResourceAllocator _allocator;
public unsafe D3D12ResourceFactory(D3D12RenderDevice device, RenderSystem renderSystem)
{
_allocator = new D3D12ResourceAllocator((IDXGIAdapter*)device.Adapter, (ID3D12Device*)device.NativeDevice, renderSystem);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc)
{
var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
{
usage |= TextureUsage.UnorderedAccess;
}
var textureDesc = new TextureDesc(desc.Width, desc.Height, desc.Format, desc.Dimension, 1, usage);
return new D3D12RenderTarget(CreateTextureHandle(in textureDesc), in desc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc)
{
return _allocator.CreateTexture2D(in desc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ITexture CreateTexture(ref readonly TextureDesc desc)
{
return new D3D12Texture(CreateTextureHandle(in desc), in desc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc)
{
return _allocator.CreateBuffer(in desc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IBuffer CreateBuffer(ref readonly BufferDesc desc)
{
throw new NotImplementedException();
}
}

View File

@@ -1,6 +1,5 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI;
using Ghost.Graphics.D3D12.Utilities;
using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
@@ -15,16 +14,27 @@ internal unsafe class D3D12SwapChain : ISwapChain
{
private ComPtr<IDXGISwapChain4> _swapChain;
private readonly D3D12Texture[] _backBuffers;
private readonly D3D12RenderTarget[] _backBufferRenderTargets;
private uint _currentBackBufferIndex;
private bool _disposed;
public uint Width { get; private set; }
public uint Height { get; private set; }
public uint BufferCount { get; }
public uint Width
{
get; private set;
}
public uint Height
{
get; private set;
}
public uint BufferCount
{
get;
}
public D3D12SwapChain(ComPtr<IDXGIFactory7> factory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc)
{
_backBuffers = new D3D12Texture[desc.BufferCount];
_backBufferRenderTargets = new D3D12RenderTarget[desc.BufferCount];
Width = desc.Width;
Height = desc.Height;
@@ -102,6 +112,9 @@ internal unsafe class D3D12SwapChain : ISwapChain
backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}");
_backBuffers[i] = new D3D12Texture(backBuffer.Move(), Width, Height, TextureFormat.B8G8R8A8_UNorm);
// Create render target wrapper for the back buffer
_backBufferRenderTargets[i] = new D3D12RenderTarget(_backBuffers[i], RenderTargetType.Color);
}
}
@@ -111,6 +124,12 @@ internal unsafe class D3D12SwapChain : ISwapChain
return _backBuffers[_currentBackBufferIndex];
}
public IRenderTarget GetCurrentBackBufferRenderTarget()
{
_currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
return _backBufferRenderTargets[_currentBackBufferIndex];
}
public void Present(bool vsync = true)
{
var presentFlags = PresentFlags.None;
@@ -127,9 +146,10 @@ internal unsafe class D3D12SwapChain : ISwapChain
if (Width == width && Height == height)
return;
// Release old back buffers
for (int i = 0; i < _backBuffers.Length; i++)
// Release old back buffers and render targets
for (var i = 0; i < _backBuffers.Length; i++)
{
_backBufferRenderTargets[i]?.Dispose();
_backBuffers[i]?.Dispose();
}
@@ -161,14 +181,18 @@ internal unsafe class D3D12SwapChain : ISwapChain
public void Dispose()
{
if (_disposed) return;
for (int i = 0; i < _backBuffers.Length; i++)
if (_disposed)
{
return;
}
for (var i = 0; i < _backBuffers.Length; i++)
{
_backBufferRenderTargets[i]?.Dispose();
_backBuffers[i]?.Dispose();
}
_swapChain.Dispose();
_disposed = true;
}
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -5,110 +6,85 @@ using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of texture interface
/// D3D12 implementation of texture interface using resource handles
/// </summary>
internal unsafe class D3D12Texture : ITexture
{
private ComPtr<ID3D12Resource> _resource;
private readonly TextureHandle _handle;
private readonly ComPtr<ID3D12Resource> _externalResource;
private ResourceState _currentState;
private bool _disposed;
public uint Width { get; }
public uint Height { get; }
public TextureFormat Format { get; }
public uint MipLevels { get; }
public string Name { get; set; } = string.Empty;
public ulong Size { get; }
public uint Width
{
get;
}
public uint Height
{
get;
}
public TextureFormat Format
{
get;
}
public uint MipLevels
{
get;
}
public string Name
{
get; set;
} = string.Empty;
public ulong Size
{
get;
}
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _resource.Get();
public ID3D12Resource* NativeResource => _handle.IsValid ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get();
public D3D12Texture(ComPtr<ID3D12Resource> resource, uint width, uint height, TextureFormat format, uint mipLevels = 1)
{
_resource = resource.Move();
_handle = TextureHandle.Invalid;
_externalResource = resource.Move();
Width = width;
Height = height;
Format = format;
MipLevels = mipLevels;
_currentState = ResourceState.Common;
var desc = _resource.Get()->GetDesc();
Size = (ulong)(desc.Width * desc.Height * GetBytesPerPixel(format));
Size = Width * Height * GetBytesPerPixel(Format);
}
public D3D12Texture(ComPtr<ID3D12Device14> device, TextureDesc desc)
public D3D12Texture(TextureHandle handle, ref readonly TextureDesc desc)
{
_handle = handle;
_externalResource = default;
Width = desc.Width;
Height = desc.Height;
Format = desc.Format;
MipLevels = desc.MipLevels;
var mipLevels = desc.MipLevels;
if (mipLevels <= 0)
{
mipLevels = (uint)(Math.Floor(Math.Log2(Math.Max(Width, Height))) + 1);
}
MipLevels = mipLevels;
_currentState = ResourceState.Common;
CreateTexture(device, desc);
Size = (ulong)(Width * Height * GetBytesPerPixel(Format));
Size = Width * Height * GetBytesPerPixel(Format);
}
private void CreateTexture(ComPtr<ID3D12Device14> device, TextureDesc desc)
~D3D12Texture()
{
var resourceDesc = new ResourceDescription
{
Dimension = ResourceDimension.Texture2D,
Alignment = 0,
Width = desc.Width,
Height = desc.Height,
DepthOrArraySize = 1,
MipLevels = (ushort)desc.MipLevels,
Format = ConvertTextureFormat(desc.Format),
SampleDesc = new Win32.Graphics.Dxgi.Common.SampleDescription(1, 0),
Layout = TextureLayout.Unknown,
Flags = ConvertTextureUsage(desc.Usage)
};
var heapProps = new HeapProperties
{
Type = HeapType.Default,
CPUPageProperty = CpuPageProperty.Unknown,
MemoryPoolPreference = MemoryPool.Unknown,
CreationNodeMask = 1,
VisibleNodeMask = 1
};
device.Get()->CreateCommittedResource(
&heapProps,
HeapFlags.None,
&resourceDesc,
Win32.Graphics.Direct3D12.ResourceStates.Common,
null,
__uuidof<ID3D12Resource>(),
_resource.GetVoidAddressOf());
}
private static Win32.Graphics.Dxgi.Common.Format ConvertTextureFormat(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => Win32.Graphics.Dxgi.Common.Format.R8G8B8A8Unorm,
TextureFormat.B8G8R8A8_UNorm => Win32.Graphics.Dxgi.Common.Format.B8G8R8A8Unorm,
TextureFormat.R16G16B16A16_Float => Win32.Graphics.Dxgi.Common.Format.R16G16B16A16Float,
TextureFormat.R32G32B32A32_Float => Win32.Graphics.Dxgi.Common.Format.R32G32B32A32Float,
TextureFormat.D24_UNorm_S8_UInt => Win32.Graphics.Dxgi.Common.Format.D24UnormS8Uint,
TextureFormat.D32_Float => Win32.Graphics.Dxgi.Common.Format.D32Float,
_ => throw new ArgumentException($"Unsupported texture format: {format}")
};
}
private static ResourceFlags ConvertTextureUsage(TextureUsage usage)
{
var flags = ResourceFlags.None;
if ((usage & TextureUsage.RenderTarget) != 0)
flags |= ResourceFlags.AllowRenderTarget;
if ((usage & TextureUsage.DepthStencil) != 0)
flags |= ResourceFlags.AllowDepthStencil;
if ((usage & TextureUsage.UnorderedAccess) != 0)
flags |= ResourceFlags.AllowUnorderedAccess;
return flags;
Dispose();
}
private static uint GetBytesPerPixel(TextureFormat format)
@@ -125,11 +101,29 @@ internal unsafe class D3D12Texture : ITexture
};
}
public void SetCurrentState(ResourceState state)
{
_currentState = state;
}
public void Dispose()
{
if (_disposed) return;
if (_disposed)
{
return;
}
if (_handle.IsValid)
{
_handle.Dispose();
}
else
{
_externalResource.Dispose();
}
_resource.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,249 +0,0 @@
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common;
using static Win32.Graphics.D3D12MemoryAllocator.Apis;
using ResourceHandle = Ghost.Graphics.Data.ResourceHandle;
namespace Ghost.Graphics.D3D12;
internal unsafe class ResourceAllocator
{
private readonly struct AllocationInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public readonly uint generation;
public bool Allocated => allocation.IsNotNull;
public AllocationInfo(in Allocation allocation, uint cpuFenceValue, uint generation)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
this.generation = generation;
}
public AllocationInfo(in Allocation allocation, uint generation)
: this(allocation, 0 /* TODO: Use proper fence value from render system */, generation)
{
}
public void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
private readonly Allocator _allocator;
private UnsafeList<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<int> _freeSlots = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private readonly Lock _lock = new();
private Guid* IID_NULL
{
get
{
fixed (Guid* pGuid = &Guid.Empty)
{
return pGuid;
}
}
}
public ResourceAllocator(IDXGIAdapter* pAdapter, ID3D12Device* pDevice)
{
var desc = new AllocatorDesc
{
pAdapter = pAdapter,
pDevice = pDevice,
Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted
};
CreateAllocator(in desc, out _allocator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckBufferSize(uint sizeInBytes)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckTexture2DSize(uint width, uint height)
{
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
{
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
}
}
private ResourceHandle TrackResource(in Allocation allocation, bool isTemp)
{
int id;
uint generation;
AllocationInfo allocInfo;
lock (_lock)
{
if (_freeSlots.Count > 0)
{
id = _freeSlots.Dequeue();
var info = _allocations[id];
if (info.Allocated)
{
throw new InvalidOperationException($"ERROR: Resource ID {id} registered as free but still allocated.");
}
generation = info.generation + 1;
allocInfo = new AllocationInfo(in allocation, generation);
_allocations[id] = allocInfo;
}
else
{
id = _allocations.Count;
generation = 0u;
allocInfo = new AllocationInfo(in allocation, generation);
_allocations.Add(allocInfo);
}
var handle = new ResourceHandle(id, generation);
if (isTemp)
{
_temResources.Enqueue(handle);
}
return handle;
}
}
public TextureHandle CreateTexture2D(uint width, uint height, ushort mipLevels, Format format = Format.R8G8B8A8Unorm, ResourceFlags resFlags = ResourceFlags.None, AllocationFlags allocFlags = AllocationFlags.None, ResourceStates state = ResourceStates.Common, bool tempResource = false)
{
CheckTexture2DSize(width, height);
var resourceDesc = ResourceDescription.Tex2D(format, width, height, mipLevels: mipLevels, arraySize: 1, flags: resFlags);
var allocationDesc = new AllocationDesc
{
HeapType = HeapType.Default,
Flags = allocFlags
};
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, state, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource));
}
public BufferHandle CreateBuffer(uint sizeInBytes, HeapType heapType = HeapType.Default, ResourceFlags resFlags = ResourceFlags.None, AllocationFlags allocFlags = AllocationFlags.None, ResourceStates initialState = ResourceStates.Common, bool tempResource = false)
{
CheckBufferSize(sizeInBytes);
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, resFlags);
var allocationDesc = new AllocationDesc
{
HeapType = heapType,
Flags = allocFlags
};
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource));
}
public BufferHandle CreateUploadBuffer(uint sizeInBytes, bool tempResource = false)
{
return CreateBuffer(sizeInBytes, HeapType.Upload, ResourceFlags.None, AllocationFlags.None, ResourceStates.GenericRead, tempResource);
}
public void ReleaseTempResource()
{
while (_temResources.Count > 0)
{
ref var handle = ref _temResources.Peek();
ref var info = ref _allocations[handle.id];
// TODO: Implement proper fence-based cleanup with RenderSystem
// For now, just release all temp resources
ReleaseAllocation(in handle);
_temResources.Dequeue();
}
}
public Allocation GetAllocation(in ResourceHandle handle)
{
if (!handle.IsValid)
{
throw new InvalidOperationException("Invalid resource handle.");
}
lock (_lock)
{
ref var allocationInfo = ref _allocations[handle.id];
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
{
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
}
return allocationInfo.allocation;
}
}
public void ReleaseAllocation(in ResourceHandle handle)
{
if (!handle.IsValid)
{
return;
}
lock (_lock)
{
ref var allocationInfo = ref _allocations[handle.id];
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
{
return;
}
allocationInfo.Dispose();
_freeSlots.Enqueue(handle.id);
}
}
public void Dispose()
{
#if DEBUG
if (_allocations.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing.");
}
#endif
for (var i = 0; i < _allocations.Count; i++)
{
_allocations[i].Dispose();
}
_allocations.Dispose();
_temResources.Dispose();
_allocator.Release();
}
}