Continue working on RHI
This commit is contained in:
@@ -39,7 +39,7 @@ internal interface IComponentPool<T> : IComponentPool
|
||||
internal class ComponentPool<T> : IComponentPool<T>
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
private struct ComponentData
|
||||
private struct ComponentMetadata
|
||||
{
|
||||
public T data;
|
||||
public Entity owner;
|
||||
@@ -48,7 +48,7 @@ internal class ComponentPool<T> : IComponentPool<T>
|
||||
private EntityID _nextId;
|
||||
private EntityID _capacity;
|
||||
|
||||
private ComponentData[] _components;
|
||||
private ComponentMetadata[] _components;
|
||||
private EntityID[] _lookup;
|
||||
|
||||
public EntityID Count => _nextId;
|
||||
@@ -58,7 +58,7 @@ internal class ComponentPool<T> : IComponentPool<T>
|
||||
_nextId = 0;
|
||||
_capacity = initialSize;
|
||||
|
||||
_components = new ComponentData[initialSize];
|
||||
_components = new ComponentMetadata[initialSize];
|
||||
_lookup = new EntityID[initialSize];
|
||||
|
||||
_lookup.AsSpan().Fill(Entity.INVALID_ID);
|
||||
@@ -124,7 +124,7 @@ internal class ComponentPool<T> : IComponentPool<T>
|
||||
}
|
||||
|
||||
_lookup[lookupIndex] = _nextId;
|
||||
_components[_nextId] = new ComponentData
|
||||
_components[_nextId] = new ComponentMetadata
|
||||
{
|
||||
data = component,
|
||||
owner = entity
|
||||
@@ -205,7 +205,7 @@ internal class ComponentPool<T> : IComponentPool<T>
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_components = Array.Empty<ComponentData>();
|
||||
_components = Array.Empty<ComponentMetadata>();
|
||||
_lookup = Array.Empty<EntityID>();
|
||||
_nextId = 0;
|
||||
_capacity = 0;
|
||||
|
||||
@@ -47,7 +47,7 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||
|
||||
public readonly bool Equals(Entity other)
|
||||
{
|
||||
return _id == other._id;
|
||||
return _id == other._id && _generation == other._generation;
|
||||
}
|
||||
|
||||
public readonly int CompareTo(Entity other)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public interface IRenderPass : IDisposable
|
||||
{
|
||||
void Initialize(CommandList cmd);
|
||||
void Execute(CommandList cmd);
|
||||
void Initialize(ICommandBuffer cmd);
|
||||
void Execute(ICommandBuffer cmd);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
|
||||
public unsafe readonly struct ISwapChainPanelNative
|
||||
public unsafe readonly struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable
|
||||
{
|
||||
[ComImport]
|
||||
[Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")]
|
||||
@@ -20,13 +20,27 @@ public unsafe readonly struct ISwapChainPanelNative
|
||||
}
|
||||
|
||||
private readonly IntPtr _nativePtr;
|
||||
public readonly IntPtr NativePointer => _nativePtr;
|
||||
|
||||
public ISwapChainPanelNative(IntPtr nativePtr)
|
||||
{
|
||||
_nativePtr = nativePtr;
|
||||
}
|
||||
|
||||
public void QueryInterface(in Guid riid, out nint ppvObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public uint AddRef()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public uint Release()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static ISwapChainPanelNative FromSwapChainPanel(object panel)
|
||||
{
|
||||
// Get the IUnknown/IInspectable pointer
|
||||
|
||||
@@ -43,7 +43,7 @@ internal unsafe class D3D12Buffer : IBuffer
|
||||
|
||||
public ResourceState CurrentState => _currentState;
|
||||
|
||||
public ID3D12Resource* NativeResource => _handle.ResourceHandle.GetAllocation().Resource;
|
||||
public ID3D12Resource* NativeResource => _externalResource.Get() == null ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for wrapping existing D3D12 resources
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Numerics;
|
||||
|
||||
@@ -13,19 +14,26 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
{
|
||||
private ComPtr<ID3D12CommandAllocator> _allocator;
|
||||
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
|
||||
|
||||
private readonly D3D12PipelineStateController _stateController;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private readonly CommandBufferType _type;
|
||||
private bool _isRecording;
|
||||
private bool _disposed;
|
||||
|
||||
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
|
||||
|
||||
public D3D12CommandBuffer(ComPtr<ID3D12Device14> device, CommandBufferType type)
|
||||
public D3D12CommandBuffer(D3D12RenderDevice device, D3D12PipelineStateController stateController, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type)
|
||||
{
|
||||
_type = type;
|
||||
var commandListType = ConvertCommandBufferType(type);
|
||||
|
||||
device.Get()->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
|
||||
device.Get()->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
|
||||
device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
|
||||
device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
|
||||
|
||||
_stateController = stateController;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
// Command lists are created in recording state, so close it
|
||||
_commandList.Get()->Close();
|
||||
@@ -35,7 +43,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
public void Begin()
|
||||
{
|
||||
if (_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is already recording");
|
||||
}
|
||||
|
||||
_allocator.Get()->Reset();
|
||||
_commandList.Get()->Reset(_allocator.Get(), null);
|
||||
@@ -45,7 +55,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
public void End()
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Command buffer is not recording");
|
||||
}
|
||||
|
||||
_commandList.Get()->Close();
|
||||
_isRecording = false;
|
||||
@@ -99,21 +111,53 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetPipelineState(IPipelineState pipelineState)
|
||||
public void SetPipelineState(IPipelineStateController pipelineState)
|
||||
{
|
||||
// TODO: Implement pipeline state setting
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetDescriptorHeaps(IDescriptorHeap[] heaps)
|
||||
public void DrawMesh(Mesh mesh, Material material)
|
||||
{
|
||||
// TODO: Implement descriptor heap setting
|
||||
throw new NotImplementedException();
|
||||
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
|
||||
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
|
||||
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
|
||||
{
|
||||
throw new InvalidOperationException("Shader pipeline is not compiled or invalid");
|
||||
}
|
||||
|
||||
public void DrawIndexedInstanced(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
||||
// Set root signature and pipeline state
|
||||
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
|
||||
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
|
||||
|
||||
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
|
||||
var heaps = stackalloc ID3D12DescriptorHeap*[2];
|
||||
heaps[0] = _descriptorAllocator.GetBindlessHeap(); // Specialized bindless heap
|
||||
heaps[1] = d3d12Pipeline.samplerHeap.Get(); // Sampler heap from shader
|
||||
_commandList.Get()->SetDescriptorHeaps(2, heaps);
|
||||
|
||||
// Bind constant buffers
|
||||
var rootParamIndex = 0u;
|
||||
foreach (var cbufferInfo in material.Shader.ConstantBuffers)
|
||||
{
|
||||
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
|
||||
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
|
||||
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress);
|
||||
}
|
||||
|
||||
// Bind sampler descriptor table (last root parameter)
|
||||
var samplerGpuHandle = d3d12Pipeline.samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart();
|
||||
_commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
|
||||
|
||||
|
||||
// For fully bindless rendering, we don't use the Input Assembler stage
|
||||
// Instead, we use instanced drawing where each "instance" represents a triangle
|
||||
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
|
||||
_commandList.Get()->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
|
||||
|
||||
// Draw without vertex/index buffers - use instanced drawing
|
||||
// Each instance represents a triangle (3 vertices)
|
||||
var triangleCount = mesh.IndexCount / 3;
|
||||
_commandList.Get()->DrawInstanced(3, triangleCount, 0, 0);
|
||||
}
|
||||
|
||||
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1)
|
||||
|
||||
@@ -15,10 +15,13 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
private ulong _fenceValue;
|
||||
private bool _disposed;
|
||||
|
||||
public CommandQueueType Type { get; }
|
||||
public CommandQueueType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
public ID3D12CommandQueue* NativeQueue => _queue.Get();
|
||||
|
||||
public D3D12CommandQueue(ComPtr<ID3D12Device14> device, CommandQueueType type)
|
||||
public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type)
|
||||
{
|
||||
Type = type;
|
||||
_fenceEvent = new AutoResetEvent(false);
|
||||
@@ -33,10 +36,10 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
|
||||
fixed (void* queuePtr = &_queue)
|
||||
{
|
||||
device.Get()->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr);
|
||||
pDevice->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr);
|
||||
}
|
||||
|
||||
device.Get()->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
|
||||
pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
public void Submit(ICommandBuffer commandBuffer)
|
||||
@@ -53,11 +56,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
}
|
||||
}
|
||||
|
||||
public void Submit(ICommandBuffer[] commandBuffers)
|
||||
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
|
||||
{
|
||||
var commandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
|
||||
|
||||
for (int i = 0; i < commandBuffers.Length; i++)
|
||||
for (var i = 0; i < commandBuffers.Length; i++)
|
||||
{
|
||||
if (commandBuffers[i] is D3D12CommandBuffer d3d12CommandBuffer)
|
||||
{
|
||||
@@ -109,7 +112,8 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_fenceEvent?.Dispose();
|
||||
_fence.Dispose();
|
||||
|
||||
@@ -4,13 +4,13 @@ using Win32.Graphics.Dxgi;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class DebugLayer
|
||||
internal unsafe class D3D12DebugLayer
|
||||
{
|
||||
private readonly ComPtr<ID3D12Debug6> _d3d12Debug;
|
||||
private readonly ComPtr<IDXGIDebug1> _dxgiDebug;
|
||||
private readonly ComPtr<IDXGIInfoQueue> _dxgiInfoQueue;
|
||||
|
||||
public DebugLayer()
|
||||
public D3D12DebugLayer()
|
||||
{
|
||||
D3D12GetDebugInterface(__uuidof<ID3D12Debug6>(), _d3d12Debug.GetVoidAddressOf());
|
||||
_d3d12Debug.Get()->EnableDebugLayer();
|
||||
@@ -25,7 +25,7 @@ internal unsafe class DebugLayer
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, ReportLiveObjectFlags.Detail | ReportLiveObjectFlags.IgnoreInternal);
|
||||
_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, ReportLiveObjectFlags.All);
|
||||
|
||||
_d3d12Debug.Dispose();
|
||||
_dxgiDebug.Dispose();
|
||||
@@ -1,88 +1,427 @@
|
||||
using Ghost.Graphics.RHI;
|
||||
using Win32;
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of descriptor allocator interface
|
||||
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
|
||||
/// </summary>
|
||||
internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator
|
||||
internal unsafe class D3D12DescriptorAllocator : IDisposable
|
||||
{
|
||||
private readonly DescriptorAllocator _internalAllocator;
|
||||
private readonly DescriptorHeapAllocator _rtvHeap;
|
||||
private readonly DescriptorHeapAllocator _dsvHeap;
|
||||
private readonly DescriptorHeapAllocator _srvHeap;
|
||||
private readonly DescriptorHeapAllocator _samplerHeap;
|
||||
private readonly BindlessDescriptorHeapAllocator _bindlessHeap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public D3D12DescriptorAllocator(ComPtr<ID3D12Device14> device)
|
||||
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000)
|
||||
{
|
||||
_internalAllocator = new DescriptorAllocator();
|
||||
var pDevice = device.NativeDevice;
|
||||
|
||||
_rtvHeap = new DescriptorHeapAllocator("rtv", pDevice, DescriptorHeapType.Rtv, initialRtvCount);
|
||||
_dsvHeap = new DescriptorHeapAllocator("dsv", pDevice, DescriptorHeapType.Dsv, initialDsvCount);
|
||||
_srvHeap = new DescriptorHeapAllocator("srv", pDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
|
||||
_samplerHeap = new DescriptorHeapAllocator("sampler", pDevice, DescriptorHeapType.Sampler, initialSamplerCount);
|
||||
_bindlessHeap = new BindlessDescriptorHeapAllocator(pDevice, initialBindlessCount);
|
||||
}
|
||||
|
||||
public DescriptorHandle AllocateRTV()
|
||||
~D3D12DescriptorAllocator()
|
||||
{
|
||||
var rtvDescriptor = _internalAllocator.AllocateRTV();
|
||||
return new DescriptorHandle(rtvDescriptor.Index);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public DescriptorHandle[] AllocateRTVs(uint count)
|
||||
#region RTV Methods
|
||||
|
||||
public RenderTargetDescriptor AllocateRTV()
|
||||
{
|
||||
var rtvDescriptors = _internalAllocator.AllocateRTVs(count);
|
||||
return rtvDescriptors.Select(desc => new DescriptorHandle(desc.Index)).ToArray();
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _rtvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
||||
}
|
||||
|
||||
public DescriptorHandle AllocateDSV()
|
||||
{
|
||||
var dsvDescriptor = _internalAllocator.AllocateDSV();
|
||||
return new DescriptorHandle(dsvDescriptor.Index);
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
return new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public DescriptorHandle AllocateSRV()
|
||||
public RenderTargetDescriptor[] AllocateRTVs(uint count)
|
||||
{
|
||||
var srvDescriptor = _internalAllocator.AllocateSRV();
|
||||
return new DescriptorHandle(srvDescriptor.Index);
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _rtvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
|
||||
}
|
||||
|
||||
public DescriptorHandle AllocateSampler()
|
||||
var descriptors = new RenderTargetDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var samplerDescriptor = _internalAllocator.AllocateSampler();
|
||||
return new DescriptorHandle(samplerDescriptor.Index);
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public DescriptorHandle AllocateBindless()
|
||||
{
|
||||
var bindlessDescriptor = _internalAllocator.AllocateBindless();
|
||||
return new DescriptorHandle(bindlessDescriptor.Index);
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseRTV(DescriptorHandle handle)
|
||||
public void ReleaseRTV(RenderTargetDescriptor descriptor)
|
||||
{
|
||||
// TODO: Convert back to internal descriptor and release
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is RenderTargetDescriptor d3d12Descriptor)
|
||||
{
|
||||
_rtvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseDSV(DescriptorHandle handle)
|
||||
public void ReleaseRTVs(RenderTargetDescriptor[] descriptors)
|
||||
{
|
||||
// TODO: Convert back to internal descriptor and release
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseRTV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSRV(DescriptorHandle handle)
|
||||
#endregion
|
||||
|
||||
#region DSV Methods
|
||||
|
||||
public DepthStencilDescriptor AllocateDSV()
|
||||
{
|
||||
// TODO: Convert back to internal descriptor and release
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _dsvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
||||
}
|
||||
|
||||
public void ReleaseSampler(DescriptorHandle handle)
|
||||
{
|
||||
// TODO: Convert back to internal descriptor and release
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
return new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public void ReleaseBindless(DescriptorHandle handle)
|
||||
public DepthStencilDescriptor[] AllocateDSVs(uint count)
|
||||
{
|
||||
// TODO: Convert back to internal descriptor and release
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _dsvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new DepthStencilDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseDSV(DepthStencilDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is DepthStencilDescriptor d3d12Descriptor)
|
||||
{
|
||||
_dsvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseDSVs(DepthStencilDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseDSV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SRV Methods
|
||||
|
||||
public ShaderResourceDescriptor AllocateSRV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _srvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate SRV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public ShaderResourceDescriptor[] AllocateSRVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _srvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new ShaderResourceDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSRV(ShaderResourceDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is ShaderResourceDescriptor d3d12Descriptor)
|
||||
{
|
||||
_srvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSRVs(ShaderResourceDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSRV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sampler Methods
|
||||
|
||||
public SamplerDescriptor AllocateSampler()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _samplerHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public SamplerDescriptor[] AllocateSamplers(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _samplerHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new SamplerDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSampler(SamplerDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is SamplerDescriptor d3d12Descriptor)
|
||||
{
|
||||
_samplerHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSamplers(SamplerDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSampler(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bindless Methods
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a bindless descriptor for SM 6.6 rendering.
|
||||
/// The returned descriptor maintains a 1:1 relationship between allocation index and shader index.
|
||||
/// </summary>
|
||||
public BindlessDescriptor AllocateBindless()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _bindlessHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate bindless descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
|
||||
return new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates multiple bindless descriptors for SM 6.6 rendering.
|
||||
/// </summary>
|
||||
public BindlessDescriptor[] AllocateBindless(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _bindlessHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new BindlessDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a bindless descriptor.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is BindlessDescriptor d3d12Descriptor)
|
||||
{
|
||||
_bindlessHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases multiple bindless descriptors.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseBindless(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RTV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetRTVHeap() => _rtvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DSV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SRV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bindless heap for SM 6.6 bindless rendering.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap* GetBindlessHeap() => _bindlessHeap.BindlessHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps that need to be bound to the command list.
|
||||
/// </summary>
|
||||
public ID3D12DescriptorHeap*[] GetShaderVisibleHeaps()
|
||||
{
|
||||
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps including bindless heap for SM 6.6 rendering.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeapsWithBindless()
|
||||
{
|
||||
return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rtvHeap.Dispose();
|
||||
_dsvHeap.Dispose();
|
||||
_srvHeap.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
_bindlessHeap.Dispose();
|
||||
|
||||
_internalAllocator?.Dispose();
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
61
Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs
Normal file
61
Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
|
||||
{
|
||||
#if DEBUG
|
||||
private readonly D3D12DebugLayer _debugLayer;
|
||||
#endif
|
||||
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly D3D12PipelineStateController _stateController;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
|
||||
private readonly D3D12PipelineStateController _pipelineState;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
public IRenderDevice Device => _device;
|
||||
public IPipelineStateController PipelineStateController => _stateController;
|
||||
public IResourceAllocator ResourceAllocator => _resourceAllocator;
|
||||
|
||||
public D3D12GraphicsEngine(RenderSystem renderSystem)
|
||||
{
|
||||
#if DEBUG
|
||||
_debugLayer = new();
|
||||
#endif
|
||||
|
||||
_device = new();
|
||||
_stateController = new(_device);
|
||||
_resourceAllocator = new(_device, renderSystem);
|
||||
|
||||
_pipelineState = new(_device);
|
||||
_descriptorAllocator = new(_device);
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer()
|
||||
{
|
||||
return new D3D12Renderer(this, _resourceAllocator);
|
||||
}
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
return new D3D12CommandBuffer(_device, _stateController, _descriptorAllocator, type);
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_descriptorAllocator.Dispose();
|
||||
_resourceAllocator.Dispose();
|
||||
_device.Dispose();
|
||||
|
||||
#if DEBUG
|
||||
_debugLayer.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
253
Ghost.Graphics/D3D12/D3D12PipelineStateController.cs
Normal file
253
Ghost.Graphics/D3D12/D3D12PipelineStateController.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal class D3D12ShaderPipeline : IShaderPipeline
|
||||
{
|
||||
public ComPtr<ID3D12RootSignature> rootSignature;
|
||||
public ComPtr<ID3D12PipelineState> pipelineState;
|
||||
public ComPtr<ID3D12DescriptorHeap> samplerHeap;
|
||||
public D3D12ShaderCompiler.CompileResult vsResult;
|
||||
public D3D12ShaderCompiler.CompileResult psResult;
|
||||
public D3D12ShaderCompiler.CompileResult csResult;
|
||||
|
||||
public PipelineType Type
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable
|
||||
{
|
||||
private const string _VS_ENTRY_POINT = "VSMain";
|
||||
private const string _PS_ENTRY_POINT = "PSMain";
|
||||
private const string _PROFILE_VS_6_6 = "vs_6_6";
|
||||
|
||||
private readonly ID3D12Device14* _device;
|
||||
|
||||
private readonly Dictionary<Shader, D3D12ShaderPipeline> _shaderPipelines;
|
||||
|
||||
public D3D12PipelineStateController(D3D12RenderDevice device)
|
||||
{
|
||||
_device = device.NativeDevice;
|
||||
_shaderPipelines = new();
|
||||
}
|
||||
|
||||
// TODO: Support compute shaders
|
||||
public void ColectionShader(ReadOnlySpan<Shader> shaders)
|
||||
{
|
||||
foreach (var shader in shaders)
|
||||
{
|
||||
_shaderPipelines.TryAdd(shader, new()
|
||||
{
|
||||
Type = PipelineType.Graphics
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void CompileCollected()
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
var vsResult = D3D12ShaderCompiler.CompileDXC(kvp.Key, _VS_ENTRY_POINT, _PROFILE_VS_6_6);
|
||||
var psResult = D3D12ShaderCompiler.CompileDXC(kvp.Key, _PS_ENTRY_POINT, _PROFILE_VS_6_6);
|
||||
|
||||
kvp.Value.vsResult = vsResult;
|
||||
kvp.Value.psResult = psResult;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline)
|
||||
{
|
||||
// Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table
|
||||
var totalRootParams = shader.ConstantBuffers.Count + (shader.RegularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler
|
||||
var rootParameters = new RootParameter1[totalRootParams];
|
||||
|
||||
var parameterIndex = 0;
|
||||
|
||||
// Add CBV root parameters
|
||||
foreach (var cbufferInfo in shader.ConstantBuffers)
|
||||
{
|
||||
rootParameters[parameterIndex++] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.Cbv,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Add regular texture descriptor table if we have regular textures
|
||||
if (shader.RegularTextures.Count > 0)
|
||||
{
|
||||
var textureRanges = new DescriptorRange1[1];
|
||||
textureRanges[0] = new DescriptorRange1
|
||||
{
|
||||
RangeType = DescriptorRangeType.Srv,
|
||||
NumDescriptors = (uint)shader.RegularTextures.Count,
|
||||
BaseShaderRegister = 0, // Start from t0
|
||||
RegisterSpace = 0,
|
||||
Flags = DescriptorRangeFlags.None,
|
||||
OffsetInDescriptorsFromTableStart = 0
|
||||
};
|
||||
|
||||
fixed (DescriptorRange1* textureRangesPtr = textureRanges)
|
||||
{
|
||||
rootParameters[parameterIndex++] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.DescriptorTable,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Sampler descriptor table (still needed for samplers)
|
||||
var samplerRanges = new DescriptorRange1[1];
|
||||
samplerRanges[0] = new DescriptorRange1
|
||||
{
|
||||
RangeType = DescriptorRangeType.Sampler,
|
||||
NumDescriptors = 1,
|
||||
BaseShaderRegister = 0, // s0
|
||||
RegisterSpace = 0,
|
||||
Flags = DescriptorRangeFlags.None,
|
||||
OffsetInDescriptorsFromTableStart = 0
|
||||
};
|
||||
|
||||
fixed (DescriptorRange1* samplerRangesPtr = samplerRanges)
|
||||
{
|
||||
rootParameters[parameterIndex] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.DescriptorTable,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr)
|
||||
};
|
||||
}
|
||||
|
||||
// Create root signature with the modern flag
|
||||
fixed (RootParameter1* rootParamsPtr = rootParameters)
|
||||
{
|
||||
var rootSignatureDesc = new RootSignatureDescription1
|
||||
{
|
||||
NumParameters = (uint)rootParameters.Length,
|
||||
pParameters = rootParamsPtr,
|
||||
NumStaticSamplers = 0,
|
||||
pStaticSamplers = null,
|
||||
// Key difference: Use the modern flag for direct heap indexing
|
||||
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
|
||||
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
|
||||
};
|
||||
|
||||
var versionedDesc = new VersionedRootSignatureDescription
|
||||
{
|
||||
Version = RootSignatureVersion.V1_1,
|
||||
Desc_1_1 = rootSignatureDesc
|
||||
};
|
||||
|
||||
using ComPtr<ID3DBlob> signature = default;
|
||||
using ComPtr<ID3DBlob> error = default;
|
||||
|
||||
D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
|
||||
|
||||
_device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), shaderPipeline.rootSignature.GetVoidAddressOf());
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
|
||||
{
|
||||
var psoDesc = new GraphicsPipelineStateDescription
|
||||
{
|
||||
pRootSignature = shaderPipeline.rootSignature.Get(),
|
||||
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
|
||||
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
|
||||
InputLayout = D3D12PipelineResource.InputLayoutDescription,
|
||||
RasterizerState = RasterizerDescription.CullNone,
|
||||
BlendState = BlendDescription.Opaque,
|
||||
DepthStencilState = DepthStencilDescription.Default,
|
||||
SampleMask = uint.MaxValue,
|
||||
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
|
||||
NumRenderTargets = 1,
|
||||
SampleDesc = new SampleDescription(1, 0),
|
||||
DSVFormat = Format.Unknown,
|
||||
};
|
||||
|
||||
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
|
||||
|
||||
_device->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
private void CreateSamplerHeap(D3D12ShaderPipeline shaderPipeline)
|
||||
{
|
||||
// Create sampler heap
|
||||
var samplerHeapDesc = new DescriptorHeapDescription
|
||||
{
|
||||
Type = DescriptorHeapType.Sampler,
|
||||
NumDescriptors = 1,
|
||||
Flags = DescriptorHeapFlags.ShaderVisible
|
||||
};
|
||||
|
||||
_device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof<ID3D12DescriptorHeap>(), shaderPipeline.samplerHeap.GetVoidAddressOf());
|
||||
|
||||
// Create default sampler
|
||||
var samplerDesc = new SamplerDescription
|
||||
{
|
||||
Filter = Filter.MinMagMipLinear,
|
||||
AddressU = TextureAddressMode.Wrap,
|
||||
AddressV = TextureAddressMode.Wrap,
|
||||
AddressW = TextureAddressMode.Wrap,
|
||||
MipLODBias = 0,
|
||||
MaxAnisotropy = 1,
|
||||
MinLOD = 0,
|
||||
MaxLOD = float.MaxValue
|
||||
};
|
||||
|
||||
// Set border color manually
|
||||
samplerDesc.BorderColor[0] = 0;
|
||||
samplerDesc.BorderColor[1] = 0;
|
||||
samplerDesc.BorderColor[2] = 0;
|
||||
samplerDesc.BorderColor[3] = 0;
|
||||
|
||||
var samplerHandle = shaderPipeline.samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart();
|
||||
_device->CreateSampler(&samplerDesc, samplerHandle);
|
||||
}
|
||||
|
||||
// TODO: Pipeline variants (keywords)
|
||||
// TODO: Disk caching
|
||||
// TODO: Async compilation
|
||||
public void PreCookPipelineState()
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
CreateRootSignature(kvp.Key, kvp.Value);
|
||||
CreatePipelineStateObject(kvp.Value);
|
||||
CreateSamplerHeap(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public IShaderPipeline GetShaderPipeline(Shader shader)
|
||||
{
|
||||
if (_shaderPipelines.TryGetValue(shader, out var pipeline))
|
||||
{
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Shader pipeline not found for shader: {shader}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in _shaderPipelines)
|
||||
{
|
||||
kvp.Value.rootSignature.Dispose();
|
||||
kvp.Value.pipelineState.Dispose();
|
||||
kvp.Value.samplerHeap.Dispose();
|
||||
kvp.Value.vsResult.Dispose();
|
||||
kvp.Value.psResult.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,12 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
|
||||
private readonly D3D12CommandQueue _graphicsQueue;
|
||||
private readonly D3D12CommandQueue _computeQueue;
|
||||
private readonly D3D12CommandQueue _copyQueue;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ICommandQueue GraphicsQueue => _graphicsQueue;
|
||||
public ICommandQueue ComputeQueue => _computeQueue;
|
||||
public ICommandQueue CopyQueue => _copyQueue;
|
||||
public IDescriptorAllocator DescriptorAllocator => _descriptorAllocator;
|
||||
|
||||
public ID3D12Device14* NativeDevice => _device.Get();
|
||||
public IDXGIFactory7* DXGIFactory => _dxgiFactory.Get();
|
||||
@@ -38,8 +36,11 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
|
||||
_graphicsQueue = new D3D12CommandQueue(_device, CommandQueueType.Graphics);
|
||||
_computeQueue = new D3D12CommandQueue(_device, CommandQueueType.Compute);
|
||||
_copyQueue = new D3D12CommandQueue(_device, CommandQueueType.Copy);
|
||||
}
|
||||
|
||||
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
|
||||
~D3D12RenderDevice()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void InitializeDevice()
|
||||
@@ -78,22 +79,13 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
return new D3D12CommandBuffer(_device, type);
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
return new D3D12SwapChain(_dxgiFactory, _graphicsQueue.NativeQueue, desc);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_descriptorAllocator?.Dispose();
|
||||
_graphicsQueue?.Dispose();
|
||||
_computeQueue?.Dispose();
|
||||
_copyQueue?.Dispose();
|
||||
@@ -103,5 +95,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
|
||||
_adapter.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
@@ -7,62 +10,51 @@ namespace Ghost.Graphics.D3D12;
|
||||
/// D3D12 implementation of render target interface
|
||||
/// Supports either color OR depth rendering, not both
|
||||
/// </summary>
|
||||
internal unsafe class D3D12RenderTarget : IRenderTarget
|
||||
internal unsafe class D3D12RenderTarget : D3D12Texture, IRenderTarget
|
||||
{
|
||||
private readonly D3D12Texture _target;
|
||||
private bool _disposed;
|
||||
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RenderTargetType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ITexture Target => _target;
|
||||
private D3D12RenderTarget(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
|
||||
: base(resource, width, height, slice, format, mipLevels)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
private D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc, ref readonly TextureDesc texDesc)
|
||||
: base(handle, in texDesc)
|
||||
{
|
||||
Type = desc.Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new render target with its own texture
|
||||
/// </summary>
|
||||
public D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc)
|
||||
/// <param name="handle">The handle to the texture resource</param>
|
||||
/// <param name="desc">The descriptor to describe the render target</param>
|
||||
/// <returns>New render target instance</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static D3D12RenderTarget Create(TextureHandle handle, ref readonly RenderTargetDesc desc)
|
||||
{
|
||||
Width = desc.Width;
|
||||
Height = desc.Height;
|
||||
Type = desc.Type;
|
||||
|
||||
// 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, desc.Dimension, desc.MipLevels, usage);
|
||||
_target = new D3D12Texture(handle, in textureDesc);
|
||||
var texDesc = RenderTargetDesc.ToTextureDescriptor(desc);
|
||||
return new D3D12RenderTarget(handle, in desc, in texDesc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrap an existing texture as a render target (for swap chain back buffers)
|
||||
/// Create a new render target from an existing D3D12 resource
|
||||
/// </summary>
|
||||
public D3D12RenderTarget(D3D12Texture existingTexture, RenderTargetType type)
|
||||
/// <param name="resource">The existing D3D12 resource</param>
|
||||
/// <param name="width">The width of the render target</param>
|
||||
/// <param name="height">The height of the render target</param>
|
||||
/// <param name="format">The format of the render target</param>
|
||||
/// <param name="type">The type of the render target</param>
|
||||
/// <param name="mipLevels">The number of mip levels</param>
|
||||
/// <returns>New render target instance</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static D3D12RenderTarget Create(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
|
||||
{
|
||||
_target = existingTexture;
|
||||
Width = existingTexture.Width;
|
||||
Height = existingTexture.Height;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_target?.Dispose();
|
||||
_disposed = true;
|
||||
return new D3D12RenderTarget(resource, width, height, slice, format, type, mipLevels);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
@@ -11,18 +11,18 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
{
|
||||
private struct FrameResource : IDisposable
|
||||
{
|
||||
public ICommandBuffer CommandBuffer;
|
||||
public ulong FenceValue;
|
||||
public ICommandBuffer commandBuffer;
|
||||
public ulong fenceValue;
|
||||
|
||||
public FrameResource(IRenderDevice device)
|
||||
public FrameResource(IGraphicsEngine graphicsEngine)
|
||||
{
|
||||
CommandBuffer = device.CreateCommandBuffer();
|
||||
FenceValue = 0;
|
||||
commandBuffer = graphicsEngine.CreateCommandBuffer();
|
||||
fenceValue = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public readonly void Dispose()
|
||||
{
|
||||
CommandBuffer?.Dispose();
|
||||
commandBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,11 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
private readonly FrameResource[] _frameResources;
|
||||
private uint _frameIndex;
|
||||
|
||||
private IRenderTarget? _destinationTarget; // Final destination (custom render target or swap chain back buffer)
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
|
||||
private IRenderTarget? _customRenderTarget; // User-provided render target
|
||||
private IRenderTarget? _offScreenRenderTarget; // Off-screen target for swap chain
|
||||
private ISwapChain? _swapChain;
|
||||
private IRenderTarget? _offScreenRenderTarget; // Always render to off-screen first
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
private uint _pendingWidth;
|
||||
@@ -43,41 +45,39 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
// TODO: Add render passes support
|
||||
// private ImmutableArray<IRenderPass> _renderPasses;
|
||||
|
||||
public D3D12Renderer(IRenderDevice device)
|
||||
public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator)
|
||||
{
|
||||
_commandQueue = device.GraphicsQueue;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_commandQueue = graphicsEngine.Device.GraphicsQueue;
|
||||
|
||||
// Create frame resources for double buffering
|
||||
_frameResources = new FrameResource[2];
|
||||
for (int i = 0; i < _frameResources.Length; i++)
|
||||
_frameResources = new FrameResource[D3D12PipelineResource.BACK_BUFFER_COUNT];
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i] = new FrameResource(device);
|
||||
_frameResources[i] = new FrameResource(graphicsEngine);
|
||||
}
|
||||
}
|
||||
|
||||
~D3D12Renderer()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void SetRenderTarget(IRenderTarget? renderTarget)
|
||||
{
|
||||
_destinationTarget = renderTarget;
|
||||
_swapChain = null; // Clear swap chain when using custom render target
|
||||
_customRenderTarget = renderTarget;
|
||||
_swapChain = null;
|
||||
|
||||
// Create or update off-screen render target to match destination size
|
||||
if (_destinationTarget != null)
|
||||
{
|
||||
CreateOrUpdateOffScreenRenderTarget(_destinationTarget.Width, _destinationTarget.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clean up off-screen target when switching to render target mode
|
||||
_offScreenRenderTarget?.Dispose();
|
||||
_offScreenRenderTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSwapChain(ISwapChain? swapChain)
|
||||
{
|
||||
_swapChain = swapChain;
|
||||
_destinationTarget = null; // Clear custom render target when using swap chain
|
||||
_customRenderTarget = null;
|
||||
|
||||
// Create or update off-screen render target to match swap chain size
|
||||
if (_swapChain != null)
|
||||
{
|
||||
CreateOrUpdateOffScreenRenderTarget(_swapChain.Width, _swapChain.Height);
|
||||
@@ -88,6 +88,7 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
_offScreenRenderTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestResize(uint width, uint height)
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -104,7 +105,9 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
public void ExecutePendingResize()
|
||||
{
|
||||
if (!_resizeRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint newWidth, newHeight;
|
||||
lock (_lock)
|
||||
@@ -131,59 +134,37 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
{
|
||||
ExecutePendingResize();
|
||||
|
||||
// Get current frame resource
|
||||
var frameIndex = _frameIndex % (uint)_frameResources.Length;
|
||||
ref var frame = ref _frameResources[frameIndex];
|
||||
|
||||
// Wait for this frame resource to be available
|
||||
if (frame.FenceValue > 0)
|
||||
if (frame.fenceValue > 0)
|
||||
{
|
||||
_commandQueue.WaitForValue(frame.FenceValue);
|
||||
_commandQueue.WaitForValue(frame.fenceValue);
|
||||
}
|
||||
|
||||
// Begin command recording
|
||||
frame.CommandBuffer.Begin();
|
||||
frame.commandBuffer.Begin();
|
||||
|
||||
// Determine the final destination target
|
||||
IRenderTarget? finalDestination = null;
|
||||
ITexture? swapChainBackBuffer = null;
|
||||
|
||||
if (_destinationTarget != null)
|
||||
if (_customRenderTarget != null)
|
||||
{
|
||||
// Rendering to custom render target
|
||||
finalDestination = _destinationTarget;
|
||||
// Render target mode: render directly to custom target
|
||||
RenderScene(_customRenderTarget, frame.commandBuffer);
|
||||
}
|
||||
else if (_swapChain != null)
|
||||
else if (_swapChain != null && _offScreenRenderTarget != null)
|
||||
{
|
||||
// Rendering to swap chain - get back buffer as render target
|
||||
finalDestination = _swapChain.GetCurrentBackBufferRenderTarget();
|
||||
swapChainBackBuffer = _swapChain.GetCurrentBackBuffer();
|
||||
// Swap chain mode: render to off-screen, then blit to back buffer
|
||||
var backBufferRT = _swapChain.GetCurrentBackBuffer();
|
||||
|
||||
// For testing, we render directly to the back buffer
|
||||
RenderScene(backBufferRT, frame.commandBuffer);
|
||||
//BlitToDestination(_offScreenRenderTarget, backBufferRT, frame.CommandBuffer);
|
||||
}
|
||||
|
||||
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 destination - skip rendering
|
||||
frame.CommandBuffer.End();
|
||||
return;
|
||||
}
|
||||
frame.commandBuffer.End();
|
||||
|
||||
// End command recording
|
||||
frame.CommandBuffer.End();
|
||||
|
||||
// Submit commands
|
||||
_commandQueue.Submit(frame.CommandBuffer);
|
||||
|
||||
// Present if using swap chain
|
||||
_commandQueue.Submit(frame.commandBuffer);
|
||||
_swapChain?.Present();
|
||||
|
||||
// Signal fence for this frame
|
||||
frame.FenceValue = _commandQueue.Signal(++_frameIndex);
|
||||
frame.fenceValue = _commandQueue.Signal(++_frameIndex);
|
||||
}
|
||||
|
||||
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
|
||||
@@ -207,13 +188,13 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
cmd.EndRenderPass();
|
||||
}
|
||||
|
||||
private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ITexture? swapChainBackBuffer, ICommandBuffer cmd)
|
||||
private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ICommandBuffer cmd)
|
||||
{
|
||||
// Handle swap chain back buffer transitions if needed
|
||||
if (swapChainBackBuffer != null)
|
||||
if (_swapChain != null)
|
||||
{
|
||||
// Transition back buffer to render target
|
||||
cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.Present, ResourceState.RenderTarget);
|
||||
cmd.ResourceBarrier(destination, ResourceState.Present, ResourceState.RenderTarget);
|
||||
}
|
||||
|
||||
// For now, we'll do a simple copy operation
|
||||
@@ -231,22 +212,24 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
cmd.EndRenderPass();
|
||||
|
||||
// Handle swap chain back buffer transitions if needed
|
||||
if (swapChainBackBuffer != null)
|
||||
if (_swapChain != null)
|
||||
{
|
||||
// Transition back buffer to present
|
||||
cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.RenderTarget, ResourceState.Present);
|
||||
cmd.ResourceBarrier(destination, ResourceState.RenderTarget, ResourceState.Present);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateOrUpdateOffScreenRenderTarget(uint width, uint height)
|
||||
{
|
||||
// Check if we need to recreate the off-screen render target
|
||||
if (_offScreenRenderTarget == null || _offScreenRenderTarget.Width != width || _offScreenRenderTarget.Height != height)
|
||||
if (_offScreenRenderTarget == null ||
|
||||
_offScreenRenderTarget.Width != width ||
|
||||
_offScreenRenderTarget.Height != height)
|
||||
{
|
||||
_offScreenRenderTarget?.Dispose();
|
||||
|
||||
var desc = RenderTargetDesc.Color(width, height, TextureFormat.B8G8R8A8_UNorm);
|
||||
_offScreenRenderTarget = _device.CreateRenderTarget(desc);
|
||||
var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm);
|
||||
_offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,16 +238,19 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
// Wait for all frame resources to complete
|
||||
foreach (ref var frame in _frameResources.AsSpan())
|
||||
{
|
||||
if (frame.FenceValue > 0)
|
||||
if (frame.fenceValue > 0)
|
||||
{
|
||||
_commandQueue.WaitForValue(frame.FenceValue);
|
||||
_commandQueue.WaitForValue(frame.fenceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WaitIdle();
|
||||
|
||||
@@ -276,5 +262,7 @@ public unsafe class D3D12Renderer : IRenderer
|
||||
_offScreenRenderTarget?.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using ResourceHandle = Ghost.Graphics.Data.ResourceHandle;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12ResourceAllocator
|
||||
internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource>, IDisposable
|
||||
{
|
||||
private readonly struct AllocationInfo : IDisposable
|
||||
{
|
||||
@@ -60,14 +60,14 @@ internal unsafe class D3D12ResourceAllocator
|
||||
}
|
||||
}
|
||||
|
||||
public D3D12ResourceAllocator(IDXGIAdapter* pAdapter, ID3D12Device* pDevice, RenderSystem renderSystem)
|
||||
public D3D12ResourceAllocator(D3D12RenderDevice device, RenderSystem renderSystem)
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
|
||||
var desc = new AllocatorDesc
|
||||
{
|
||||
pAdapter = pAdapter,
|
||||
pDevice = pDevice,
|
||||
pAdapter = (IDXGIAdapter*)device.Adapter,
|
||||
pDevice = (ID3D12Device*)device.NativeDevice,
|
||||
Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ internal unsafe class D3D12ResourceAllocator
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceHandle TrackResource(in Allocation allocation, bool isTemp)
|
||||
private ResourceHandle TrackResource(ref readonly Allocation allocation, bool isTemp)
|
||||
{
|
||||
int id;
|
||||
uint generation;
|
||||
@@ -130,7 +130,7 @@ internal unsafe class D3D12ResourceAllocator
|
||||
return handle;
|
||||
}
|
||||
|
||||
public TextureHandle CreateTexture2D(in TextureDesc desc, bool tempResource = false)
|
||||
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
{
|
||||
CheckTexture2DSize(desc.Width, desc.Height);
|
||||
|
||||
@@ -139,7 +139,7 @@ internal unsafe class D3D12ResourceAllocator
|
||||
desc.Width,
|
||||
desc.Height,
|
||||
mipLevels: (ushort)desc.MipLevels,
|
||||
arraySize: 1,
|
||||
arraySize: (ushort)desc.Slice,
|
||||
flags: ConvertTextureUsage(desc.Usage)
|
||||
);
|
||||
|
||||
@@ -157,7 +157,7 @@ internal unsafe class D3D12ResourceAllocator
|
||||
return new(TrackResource(in allocation, tempResource));
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(in BufferDesc desc, bool tempResource = false)
|
||||
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false)
|
||||
{
|
||||
CheckBufferSize((uint)desc.Size);
|
||||
|
||||
@@ -176,10 +176,30 @@ internal unsafe class D3D12ResourceAllocator
|
||||
return new(TrackResource(in allocation, tempResource));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public BufferHandle CreateUploadBuffer(uint sizeInBytes, bool tempResource = false)
|
||||
{
|
||||
var desc = new BufferDesc(sizeInBytes, BufferUsage.Upload, MemoryType.Upload);
|
||||
return CreateBuffer(in desc, tempResource);
|
||||
return CreateBufferHandle(in desc, tempResource);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
|
||||
{
|
||||
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
|
||||
return D3D12RenderTarget.Create(CreateTextureHandle(in textureDesc), in desc);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
{
|
||||
return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
|
||||
{
|
||||
return new D3D12Buffer(CreateBufferHandle(in desc, tempResource), in desc);
|
||||
}
|
||||
|
||||
#region Conversion Methods
|
||||
@@ -203,13 +223,19 @@ internal unsafe class D3D12ResourceAllocator
|
||||
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;
|
||||
}
|
||||
@@ -219,7 +245,9 @@ internal unsafe class D3D12ResourceAllocator
|
||||
var flags = ResourceFlags.None;
|
||||
|
||||
if (usage.HasFlag(BufferUsage.Raw))
|
||||
{
|
||||
flags |= ResourceFlags.AllowUnorderedAccess;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
@@ -238,13 +266,19 @@ internal unsafe class D3D12ResourceAllocator
|
||||
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;
|
||||
}
|
||||
@@ -252,19 +286,29 @@ internal unsafe class D3D12ResourceAllocator
|
||||
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;
|
||||
}
|
||||
@@ -275,20 +319,20 @@ internal unsafe class D3D12ResourceAllocator
|
||||
{
|
||||
while (_temResources.Count > 0)
|
||||
{
|
||||
ref var handle = ref _temResources.Peek();
|
||||
ref var info = ref _allocations[handle.id];
|
||||
var handle = _temResources.Peek();
|
||||
var info = _allocations[handle.id];
|
||||
|
||||
if (info.Allocated && info.cpuFenceValue > _renderSystem.CPUFenceValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ReleaseAllocation(in handle);
|
||||
ReleaseResource(handle);
|
||||
_temResources.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public Allocation GetAllocation(in ResourceHandle handle)
|
||||
public ID3D12Resource* GetResource(ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
@@ -301,10 +345,10 @@ internal unsafe class D3D12ResourceAllocator
|
||||
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
|
||||
}
|
||||
|
||||
return allocationInfo.allocation;
|
||||
return allocationInfo.allocation.Resource;
|
||||
}
|
||||
|
||||
public void ReleaseAllocation(in ResourceHandle handle)
|
||||
public void ReleaseResource(ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
245
Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs
Normal file
245
Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D.Dxc;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe static class D3D12ShaderCompiler
|
||||
{
|
||||
public struct CompileResult : IDisposable
|
||||
{
|
||||
public UnsafeArray<byte> bytecode;
|
||||
public ComPtr<IDxcBlob> reflection;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
bytecode.Dispose();
|
||||
reflection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static CompileResult CompileDXC(Shader shader, string entryPoint, string profile)
|
||||
{
|
||||
using ComPtr<IDxcCompiler3> compiler = default;
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
|
||||
// Create DXC compiler and utils
|
||||
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
|
||||
// Create source blob
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shader.Source);
|
||||
fixed (byte* sourceBytesPtr = sourceBytes)
|
||||
{
|
||||
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
|
||||
}
|
||||
|
||||
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
|
||||
var argsArray = new string[]
|
||||
{
|
||||
"-T", profile, // Target profile (vs_6_6, ps_6_6)
|
||||
"-E", entryPoint, // Entry point
|
||||
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
|
||||
"-enable-16bit-types", // Enable 16-bit types
|
||||
"-O3", // Optimization level
|
||||
"-Qstrip_debug" // Strip debug info but KEEP reflection
|
||||
};
|
||||
|
||||
// Convert to wide strings (DXC expects LPCWSTR)
|
||||
var wideArgs = new nuint[argsArray.Length];
|
||||
var argPointers = new IntPtr[argsArray.Length];
|
||||
|
||||
for (var i = 0; i < argsArray.Length; i++)
|
||||
{
|
||||
argPointers[i] = Marshal.StringToHGlobalUni(argsArray[i]);
|
||||
wideArgs[i] = (nuint)argPointers[i];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Compile shader
|
||||
using ComPtr<IDxcResult> result = default;
|
||||
fixed (nuint* argsPtr = wideArgs)
|
||||
{
|
||||
var buffer = new DxcBuffer
|
||||
{
|
||||
Ptr = sourceBlob.Get()->GetBufferPointer(),
|
||||
Size = sourceBlob.Get()->GetBufferSize(),
|
||||
Encoding = DXC_CP_UTF8
|
||||
};
|
||||
|
||||
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
// Check compilation result
|
||||
HResult hrStatus;
|
||||
result.Get()->GetStatus(&hrStatus);
|
||||
if (hrStatus.Failure)
|
||||
{
|
||||
// Get error messages
|
||||
using ComPtr<IDxcBlobEncoding> errorBlob = default;
|
||||
result.Get()->GetErrorBuffer(errorBlob.GetAddressOf());
|
||||
|
||||
if (errorBlob.Get() != null)
|
||||
{
|
||||
var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer());
|
||||
throw new Exception($"DXC shader compilation failed: {errorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("DXC shader compilation failed with unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
// Get compiled bytecode
|
||||
using ComPtr<IDxcBlob> bytecodeBlob = default;
|
||||
result.Get()->GetResult(bytecodeBlob.GetAddressOf());
|
||||
|
||||
if (bytecodeBlob.Get() == null)
|
||||
{
|
||||
throw new Exception("DXC compilation succeeded but no bytecode was produced");
|
||||
}
|
||||
|
||||
// Get reflection data using DXC API
|
||||
using ComPtr<IDxcBlob> reflectionBlob = default;
|
||||
result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null);
|
||||
|
||||
if (reflectionBlob.Get() == null)
|
||||
{
|
||||
throw new Exception("DXC compilation succeeded but no reflection data was produced");
|
||||
}
|
||||
|
||||
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
|
||||
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
|
||||
|
||||
return new CompileResult
|
||||
{
|
||||
bytecode = bytecode,
|
||||
reflection = reflectionBlob.Move()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Free allocated wide strings
|
||||
for (var i = 0; i < argPointers.Length; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(argPointers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PerformDXCReflection(Shader shader, IDxcBlob* reflectionBlob)
|
||||
{
|
||||
// Create DXC utils to parse reflection data
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
|
||||
// Create reflection interface from blob
|
||||
var reflectionData = new DxcBuffer
|
||||
{
|
||||
Ptr = reflectionBlob->GetBufferPointer(),
|
||||
Size = reflectionBlob->GetBufferSize(),
|
||||
Encoding = DXC_CP_ACP
|
||||
};
|
||||
|
||||
using ComPtr<ID3D12ShaderReflection> reflection = default;
|
||||
utils.Get()->CreateReflection(&reflectionData, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf());
|
||||
|
||||
if (reflection.Get() == null)
|
||||
{
|
||||
throw new Exception("Failed to create shader reflection from DXC output");
|
||||
}
|
||||
|
||||
ShaderDescription shaderDesc;
|
||||
reflection.Get()->GetDesc(&shaderDesc);
|
||||
|
||||
var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name);
|
||||
var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name);
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
ShaderInputBindDescription bindDesc;
|
||||
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
|
||||
|
||||
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
|
||||
{
|
||||
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
|
||||
ShaderBufferDescription cbufferDesc;
|
||||
cbuffer->GetDesc(&cbufferDesc);
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = cbufferName,
|
||||
Size = cbufferDesc.Size,
|
||||
RegisterSlot = bindDesc.BindPoint
|
||||
};
|
||||
cbufferRegistry.Add(cbufferName, cbufferInfo);
|
||||
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
ShaderVariableDescription varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
|
||||
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propInfo = new PropertyInfo
|
||||
{
|
||||
Name = variableName,
|
||||
CBufferIndex = cbufferInfo.RegisterSlot,
|
||||
ByteOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
};
|
||||
|
||||
// Add to the list and create the name-to-ID mapping
|
||||
var newId = shader.Properties.Count;
|
||||
shader.Properties.Add(propInfo);
|
||||
shader.PropertyNameToIdMap.Add(variableName, newId);
|
||||
}
|
||||
}
|
||||
else if (bindDesc.Type == ShaderInputType.Texture)
|
||||
{
|
||||
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (textureName == null || textureRegistry.ContainsKey(textureName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ALL texture input slots are regular textures!
|
||||
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
|
||||
var textureInfo = new TextureInfo
|
||||
{
|
||||
Name = textureName,
|
||||
RegisterSlot = bindDesc.BindPoint,
|
||||
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
|
||||
};
|
||||
|
||||
textureRegistry.Add(textureName, textureInfo);
|
||||
}
|
||||
}
|
||||
|
||||
shader.ConstantBuffers.Clear();
|
||||
shader.ConstantBuffers.AddRange(cbufferRegistry.Values);
|
||||
|
||||
shader.RegularTextures.Clear();
|
||||
shader.RegularTextures.AddRange(textureRegistry.Values);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi;
|
||||
@@ -13,47 +15,46 @@ namespace Ghost.Graphics.D3D12;
|
||||
internal unsafe class D3D12SwapChain : ISwapChain
|
||||
{
|
||||
private ComPtr<IDXGISwapChain4> _swapChain;
|
||||
private readonly D3D12Texture[] _backBuffers;
|
||||
private readonly D3D12RenderTarget[] _backBufferRenderTargets;
|
||||
private uint _currentBackBufferIndex;
|
||||
private readonly D3D12RenderTarget[] _backBuffers;
|
||||
private bool _disposed;
|
||||
|
||||
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)
|
||||
public D3D12SwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc)
|
||||
{
|
||||
_backBuffers = new D3D12Texture[desc.BufferCount];
|
||||
_backBufferRenderTargets = new D3D12RenderTarget[desc.BufferCount];
|
||||
_backBuffers = new D3D12RenderTarget[D3D12PipelineResource.BACK_BUFFER_COUNT];
|
||||
|
||||
Width = desc.Width;
|
||||
Height = desc.Height;
|
||||
BufferCount = desc.BufferCount;
|
||||
Width = desc.width;
|
||||
Height = desc.height;
|
||||
BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT;
|
||||
|
||||
CreateSwapChain(factory, commandQueue, desc);
|
||||
CreateSwapChain(pFactory, pCommandQueue, desc);
|
||||
CreateBackBuffers();
|
||||
}
|
||||
|
||||
private void CreateSwapChain(ComPtr<IDXGIFactory7> factory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc)
|
||||
private void CreateSwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc)
|
||||
{
|
||||
var swapChainDesc = new SwapChainDescription1
|
||||
{
|
||||
Width = desc.Width,
|
||||
Height = desc.Height,
|
||||
Format = ConvertTextureFormat(desc.Format),
|
||||
Width = desc.width,
|
||||
Height = desc.height,
|
||||
Format = ConvertTextureFormat(desc.format),
|
||||
SampleDesc = new SampleDescription(1, 0),
|
||||
BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput,
|
||||
BufferCount = desc.BufferCount,
|
||||
BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT,
|
||||
Scaling = Scaling.Stretch,
|
||||
SwapEffect = SwapEffect.FlipDiscard,
|
||||
AlphaMode = AlphaMode.Ignore,
|
||||
@@ -63,15 +64,15 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
|
||||
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
|
||||
|
||||
switch (desc.Target.Type)
|
||||
switch (desc.target.Type)
|
||||
{
|
||||
case SwapChainTargetType.Composition:
|
||||
factory.Get()->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf());
|
||||
pFactory->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf());
|
||||
|
||||
// Set the composition surface
|
||||
if (desc.Target.CompositionSurface != null)
|
||||
if (desc.target.CompositionSurface != null)
|
||||
{
|
||||
var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.Target.CompositionSurface);
|
||||
using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.CompositionSurface);
|
||||
swapChainPanelNative.SetSwapChain((IntPtr)tempSwapChain.Get());
|
||||
}
|
||||
break;
|
||||
@@ -82,9 +83,9 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
Windowed = true,
|
||||
};
|
||||
|
||||
factory.Get()->CreateSwapChainForHwnd(
|
||||
pFactory->CreateSwapChainForHwnd(
|
||||
(IUnknown*)commandQueue,
|
||||
desc.Target.WindowHandle,
|
||||
desc.target.WindowHandle,
|
||||
&swapChainDesc,
|
||||
&swapChainFullscreenDesc,
|
||||
null,
|
||||
@@ -99,8 +100,6 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface.");
|
||||
}
|
||||
|
||||
_currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
}
|
||||
|
||||
private void CreateBackBuffers()
|
||||
@@ -111,23 +110,14 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), backBuffer.GetVoidAddressOf());
|
||||
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);
|
||||
_backBuffers[i] = D3D12RenderTarget.Create(backBuffer.Move(), Width, Height, 1, TextureFormat.B8G8R8A8_UNorm, RenderTargetType.Color);
|
||||
}
|
||||
}
|
||||
|
||||
public ITexture GetCurrentBackBuffer()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IRenderTarget GetCurrentBackBuffer()
|
||||
{
|
||||
_currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
return _backBuffers[_currentBackBufferIndex];
|
||||
}
|
||||
|
||||
public IRenderTarget GetCurrentBackBufferRenderTarget()
|
||||
{
|
||||
_currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
return _backBufferRenderTargets[_currentBackBufferIndex];
|
||||
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
|
||||
}
|
||||
|
||||
public void Present(bool vsync = true)
|
||||
@@ -149,7 +139,6 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
// Release old back buffers and render targets
|
||||
for (var i = 0; i < _backBuffers.Length; i++)
|
||||
{
|
||||
_backBufferRenderTargets[i]?.Dispose();
|
||||
_backBuffers[i]?.Dispose();
|
||||
}
|
||||
|
||||
@@ -164,7 +153,6 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
|
||||
// Recreate back buffers
|
||||
CreateBackBuffers();
|
||||
_currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
}
|
||||
|
||||
private static Format ConvertTextureFormat(TextureFormat format)
|
||||
@@ -188,7 +176,6 @@ internal unsafe class D3D12SwapChain : ISwapChain
|
||||
|
||||
for (var i = 0; i < _backBuffers.Length; i++)
|
||||
{
|
||||
_backBufferRenderTargets[i]?.Dispose();
|
||||
_backBuffers[i]?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ internal unsafe class D3D12Texture : ITexture
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Slice
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public TextureFormat Format
|
||||
{
|
||||
get;
|
||||
@@ -49,13 +54,14 @@ internal unsafe class D3D12Texture : ITexture
|
||||
|
||||
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)
|
||||
public D3D12Texture(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, uint mipLevels = 1)
|
||||
{
|
||||
_handle = TextureHandle.Invalid;
|
||||
_externalResource = resource.Move();
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Slice = slice;
|
||||
Format = format;
|
||||
MipLevels = mipLevels;
|
||||
_currentState = ResourceState.Common;
|
||||
@@ -69,6 +75,7 @@ internal unsafe class D3D12Texture : ITexture
|
||||
|
||||
Width = desc.Width;
|
||||
Height = desc.Height;
|
||||
Slice = desc.Slice;
|
||||
Format = desc.Format;
|
||||
|
||||
var mipLevels = desc.MipLevels;
|
||||
@@ -84,7 +91,7 @@ internal unsafe class D3D12Texture : ITexture
|
||||
|
||||
~D3D12Texture()
|
||||
{
|
||||
Dispose();
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private static uint GetBytesPerPixel(TextureFormat format)
|
||||
@@ -107,6 +114,12 @@ internal unsafe class D3D12Texture : ITexture
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
@@ -123,7 +136,5 @@ internal unsafe class D3D12Texture : ITexture
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,421 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
|
||||
/// </summary>
|
||||
internal class DescriptorAllocator : IDisposable
|
||||
{
|
||||
private readonly DescriptorHeapAllocator _rtvHeap;
|
||||
private readonly DescriptorHeapAllocator _dsvHeap;
|
||||
private readonly DescriptorHeapAllocator _srvHeap;
|
||||
private readonly DescriptorHeapAllocator _samplerHeap;
|
||||
private readonly BindlessDescriptorHeapAllocator _bindlessHeap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice;
|
||||
|
||||
_rtvHeap = new DescriptorHeapAllocator("rtv", device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount);
|
||||
_dsvHeap = new DescriptorHeapAllocator("dsv", device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount);
|
||||
_srvHeap = new DescriptorHeapAllocator("srv", device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
|
||||
_samplerHeap = new DescriptorHeapAllocator("sampler", device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount);
|
||||
_bindlessHeap = new BindlessDescriptorHeapAllocator(device.NativeDevice, initialBindlessCount);
|
||||
}
|
||||
|
||||
#region RTV Methods
|
||||
|
||||
public RenderTargetDescriptor AllocateRTV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _rtvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
return new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public RenderTargetDescriptor[] AllocateRTVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _rtvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new RenderTargetDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseRTV(RenderTargetDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is RenderTargetDescriptor d3d12Descriptor)
|
||||
{
|
||||
_rtvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseRTVs(RenderTargetDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseRTV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DSV Methods
|
||||
|
||||
public DepthStencilDescriptor AllocateDSV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _dsvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
return new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public DepthStencilDescriptor[] AllocateDSVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _dsvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new DepthStencilDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseDSV(DepthStencilDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is DepthStencilDescriptor d3d12Descriptor)
|
||||
{
|
||||
_dsvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseDSVs(DepthStencilDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseDSV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SRV Methods
|
||||
|
||||
public ShaderResourceDescriptor AllocateSRV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _srvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate SRV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public ShaderResourceDescriptor[] AllocateSRVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _srvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new ShaderResourceDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSRV(ShaderResourceDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is ShaderResourceDescriptor d3d12Descriptor)
|
||||
{
|
||||
_srvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSRVs(ShaderResourceDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSRV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sampler Methods
|
||||
|
||||
public SamplerDescriptor AllocateSampler()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _samplerHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public SamplerDescriptor[] AllocateSamplers(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _samplerHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new SamplerDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSampler(SamplerDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is SamplerDescriptor d3d12Descriptor)
|
||||
{
|
||||
_samplerHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSamplers(SamplerDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSampler(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bindless Methods
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a bindless descriptor for SM 6.6 rendering.
|
||||
/// The returned descriptor maintains a 1:1 relationship between allocation index and shader index.
|
||||
/// </summary>
|
||||
public BindlessDescriptor AllocateBindless()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _bindlessHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate bindless descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
|
||||
return new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates multiple bindless descriptors for SM 6.6 rendering.
|
||||
/// </summary>
|
||||
public BindlessDescriptor[] AllocateBindless(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _bindlessHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new BindlessDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a bindless descriptor.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is BindlessDescriptor d3d12Descriptor)
|
||||
{
|
||||
_bindlessHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases multiple bindless descriptors.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseBindless(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RTV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetRTVHeap() => _rtvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DSV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetDSVHeap() => _dsvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SRV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bindless heap for SM 6.6 bindless rendering.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetBindlessHeap() => _bindlessHeap.BindlessHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps that need to be bound to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeaps()
|
||||
{
|
||||
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps including bindless heap for SM 6.6 rendering.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeapsWithBindless()
|
||||
{
|
||||
return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rtvHeap.Dispose();
|
||||
_dsvHeap.Dispose();
|
||||
_srvHeap.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
_bindlessHeap.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,131 @@ using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// Base class descriptor implementations.
|
||||
/// </summary>
|
||||
public abstract class Descriptor
|
||||
{
|
||||
protected readonly uint index;
|
||||
protected readonly bool isShaderVisible;
|
||||
|
||||
protected Descriptor(uint index, bool isShaderVisible)
|
||||
{
|
||||
this.index = index;
|
||||
this.isShaderVisible = isShaderVisible;
|
||||
}
|
||||
|
||||
public bool IsValid => index != uint.MaxValue;
|
||||
public uint Index => index;
|
||||
public bool IsShaderVisible => isShaderVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU descriptor handle.
|
||||
/// </summary>
|
||||
public abstract CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPU descriptor handle (only valid for shader-visible descriptors).
|
||||
/// </summary>
|
||||
public abstract GpuDescriptorHandle GpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of render target view (RTV) descriptor.
|
||||
/// </summary>
|
||||
public sealed class RenderTargetDescriptor : Descriptor
|
||||
public readonly struct RenderTargetDescriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
: base(index, false)
|
||||
public uint Index
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
get;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => default;
|
||||
public CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
{
|
||||
Index = index;
|
||||
CpuHandle = cpuHandle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of depth stencil view (DSV) descriptor.
|
||||
/// </summary>
|
||||
public sealed class DepthStencilDescriptor : Descriptor
|
||||
public readonly struct DepthStencilDescriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
: base(index, false) // DSVs are not shader visible
|
||||
public uint Index
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
get;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => default;
|
||||
public CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
{
|
||||
Index = index;
|
||||
CpuHandle = cpuHandle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of shader resource view (SRV) descriptor.
|
||||
/// </summary>
|
||||
public sealed class ShaderResourceDescriptor : Descriptor
|
||||
public sealed class ShaderResourceDescriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
|
||||
public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
public uint Index
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
get;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
public CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public GpuDescriptorHandle GpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
{
|
||||
Index = index;
|
||||
CpuHandle = cpuHandle;
|
||||
GpuHandle = gpuHandle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of sampler descriptor.
|
||||
/// </summary>
|
||||
public sealed class SamplerDescriptor : Descriptor
|
||||
public sealed class SamplerDescriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
|
||||
public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
public uint Index
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
get;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
public CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public GpuDescriptorHandle GpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
{
|
||||
Index = index;
|
||||
CpuHandle = cpuHandle;
|
||||
GpuHandle = gpuHandle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of bindless descriptor for SM 6.6 rendering.
|
||||
/// This descriptor maintains a 1:1 relationship between allocation indices and shader indices.
|
||||
/// </summary>
|
||||
public sealed class BindlessDescriptor : Descriptor
|
||||
public sealed class BindlessDescriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
public uint Index
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public GpuDescriptorHandle GpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public BindlessDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
Index = index;
|
||||
CpuHandle = cpuHandle;
|
||||
GpuHandle = gpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable
|
||||
{
|
||||
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
|
||||
|
||||
private readonly ConstPtr<ID3D12Device14> _device;
|
||||
private readonly ComPtr<ID3D12Device14> _device;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private ComPtr<ID3D12DescriptorHeap> _bindlessHeap;
|
||||
@@ -42,12 +42,14 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable
|
||||
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> BindlessHeap => new(_bindlessHeap.Get());
|
||||
|
||||
public BindlessDescriptorHeapAllocator(ConstPtr<ID3D12Device14> device, uint numDescriptors = 10000)
|
||||
public BindlessDescriptorHeapAllocator(ComPtr<ID3D12Device14> device, uint numDescriptors = 10000)
|
||||
{
|
||||
_device = device;
|
||||
device.Get()->AddRef();
|
||||
|
||||
HeapType = DescriptorHeapType.CbvSrvUav;
|
||||
NumDescriptors = numDescriptors;
|
||||
_stride = device.Ptr->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
|
||||
_stride = device.Get()->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
|
||||
_freeDescriptors = new Queue<uint>();
|
||||
|
||||
var success = AllocateResources(numDescriptors);
|
||||
@@ -169,7 +171,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable
|
||||
|
||||
fixed (void* heapPtr = &_bindlessHeap)
|
||||
{
|
||||
var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
if (hr.Failure)
|
||||
{
|
||||
return false;
|
||||
@@ -204,7 +206,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable
|
||||
// Copy old descriptors to new heap
|
||||
if (oldHeap.Get() is not null)
|
||||
{
|
||||
_device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
_device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Ghost.Graphics.D3D12.Utilities;
|
||||
|
||||
internal unsafe static class D3D12PipelineResource
|
||||
{
|
||||
public const int BACK_BUFFER_COUNT = 2;
|
||||
|
||||
private readonly static InputElementDescription[] s_inputElementDescs = [
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.PositionName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
new InputElementDescription{ SemanticName = Vertex.Semantic.NormalName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
@@ -11,8 +10,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
{
|
||||
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
|
||||
|
||||
private readonly ConstPtr<ID3D12Device14> _device;
|
||||
private readonly Lock _lock = new();
|
||||
private ComPtr<ID3D12Device14> _device;
|
||||
|
||||
private ComPtr<ID3D12DescriptorHeap> _heap;
|
||||
private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
|
||||
@@ -22,6 +20,8 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
private DescriptorIndex _searchStart;
|
||||
private bool[] _allocatedDescriptors = [];
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public DescriptorHeapType HeapType
|
||||
{
|
||||
get;
|
||||
@@ -47,16 +47,18 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
get;
|
||||
}
|
||||
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> Heap => new(_heap.Get());
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> ShaderVisibleHeap => new(_shaderVisibleHeap.Get());
|
||||
public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
|
||||
public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
|
||||
|
||||
public DescriptorHeapAllocator(string name, ConstPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
public DescriptorHeapAllocator(string name, ComPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
{
|
||||
_device = device;
|
||||
device.Get()->AddRef();
|
||||
|
||||
HeapType = type;
|
||||
NumDescriptors = numDescriptors;
|
||||
ShaderVisible = type == DescriptorHeapType.CbvSrvUav || type == DescriptorHeapType.Sampler;
|
||||
Stride = device.Ptr->GetDescriptorHandleIncrementSize(type);
|
||||
Stride = device.Get()->GetDescriptorHandleIncrementSize(type);
|
||||
|
||||
var success = AllocateResources(numDescriptors);
|
||||
Debug.Assert(success);
|
||||
@@ -172,7 +174,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
|
||||
public void CopyToShaderVisibleHeap(DescriptorIndex index, uint count = 1)
|
||||
{
|
||||
_device.Ptr->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
|
||||
_device.Get()->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
|
||||
}
|
||||
|
||||
private bool AllocateResources(uint numDescriptors)
|
||||
@@ -191,7 +193,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
|
||||
fixed (void* heapPtr = &_heap)
|
||||
{
|
||||
var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
if (hr.Failure)
|
||||
{
|
||||
return false;
|
||||
@@ -207,7 +209,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
|
||||
fixed (void* heapPtr = &_shaderVisibleHeap)
|
||||
{
|
||||
var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
|
||||
if (hr.Failure)
|
||||
{
|
||||
return false;
|
||||
@@ -233,11 +235,11 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
_device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
_device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
|
||||
if (_shaderVisibleHeap.Get() is not null)
|
||||
{
|
||||
_device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
_device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -246,6 +248,8 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_device.Dispose();
|
||||
|
||||
_heap.Dispose();
|
||||
_shaderVisibleHeap.Dispose();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Shading;
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
internal struct CBufferCache : IDisposable
|
||||
{
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.Shading;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
@@ -12,10 +12,11 @@ namespace Ghost.Graphics.Data;
|
||||
public unsafe class Material : IDisposable
|
||||
{
|
||||
private readonly CBufferCache[] _cbufferCaches;
|
||||
private readonly List<Texture2D> _textures = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches;
|
||||
|
||||
public Shader Shader
|
||||
{
|
||||
get; set;
|
||||
@@ -129,23 +130,12 @@ public unsafe class Material : IDisposable
|
||||
SetMatrix(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a bindless texture to the material and returns its index
|
||||
/// </summary>
|
||||
/// <param name="texture">The bindless texture to add</param>
|
||||
/// <returns>The index of the texture in the material's texture list</returns>
|
||||
public int AddTexture(Texture2D texture)
|
||||
{
|
||||
_textures.Add(texture);
|
||||
return _textures.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a texture index for a shader property (for bindless texture access)
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
|
||||
/// <param name="texture">The bindless texture to reference</param>
|
||||
public void SetTextureIndex(string propertyName, Texture2D texture)
|
||||
public void SetTexture(string propertyName, Texture2D texture)
|
||||
{
|
||||
SetUInt(propertyName, texture.DescriptorIndex);
|
||||
}
|
||||
@@ -156,18 +146,14 @@ public unsafe class Material : IDisposable
|
||||
/// <param name="mesh">The mesh whose buffer indices to set</param>
|
||||
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
|
||||
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
|
||||
public void SetMeshBufferIndices(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
|
||||
public void SetMeshBuffer(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
|
||||
{
|
||||
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
|
||||
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all textures used by this material
|
||||
/// </summary>
|
||||
public IReadOnlyList<Texture2D> Textures => _textures;
|
||||
|
||||
private unsafe void WriteToCache<T>(int propertyId, in T value) where T : unmanaged
|
||||
private unsafe void WriteToCache<T>(int propertyId, in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (propertyId == -1)
|
||||
{
|
||||
@@ -177,7 +163,7 @@ public unsafe class Material : IDisposable
|
||||
var propInfo = Shader.Properties[propertyId];
|
||||
if (propInfo.Size != sizeof(T))
|
||||
{
|
||||
throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.");
|
||||
throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var cache = _cbufferCaches[propInfo.CBufferIndex];
|
||||
@@ -196,37 +182,6 @@ public unsafe class Material : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds the material for bindless rendering
|
||||
/// </summary>
|
||||
/// <param name="cmd">Command list to bind to</param>
|
||||
internal void Bind(CommandList cmd)
|
||||
{
|
||||
var commandList = cmd.NativeCommandList.Ptr;
|
||||
|
||||
// Set root signature and pipeline state
|
||||
commandList->SetGraphicsRootSignature(Shader.RootSignature);
|
||||
commandList->SetPipelineState(Shader.PipelineState);
|
||||
|
||||
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
|
||||
var heaps = stackalloc ID3D12DescriptorHeap*[2];
|
||||
heaps[0] = GraphicsPipeline.DescriptorAllocator.GetBindlessHeap().Ptr; // Specialized bindless heap
|
||||
heaps[1] = Shader.SamplerHeap.Ptr; // Sampler heap from shader
|
||||
commandList->SetDescriptorHeaps(2, heaps);
|
||||
|
||||
// Bind constant buffers
|
||||
var rootParamIndex = 0u;
|
||||
foreach (var cbufferInfo in Shader.ConstantBuffers)
|
||||
{
|
||||
var cache = _cbufferCaches[cbufferInfo.RegisterSlot];
|
||||
commandList->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress);
|
||||
}
|
||||
|
||||
// Bind sampler descriptor table (last root parameter)
|
||||
var samplerGpuHandle = Shader.SamplerHeap.Ptr->GetGPUDescriptorHandleForHeapStart();
|
||||
commandList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
|
||||
@@ -3,7 +3,7 @@ using Win32.Graphics.D3D12MemoryAllocator;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
internal readonly struct ResourceHandle : IEquatable<ResourceHandle>, IDisposable
|
||||
public readonly struct ResourceHandle : IEquatable<ResourceHandle>, IDisposable
|
||||
{
|
||||
private const int _INVALID_ID = -1;
|
||||
|
||||
@@ -28,7 +28,7 @@ internal readonly struct ResourceHandle : IEquatable<ResourceHandle>, IDisposabl
|
||||
throw new InvalidOperationException("Cannot get allocation from an invalid AllocationHandle.");
|
||||
}
|
||||
|
||||
return GraphicsPipeline.ResourceAllocator.GetAllocation(this);
|
||||
return GraphicsPipeline.ResourceAllocator.GetResource(this);
|
||||
}
|
||||
|
||||
public bool Equals(ResourceHandle other)
|
||||
@@ -51,7 +51,7 @@ internal readonly struct ResourceHandle : IEquatable<ResourceHandle>, IDisposabl
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GraphicsPipeline.ResourceAllocator.ReleaseAllocation(this);
|
||||
GraphicsPipeline.ResourceAllocator.ReleaseResource(this);
|
||||
}
|
||||
|
||||
public static implicit operator Allocation(ResourceHandle handle)
|
||||
@@ -79,8 +79,7 @@ public readonly struct TextureHandle : IEquatable<TextureHandle>, IDisposable
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
|
||||
internal ResourceHandle ResourceHandle => _resourceHandle;
|
||||
|
||||
public ResourceHandle ResourceHandle => _resourceHandle;
|
||||
public static TextureHandle Invalid => new(ResourceHandle.Invalid);
|
||||
|
||||
internal TextureHandle(ResourceHandle resourceHandle)
|
||||
@@ -125,8 +124,7 @@ public readonly struct BufferHandle : IEquatable<BufferHandle>, IDisposable
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
|
||||
internal ResourceHandle ResourceHandle => _resourceHandle;
|
||||
|
||||
public ResourceHandle ResourceHandle => _resourceHandle;
|
||||
public static BufferHandle Invalid => new(ResourceHandle.Invalid);
|
||||
|
||||
internal BufferHandle(ResourceHandle resourceHandle)
|
||||
|
||||
116
Ghost.Graphics/Data/Shader.cs
Normal file
116
Ghost.Graphics/Data/Shader.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
internal readonly struct TextureInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RegisterSlot
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct PropertyInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint CBufferIndex
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint ByteOffset
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct CBufferInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RegisterSlot
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bindless shader implementation using SM 6.6 with ResourceDescriptorHeap
|
||||
/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
|
||||
/// Enhanced to support both bindless and regular texture binding for hybrid materials
|
||||
/// </summary>
|
||||
public unsafe class Shader : IDisposable
|
||||
{
|
||||
private readonly string _source;
|
||||
|
||||
private readonly List<CBufferInfo> _constantBuffers = new();
|
||||
private readonly List<PropertyInfo> _properties = new();
|
||||
private readonly List<TextureInfo> _regularTextures = new(); // Add regular texture support
|
||||
private readonly Dictionary<string, int> _propertyNameToIdMap = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal string Source => _source;
|
||||
|
||||
internal List<CBufferInfo> ConstantBuffers => _constantBuffers;
|
||||
internal List<PropertyInfo> Properties => _properties;
|
||||
internal List<TextureInfo> RegularTextures => _regularTextures;
|
||||
internal Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
|
||||
|
||||
internal Shader(string shaderCode)
|
||||
{
|
||||
_source = shaderCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique, stable ID for a shader property.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property (e.g., "_Color").</param>
|
||||
/// <returns>The integer ID of the property, or -1 if not found.</returns>
|
||||
public int GetPropertyId(string propertyName)
|
||||
{
|
||||
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_constantBuffers.Clear();
|
||||
_properties.Clear();
|
||||
_propertyNameToIdMap.Clear();
|
||||
_regularTextures.Clear();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
# Bindless Rendering in Ghost Engine: A Technical Deep-Dive
|
||||
|
||||
This document provides a detailed explanation of the bindless rendering architecture implemented in the Ghost Engine, leveraging modern Direct3D 12 features.
|
||||
|
||||
## 1. Introduction to Bindless Rendering
|
||||
|
||||
Traditional rendering approaches require the CPU to explicitly bind resources (textures, buffers, etc.) to specific "slots" in the shader pipeline before a draw call. This process, known as "binding," involves creating descriptor tables, setting them on the command list, and managing resource state transitions. While functional, this can lead to significant CPU overhead, complex state management, and a high number of draw calls.
|
||||
|
||||
**Bindless rendering** revolutionizes this by moving resource selection from the CPU to the GPU. Instead of binding individual resources, we bind a single, massive descriptor heap containing descriptors for (potentially) all resources. Shaders can then access any resource in this heap using a simple index, which can be passed via a constant buffer or calculated dynamically.
|
||||
|
||||
**Key Advantages:**
|
||||
- **Reduced CPU Overhead:** Eliminates the need for constant re-binding of resources and management of numerous descriptor tables.
|
||||
- **Simplified Rendering Code:** Drastically simplifies the logic for drawing objects with different materials.
|
||||
- **Increased GPU Autonomy:** Allows the GPU to fetch required data on its own, leading to more efficient execution.
|
||||
- **Enables Advanced Techniques:** Opens the door for techniques like "fully bindless" rendering, where even vertex and index data are fetched manually in the vertex shader.
|
||||
|
||||
The Ghost Engine implements a state-of-the-art bindless system using DirectX 12's Shader Model 6.6 capabilities.
|
||||
|
||||
## 2. Core D3D12 Implementation
|
||||
|
||||
The foundation of the engine's bindless architecture rests on a few key D3D12 features.
|
||||
|
||||
### 2.1. The Bindless Root Signature
|
||||
|
||||
The root signature defines how shaders access resources. For bindless, we use a special flag: `D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED`. This tells the GPU that shaders will be able to directly index the entire CBV/SRV/UAV descriptor heap.
|
||||
|
||||
Here's how the root signature is created in `Ghost.Graphics\Shading\Shader.cs`:
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\Shading\Shader.cs
|
||||
|
||||
private void CreateBindlessRootSignature()
|
||||
{
|
||||
// ... (parameter setup for constant buffers and samplers)
|
||||
|
||||
// Create root signature with the modern flag
|
||||
fixed (RootParameter1* rootParamsPtr = rootParameters)
|
||||
{
|
||||
var rootSignatureDesc = new RootSignatureDescription1
|
||||
{
|
||||
// ... (parameters)
|
||||
|
||||
// Key difference: Use the modern flag for direct heap indexing
|
||||
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
|
||||
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
|
||||
};
|
||||
|
||||
var versionedDesc = new VersionedRootSignatureDescription
|
||||
{
|
||||
Version = RootSignatureVersion.V1_1,
|
||||
Desc_1_1 = rootSignatureDesc
|
||||
};
|
||||
|
||||
// ... (serialization and creation)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With this flag, the HLSL global `ResourceDescriptorHeap` becomes accessible, representing the entire heap of shader-visible resources.
|
||||
|
||||
### 2.2. Resource Descriptors
|
||||
|
||||
For a resource to be accessible in a bindless fashion, it needs a Shader Resource View (SRV) created in the global, shader-visible descriptor heap. This is handled by the `Mesh` and `Texture2D` classes.
|
||||
|
||||
#### Texture2D Descriptors
|
||||
|
||||
When a `Texture2D` is created, it allocates a "bindless descriptor" and creates an SRV for itself at that descriptor's location.
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\Data\Texture2D.cs
|
||||
|
||||
private BindlessDescriptor CreateBindlessDescriptors()
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
// Allocate bindless descriptor from the descriptor allocator
|
||||
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
// Create the SRV description
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
// Create the SRV in the bindless heap
|
||||
device->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, bindlessDescriptor.CpuHandle);
|
||||
|
||||
return bindlessDescriptor;
|
||||
}
|
||||
```
|
||||
The `bindlessDescriptor.Index` now holds the unique integer ID that shaders will use to access this texture.
|
||||
|
||||
#### Mesh Buffer Descriptors (Fully Bindless)
|
||||
|
||||
The engine takes bindless a step further by also making vertex and index buffers available as bindless resources. This enables a "fully bindless" or "meshlet-style" rendering approach where the Vertex Shader manually fetches its own data, bypassing the Input Assembler stage.
|
||||
|
||||
The `Mesh` class creates SRVs for its vertex and index buffers, treating them as raw byte-addressable buffers.
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\Data\Mesh.cs
|
||||
|
||||
private void CreateBindlessDescriptors()
|
||||
{
|
||||
// ... (null checks)
|
||||
|
||||
_vertexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
_indexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
// Create SRV for vertex buffer (as ByteAddressBuffer)
|
||||
var vertexSrvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format.R32Typeless,
|
||||
ViewDimension = SrvDimension.Buffer,
|
||||
// ...
|
||||
Buffer = new()
|
||||
{
|
||||
// ...
|
||||
Flags = BufferSrvFlags.Raw // ByteAddressBuffer
|
||||
}
|
||||
};
|
||||
device->CreateShaderResourceView(_vertexBuffer.NativeResource.Ptr, &vertexSrvDesc, _vertexBufferDescriptor.CpuHandle);
|
||||
|
||||
// Create SRV for index buffer (as ByteAddressBuffer)
|
||||
var indexSrvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format.R32Typeless,
|
||||
ViewDimension = SrvDimension.Buffer,
|
||||
// ...
|
||||
Buffer = new()
|
||||
{
|
||||
// ...
|
||||
Flags = BufferSrvFlags.Raw // ByteAddressBuffer
|
||||
}
|
||||
};
|
||||
device->CreateShaderResourceView(_indexBuffer.NativeResource.Ptr, &indexSrvDesc, _indexBufferDescriptor.CpuHandle);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. HLSL Shader Implementation
|
||||
|
||||
With the D3D12 backend in place, accessing resources in HLSL becomes remarkably simple. The `MeshRenderPass.cs` file contains a perfect example of a fully bindless shader.
|
||||
|
||||
### 3.1. The Constant Buffer
|
||||
|
||||
First, we define a constant buffer to pass the resource indices from the CPU to the GPU.
|
||||
|
||||
```hlsl
|
||||
// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs
|
||||
|
||||
cbuffer ConstantBuffer : register(b0)
|
||||
{
|
||||
float4 _Color;
|
||||
uint _TextureIndex1;
|
||||
uint _TextureIndex2;
|
||||
uint _TextureIndex3;
|
||||
uint _TextureIndex4;
|
||||
uint _VertexBufferIndex;
|
||||
uint _IndexBufferIndex;
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2. Vertex Shader: Manual Vertex Fetching
|
||||
|
||||
The vertex shader (`VSMain`) demonstrates the power of fully bindless rendering. Instead of receiving pre-fetched vertex attributes, it receives only a `vertexId` and `instanceId`. It uses the `_VertexBufferIndex` and `_IndexBufferIndex` to access the correct buffers from the global `ResourceDescriptorHeap` and manually loads the vertex data.
|
||||
|
||||
```hlsl
|
||||
// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs
|
||||
|
||||
PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
|
||||
{
|
||||
// Get bindless buffers from the global heap
|
||||
ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex];
|
||||
ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex];
|
||||
|
||||
// Each instance represents one triangle
|
||||
// vertexId is 0, 1, or 2
|
||||
|
||||
// Calculate index into the index buffer to find the vertex index
|
||||
uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index
|
||||
uint vertexIndex = indexBuffer.Load(indexOffset);
|
||||
|
||||
// Calculate offset into the vertex buffer using the vertex index
|
||||
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4)
|
||||
|
||||
// Load vertex data from the bindless vertex buffer
|
||||
Vertex vertex;
|
||||
vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
|
||||
vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
|
||||
// ... etc.
|
||||
|
||||
// ...
|
||||
return output;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3. Pixel Shader: Bindless Texture Sampling
|
||||
|
||||
The pixel shader (`PSMain`) uses the same principle to access textures. It uses the `_TextureIndexN` values to grab the correct `Texture2D` from the `ResourceDescriptorHeap` and samples them.
|
||||
|
||||
```hlsl
|
||||
// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs
|
||||
|
||||
float4 PSMain(PixelInput input) : SV_TARGET
|
||||
{
|
||||
// Access textures directly from the heap using their indices
|
||||
Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
|
||||
Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
|
||||
Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
|
||||
Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
|
||||
|
||||
// Sample the textures
|
||||
float4 color1 = tex1.Sample(_MainSampler, input.uv.xy);
|
||||
// ... etc.
|
||||
|
||||
// Blend all textures together
|
||||
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
||||
|
||||
return blendedColor * _Color;
|
||||
}
|
||||
```
|
||||
|
||||
## 4. C# Usage Example
|
||||
|
||||
The C# side of the implementation is elegant and straightforward, abstracting the low-level details. The `MeshRenderPass` class demonstrates a typical setup.
|
||||
|
||||
### 4.1. Initialization
|
||||
|
||||
During initialization, we create the mesh and textures. The `UploadMeshData` and `UploadTextureData` calls ensure the data is on the GPU, and the underlying `Mesh` and `Texture2D` constructors have already created the necessary bindless SRVs.
|
||||
|
||||
We then create a `Material` and use it to bridge the gap between the C# objects and the shader's constant buffer properties.
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs
|
||||
|
||||
public void Initialize(CommandList cmd)
|
||||
{
|
||||
_mesh = MeshBuilder.CreateCube(0.75f);
|
||||
_mesh.UploadMeshData();
|
||||
|
||||
_shader = new Shader(_HLSL_SOURCE);
|
||||
_material = new Material(_shader);
|
||||
|
||||
// Load textures
|
||||
_textures = new Texture2D[_textureFiles.Length];
|
||||
for (var i = 0; i < _textureFiles.Length; i++)
|
||||
{
|
||||
_textures[i] = Texture2D.FromFile(_textureFiles[i]);
|
||||
_textures[i].UploadTextureData();
|
||||
}
|
||||
|
||||
// Set material properties, passing the descriptor indices to the shader
|
||||
_material.SetVector("_Color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
for (var i = 0; i < _textures.Length; i++)
|
||||
{
|
||||
var texture = _textures[i];
|
||||
// This sets the uint property in the CBuffer
|
||||
_material.SetTextureIndex($"_TextureIndex{i + 1}", texture);
|
||||
}
|
||||
|
||||
// This is a helper on Material that sets _VertexBufferIndex and _IndexBufferIndex
|
||||
// It is not used in the provided code, but it's the intended way.
|
||||
// For this example, the indices are set implicitly by the DrawMeshBindless call.
|
||||
// material.SetMeshBufferIndices(mesh);
|
||||
|
||||
// Uploads the constant buffer data to the GPU
|
||||
_material.UploadMaterialData();
|
||||
}
|
||||
```
|
||||
*Note: The `Material.SetTextureIndex` method is a convenient wrapper that calls `SetUInt` internally, passing the `texture.DescriptorIndex`.*
|
||||
|
||||
### 4.2. Execution
|
||||
|
||||
Executing the render pass is now incredibly simple. The `DrawMeshBindless` command takes the mesh and material, binds the necessary state (Root Signature, PSO, and the material's constant buffer), and issues a single `DrawInstanced` call.
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs
|
||||
|
||||
public void Execute(CommandList cmd)
|
||||
{
|
||||
// This single call handles everything!
|
||||
cmd.DrawMeshBindless(_mesh!, _material!);
|
||||
}
|
||||
```
|
||||
|
||||
The `DrawMeshBindless` method (in `CommandList.cs`) is where the magic happens. It doesn't need to set vertex or index buffers on the Input Assembler. It just needs to know the number of triangles to draw.
|
||||
|
||||
```csharp
|
||||
// From: Ghost.Graphics\D3D12\CommandList.cs (Conceptual)
|
||||
|
||||
public void DrawMeshBindless(Mesh mesh, Material material)
|
||||
{
|
||||
// 1. Bind the material (sets PSO, Root Signature, CBuffers, Samplers)
|
||||
material.Bind(this);
|
||||
|
||||
// 2. Set the mesh buffer indices on the material
|
||||
// This is the crucial step that connects the mesh to the shader
|
||||
material.SetMeshBufferIndices(mesh);
|
||||
material.UploadMaterialData(); // Re-upload CBuffer with mesh indices
|
||||
|
||||
// 3. Draw instanced.
|
||||
// - VertexCountPerInstance = 3 (for a triangle)
|
||||
// - InstanceCount = total number of triangles in the mesh
|
||||
// - StartVertexLocation = 0
|
||||
// - StartInstanceLocation = 0
|
||||
uint triangleCount = mesh.IndexCount / 3;
|
||||
_commandList.Get()->DrawInstanced(3, triangleCount, 0, 0);
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Conclusion
|
||||
|
||||
The bindless architecture in the Ghost Engine is a powerful, modern, and efficient way to handle rendering. By leveraging Shader Model 6.6, it significantly reduces CPU overhead and simplifies rendering logic, paving the way for more complex and dynamic scenes. The "fully bindless" approach for mesh data further enhances this paradigm, offering maximum flexibility and performance on the GPU.
|
||||
@@ -1,123 +0,0 @@
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.D3D12;
|
||||
|
||||
namespace Ghost.Graphics.Examples;
|
||||
|
||||
/// <summary>
|
||||
/// Example showing how to use the new RHI architecture
|
||||
/// </summary>
|
||||
public class ModernRenderingExample
|
||||
{
|
||||
private IRenderDevice _device;
|
||||
private RenderSystem _renderSystem;
|
||||
private IRenderer _forwardRenderer;
|
||||
private ISwapChain _swapChain;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// Create modern RHI device
|
||||
_device = new D3D12RenderDevice();
|
||||
|
||||
// Create render system for frame management
|
||||
_renderSystem = new RenderSystem(_device);
|
||||
|
||||
// Create a renderer using RHI abstractions
|
||||
_forwardRenderer = new D3D12Renderer(_device);
|
||||
|
||||
// Create swap chain for presentation
|
||||
var swapChainDesc = new SwapChainDesc
|
||||
{
|
||||
Width = 1920,
|
||||
Height = 1080,
|
||||
BufferCount = 2,
|
||||
Format = TextureFormat.R8G8B8A8_UNorm,
|
||||
// Presenter would be set based on your window system
|
||||
};
|
||||
_swapChain = _device.CreateSwapChain(swapChainDesc);
|
||||
|
||||
// Configure renderer
|
||||
_forwardRenderer.SetSwapChain(_swapChain);
|
||||
|
||||
// Register renderer with render system
|
||||
_renderSystem.AddRenderer(_forwardRenderer);
|
||||
|
||||
// Start rendering loop
|
||||
_renderSystem.Start();
|
||||
}
|
||||
|
||||
public void RenderOffscreen()
|
||||
{
|
||||
// Example of rendering to off-screen color target
|
||||
var colorRenderTarget = RenderTargetDesc.Color(1024, 1024, TextureFormat.R8G8B8A8_UNorm);
|
||||
var offscreenColorTarget = _device.CreateRenderTarget(colorRenderTarget);
|
||||
|
||||
var colorRenderer = new D3D12Renderer(_device);
|
||||
colorRenderer.SetRenderTarget(offscreenColorTarget);
|
||||
_renderSystem.AddRenderer(colorRenderer);
|
||||
|
||||
// Example of rendering to depth target
|
||||
var depthRenderTarget = RenderTargetDesc.Depth(1024, 1024, TextureFormat.D24_UNorm_S8_UInt);
|
||||
var offscreenDepthTarget = _device.CreateRenderTarget(depthRenderTarget);
|
||||
|
||||
var depthRenderer = new D3D12Renderer(_device);
|
||||
depthRenderer.SetRenderTarget(offscreenDepthTarget);
|
||||
_renderSystem.AddRenderer(depthRenderer);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Signal that CPU work is ready
|
||||
_renderSystem.SignalCPUReady();
|
||||
|
||||
// Wait for GPU to complete previous frame (optional)
|
||||
_renderSystem.WaitForGPUReady(16); // 16ms timeout for 60fps
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderSystem?.Stop();
|
||||
_renderSystem?.Dispose();
|
||||
|
||||
_forwardRenderer?.Dispose();
|
||||
_swapChain?.Dispose();
|
||||
_device?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example showing legacy vs modern usage
|
||||
/// </summary>
|
||||
public static class LegacyVsModernExample
|
||||
{
|
||||
public static void LegacyApproach()
|
||||
{
|
||||
// OLD WAY - tightly coupled to D3D12
|
||||
GraphicsPipeline.Initialize();
|
||||
|
||||
var graphicsDevice = GraphicsPipeline.GraphicsDevice;
|
||||
// Renderer creation and management handled internally
|
||||
// Frame synchronization handled by GraphicsPipeline
|
||||
|
||||
GraphicsPipeline.Start();
|
||||
GraphicsPipeline.SignalCPUReady();
|
||||
GraphicsPipeline.WaitForGPUReady();
|
||||
}
|
||||
|
||||
public static void ModernApproach()
|
||||
{
|
||||
// NEW WAY - clean RHI abstractions
|
||||
var device = new D3D12RenderDevice();
|
||||
var renderSystem = new RenderSystem(device);
|
||||
|
||||
var renderer = new D3D12Renderer(device);
|
||||
var swapChain = device.CreateSwapChain(new SwapChainDesc { /* ... */ });
|
||||
|
||||
renderer.SetSwapChain(swapChain);
|
||||
renderSystem.AddRenderer(renderer);
|
||||
|
||||
renderSystem.Start();
|
||||
renderSystem.SignalCPUReady();
|
||||
renderSystem.WaitForGPUReady();
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@ public static class GraphicsPipeline
|
||||
internal const uint _FRAME_COUNT = 2;
|
||||
|
||||
#if DEBUG
|
||||
private static DebugLayer? s_debugLayer;
|
||||
private static D3D12DebugLayer? s_debugLayer;
|
||||
#endif
|
||||
|
||||
private static GraphicsDevice? s_graphicsDevice;
|
||||
private static DescriptorAllocator? s_descriptorAllocator;
|
||||
private static D3D12ResourceAllocator? s_resourceAllocator;
|
||||
private static D3D12DescriptorAllocator? s_descriptorAllocator;
|
||||
private static D3D12DescriptorAllocator? s_resourceAllocator;
|
||||
private static ResourceUploadBatch? s_uploadBatch;
|
||||
|
||||
// New RHI-based device for modern usage
|
||||
@@ -31,8 +31,8 @@ public static class GraphicsPipeline
|
||||
private static bool s_initialized;
|
||||
|
||||
internal static GraphicsDevice GraphicsDevice => s_graphicsDevice ?? throw new InvalidOperationException("Graphics device is not initialized.");
|
||||
internal static D3D12ResourceAllocator ResourceAllocator => s_resourceAllocator ?? throw new InvalidOperationException("Resource allocator is not initialized.");
|
||||
internal static DescriptorAllocator DescriptorAllocator => s_descriptorAllocator ?? throw new InvalidOperationException("Descriptor allocator is not initialized.");
|
||||
internal static D3D12DescriptorAllocator ResourceAllocator => s_resourceAllocator ?? throw new InvalidOperationException("Resource allocator is not initialized.");
|
||||
internal static D3D12DescriptorAllocator DescriptorAllocator => s_descriptorAllocator ?? throw new InvalidOperationException("Descriptor allocator is not initialized.");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modern RHI render device - prefer this over legacy GraphicsDevice
|
||||
@@ -61,7 +61,7 @@ public static class GraphicsPipeline
|
||||
internal static unsafe void Initialize()
|
||||
{
|
||||
#if DEBUG
|
||||
s_debugLayer = new DebugLayer();
|
||||
s_debugLayer = new D3D12DebugLayer();
|
||||
#endif
|
||||
// Initialize legacy components for compatibility
|
||||
s_graphicsDevice = new GraphicsDevice();
|
||||
|
||||
@@ -10,36 +10,36 @@ public interface ICommandBuffer : IDisposable
|
||||
/// <summary>
|
||||
/// Begins recording commands into this command buffer
|
||||
/// </summary>
|
||||
void Begin();
|
||||
public void Begin();
|
||||
|
||||
/// <summary>
|
||||
/// Ends recording commands and prepares for submission
|
||||
/// </summary>
|
||||
void End();
|
||||
public void End();
|
||||
|
||||
/// <summary>
|
||||
/// Begins a render pass with the specified render target
|
||||
/// </summary>
|
||||
/// <param name="renderTarget">Render target to render into</param>
|
||||
/// <param name="clearColor">Color to clear the render target with</param>
|
||||
void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor);
|
||||
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current render pass
|
||||
/// </summary>
|
||||
void EndRenderPass();
|
||||
public void EndRenderPass();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the viewport for rendering
|
||||
/// </summary>
|
||||
/// <param name="viewport">Viewport to set</param>
|
||||
void SetViewport(ViewportDesc viewport);
|
||||
public void SetViewport(ViewportDesc viewport);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scissor rectangle
|
||||
/// </summary>
|
||||
/// <param name="rect">Scissor rectangle to set</param>
|
||||
void SetScissorRect(RectDesc rect);
|
||||
public void SetScissorRect(RectDesc rect);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a resource barrier for state transitions
|
||||
@@ -47,35 +47,26 @@ public interface ICommandBuffer : IDisposable
|
||||
/// <param name="resource">Resource to transition</param>
|
||||
/// <param name="before">Current resource state</param>
|
||||
/// <param name="after">Target resource state</param>
|
||||
void ResourceBarrier(IResource resource, ResourceState before, ResourceState after);
|
||||
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the graphics root signature
|
||||
/// </summary>
|
||||
/// <param name="rootSignature">Root signature to set</param>
|
||||
void SetGraphicsRootSignature(IRootSignature rootSignature);
|
||||
public void SetGraphicsRootSignature(IRootSignature rootSignature);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pipeline state object
|
||||
/// </summary>
|
||||
/// <param name="pipelineState">Pipeline state to set</param>
|
||||
void SetPipelineState(IPipelineState pipelineState);
|
||||
public void SetPipelineState(IPipelineStateController pipelineState);
|
||||
|
||||
/// <summary>
|
||||
/// Sets descriptor heaps for bindless rendering
|
||||
/// Renders the specified mesh using the provided material.
|
||||
/// </summary>
|
||||
/// <param name="heaps">Descriptor heaps to set</param>
|
||||
void SetDescriptorHeaps(IDescriptorHeap[] heaps);
|
||||
|
||||
/// <summary>
|
||||
/// Draws indexed geometry
|
||||
/// </summary>
|
||||
/// <param name="indexCount">Number of indices to draw</param>
|
||||
/// <param name="instanceCount">Number of instances to draw</param>
|
||||
/// <param name="startIndex">Starting index location</param>
|
||||
/// <param name="baseVertex">Base vertex location</param>
|
||||
/// <param name="startInstance">Starting instance location</param>
|
||||
void DrawIndexedInstanced(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0);
|
||||
/// <param name="mesh">The mesh to be drawn. Must not be null.</param>
|
||||
/// <param name="material">The material to use for rendering the mesh. Must not be null.</param>
|
||||
public void DrawMesh(Mesh mesh, Material material);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches compute threads
|
||||
@@ -83,7 +74,7 @@ public interface ICommandBuffer : IDisposable
|
||||
/// <param name="threadGroupCountX">Thread groups in X dimension</param>
|
||||
/// <param name="threadGroupCountY">Thread groups in Y dimension</param>
|
||||
/// <param name="threadGroupCountZ">Thread groups in Z dimension</param>
|
||||
void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1);
|
||||
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,7 +120,7 @@ public struct RectDesc
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-style resource states
|
||||
/// Resource states
|
||||
/// </summary>
|
||||
public enum ResourceState
|
||||
{
|
||||
@@ -143,5 +134,5 @@ public enum ResourceState
|
||||
PixelShaderResource = 0x80,
|
||||
CopyDest = 0x400,
|
||||
CopySource = 0x800,
|
||||
Present = 0
|
||||
Present = 0,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ public interface ICommandQueue : IDisposable
|
||||
/// <summary>
|
||||
/// Type of commands this queue can execute
|
||||
/// </summary>
|
||||
CommandQueueType Type { get; }
|
||||
CommandQueueType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits a single command buffer for execution
|
||||
@@ -20,7 +23,7 @@ public interface ICommandQueue : IDisposable
|
||||
/// Submits multiple command buffers for execution
|
||||
/// </summary>
|
||||
/// <param name="commandBuffers">Command buffers to submit</param>
|
||||
void Submit(ICommandBuffer[] commandBuffers);
|
||||
void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers);
|
||||
|
||||
/// <summary>
|
||||
/// Signals a fence with the specified value
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-style descriptor allocator interface
|
||||
/// </summary>
|
||||
public interface IDescriptorAllocator : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Allocates a render target view descriptor
|
||||
/// </summary>
|
||||
/// <returns>RTV descriptor handle</returns>
|
||||
DescriptorHandle AllocateRTV();
|
||||
|
||||
/// <summary>
|
||||
/// Allocates multiple render target view descriptors
|
||||
/// </summary>
|
||||
/// <param name="count">Number of descriptors to allocate</param>
|
||||
/// <returns>Array of RTV descriptor handles</returns>
|
||||
DescriptorHandle[] AllocateRTVs(uint count);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a depth stencil view descriptor
|
||||
/// </summary>
|
||||
/// <returns>DSV descriptor handle</returns>
|
||||
DescriptorHandle AllocateDSV();
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a shader resource view descriptor
|
||||
/// </summary>
|
||||
/// <returns>SRV descriptor handle</returns>
|
||||
DescriptorHandle AllocateSRV();
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a sampler descriptor
|
||||
/// </summary>
|
||||
/// <returns>Sampler descriptor handle</returns>
|
||||
DescriptorHandle AllocateSampler();
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a bindless descriptor for SM 6.6 rendering
|
||||
/// </summary>
|
||||
/// <returns>Bindless descriptor handle</returns>
|
||||
DescriptorHandle AllocateBindless();
|
||||
|
||||
/// <summary>
|
||||
/// Releases a render target view descriptor
|
||||
/// </summary>
|
||||
/// <param name="handle">RTV descriptor to release</param>
|
||||
void ReleaseRTV(DescriptorHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a depth stencil view descriptor
|
||||
/// </summary>
|
||||
/// <param name="handle">DSV descriptor to release</param>
|
||||
void ReleaseDSV(DescriptorHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a shader resource view descriptor
|
||||
/// </summary>
|
||||
/// <param name="handle">SRV descriptor to release</param>
|
||||
void ReleaseSRV(DescriptorHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a sampler descriptor
|
||||
/// </summary>
|
||||
/// <param name="handle">Sampler descriptor to release</param>
|
||||
void ReleaseSampler(DescriptorHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a bindless descriptor
|
||||
/// </summary>
|
||||
/// <param name="handle">Bindless descriptor to release</param>
|
||||
void ReleaseBindless(DescriptorHandle handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-style descriptor heap interface
|
||||
/// </summary>
|
||||
public interface IDescriptorHeap : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of descriptors this heap contains
|
||||
/// </summary>
|
||||
DescriptorHeapType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of descriptors in this heap
|
||||
/// </summary>
|
||||
uint MaxDescriptors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this heap is shader visible
|
||||
/// </summary>
|
||||
bool IsShaderVisible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a CPU descriptor handle at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the descriptor</param>
|
||||
/// <returns>CPU descriptor handle</returns>
|
||||
DescriptorHandle GetCPUHandle(uint index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a GPU descriptor handle at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the descriptor</param>
|
||||
/// <returns>GPU descriptor handle</returns>
|
||||
DescriptorHandle GetGPUHandle(uint index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Descriptor handle for D3D12-style descriptor management
|
||||
/// </summary>
|
||||
public struct DescriptorHandle : IEquatable<DescriptorHandle>
|
||||
{
|
||||
public uint Index;
|
||||
public bool IsValid;
|
||||
|
||||
public DescriptorHandle(uint index)
|
||||
{
|
||||
Index = index;
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public static DescriptorHandle Invalid => new() { Index = uint.MaxValue, IsValid = false };
|
||||
|
||||
public bool Equals(DescriptorHandle other) => Index == other.Index && IsValid == other.IsValid;
|
||||
public override bool Equals(object? obj) => obj is DescriptorHandle other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(Index, IsValid);
|
||||
|
||||
public static bool operator ==(DescriptorHandle left, DescriptorHandle right) => left.Equals(right);
|
||||
public static bool operator !=(DescriptorHandle left, DescriptorHandle right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 descriptor heap types
|
||||
/// </summary>
|
||||
public enum DescriptorHeapType
|
||||
{
|
||||
CBV_SRV_UAV,
|
||||
Sampler,
|
||||
RTV,
|
||||
DSV
|
||||
}
|
||||
30
Ghost.Graphics/RHI/IGraphicsEngine.cs
Normal file
30
Ghost.Graphics/RHI/IGraphicsEngine.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IGraphicsEngine : IDisposable
|
||||
{
|
||||
public IRenderDevice Device
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public IResourceAllocator ResourceAllocator
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a command buffer for recording rendering commands
|
||||
/// </summary>
|
||||
/// <param name="type">Type of command buffer to create</param>
|
||||
/// <returns>A new command buffer instance</returns>
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a swap chain for presentation
|
||||
/// </summary>
|
||||
/// <param name="desc">Swap chain description</param>
|
||||
/// <returns>A new swap chain instance</returns>
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc);
|
||||
}
|
||||
25
Ghost.Graphics/RHI/IPipelineStateController.cs
Normal file
25
Ghost.Graphics/RHI/IPipelineStateController.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Ghost.Graphics.Data;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IShaderPipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Pipeline type
|
||||
/// </summary>
|
||||
PipelineType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPipelineStateController
|
||||
{
|
||||
public void ColectionShader(ReadOnlySpan<Shader> shaders);
|
||||
|
||||
public void CompileCollected();
|
||||
|
||||
public void PreCookPipelineState();
|
||||
|
||||
public IShaderPipeline GetShaderPipeline(Shader shader);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public interface IRenderDevice : IDisposable
|
||||
/// <summary>
|
||||
/// Graphics command queue for rendering operations
|
||||
/// </summary>
|
||||
ICommandQueue GraphicsQueue
|
||||
public ICommandQueue GraphicsQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public interface IRenderDevice : IDisposable
|
||||
/// <summary>
|
||||
/// Compute command queue for compute shader operations
|
||||
/// </summary>
|
||||
ICommandQueue ComputeQueue
|
||||
public ICommandQueue ComputeQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
@@ -24,32 +24,10 @@ public interface IRenderDevice : IDisposable
|
||||
/// <summary>
|
||||
/// Copy command queue for data transfer operations
|
||||
/// </summary>
|
||||
ICommandQueue CopyQueue
|
||||
public ICommandQueue CopyQueue
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the descriptor allocator for managing descriptors
|
||||
/// </summary>
|
||||
IDescriptorAllocator DescriptorAllocator
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a command buffer for recording rendering commands
|
||||
/// </summary>
|
||||
/// <param name="type">Type of command buffer to create</param>
|
||||
/// <returns>A new command buffer instance</returns>
|
||||
ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a swap chain for presentation
|
||||
/// </summary>
|
||||
/// <param name="desc">Swap chain description</param>
|
||||
/// <returns>A new swap chain instance</returns>
|
||||
ISwapChain CreateSwapChain(SwapChainDesc desc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline state object interface
|
||||
/// </summary>
|
||||
public interface IPipelineState : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Pipeline type (graphics or compute)
|
||||
/// </summary>
|
||||
PipelineType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline name for debugging
|
||||
/// </summary>
|
||||
string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Root signature interface
|
||||
/// </summary>
|
||||
@@ -54,48 +34,89 @@ public struct RenderTargetDesc
|
||||
/// <summary>
|
||||
/// Width of the render target
|
||||
/// </summary>
|
||||
public uint Width;
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of the render target
|
||||
/// </summary>
|
||||
public uint Height;
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of render target (color or depth)
|
||||
/// Slice of the render target
|
||||
/// </summary>
|
||||
public RenderTargetType Type;
|
||||
public uint Slice
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of render target
|
||||
/// </summary>
|
||||
public RenderTargetType Type
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Target texture format
|
||||
/// </summary>
|
||||
public TextureFormat Format;
|
||||
public TextureFormat Format
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture dimension
|
||||
/// </summary>
|
||||
public TextureDimension Dimension;
|
||||
public TextureDimension Dimension
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creation flags for the render target
|
||||
/// </summary>
|
||||
public RenderTargetCreationFlags CreationFlags;
|
||||
public RenderTargetCreationFlags CreationFlags
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of mip levels. 0 to generate full mip chain
|
||||
/// </summary>
|
||||
public uint MipLevels;
|
||||
public uint MipLevels
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of samples for MSAA
|
||||
/// </summary>
|
||||
public uint SampleCount;
|
||||
public uint SampleCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color render target
|
||||
/// </summary>
|
||||
public static RenderTargetDesc Color(uint width, uint height,
|
||||
TextureFormat format, TextureDimension dimension = TextureDimension.Texture2D,
|
||||
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
|
||||
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
|
||||
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable | RenderTargetCreationFlags.GenerateMips,
|
||||
uint mipLevels = 0u, uint sampleCount = 1)
|
||||
{
|
||||
@@ -103,6 +124,7 @@ public struct RenderTargetDesc
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Slice = slice,
|
||||
Type = RenderTargetType.Color,
|
||||
Format = format,
|
||||
Dimension = dimension,
|
||||
@@ -115,7 +137,7 @@ public struct RenderTargetDesc
|
||||
/// <summary>
|
||||
/// Creates a depth render target
|
||||
/// </summary>
|
||||
public static RenderTargetDesc Depth(uint width, uint height,
|
||||
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
|
||||
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
|
||||
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable,
|
||||
uint mipLevels = 0u, uint sampleCount = 1)
|
||||
@@ -124,6 +146,7 @@ public struct RenderTargetDesc
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Slice = slice,
|
||||
Type = RenderTargetType.Depth,
|
||||
Format = format,
|
||||
Dimension = dimension,
|
||||
@@ -132,6 +155,18 @@ public struct RenderTargetDesc
|
||||
SampleCount = sampleCount
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TextureDesc ToTextureDescriptor(RenderTargetDesc desc)
|
||||
{
|
||||
var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
|
||||
if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
|
||||
{
|
||||
usage |= TextureUsage.UnorderedAccess;
|
||||
}
|
||||
|
||||
return new TextureDesc(desc.Width, desc.Height, desc.Slice, desc.Format, desc.Dimension, desc.MipLevels, usage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -142,37 +177,73 @@ public struct TextureDesc
|
||||
/// <summary>
|
||||
/// Width of the texture
|
||||
/// </summary>
|
||||
public uint Width;
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of the texture
|
||||
/// </summary>
|
||||
public uint Height;
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slice of the texture
|
||||
/// </summary>
|
||||
public uint Slice
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture format
|
||||
/// </summary>
|
||||
public TextureFormat Format;
|
||||
public TextureFormat Format
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture dimension
|
||||
/// </summary>
|
||||
public TextureDimension Dimension;
|
||||
public TextureDimension Dimension
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of mip levels. 0 to generate full mip chain
|
||||
/// </summary>
|
||||
public uint MipLevels;
|
||||
public uint MipLevels
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture usage flags
|
||||
/// </summary>
|
||||
public TextureUsage Usage;
|
||||
public TextureUsage Usage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public TextureDesc(uint width, uint height, TextureFormat format, TextureDimension dimension = TextureDimension.Texture2D, uint mipLevels = 0u, TextureUsage usage = TextureUsage.ShaderResource)
|
||||
public TextureDesc(uint width, uint height, uint slice = 1,
|
||||
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
|
||||
uint mipLevels = 0u, TextureUsage usage = TextureUsage.ShaderResource)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Slice = slice;
|
||||
Format = format;
|
||||
Dimension = dimension;
|
||||
MipLevels = mipLevels;
|
||||
@@ -188,17 +259,29 @@ public struct BufferDesc
|
||||
/// <summary>
|
||||
/// Size of the buffer in bytes
|
||||
/// </summary>
|
||||
public ulong Size;
|
||||
public ulong Size
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffer usage flags
|
||||
/// </summary>
|
||||
public BufferUsage Usage;
|
||||
public BufferUsage Usage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory type for the buffer
|
||||
/// </summary>
|
||||
public MemoryType MemoryType;
|
||||
public MemoryType MemoryType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BufferDesc(ulong size, BufferUsage usage, MemoryType memoryType = MemoryType.Default)
|
||||
{
|
||||
|
||||
@@ -73,27 +73,12 @@ public interface IBuffer : IResource
|
||||
/// Render target interface for rendering operations
|
||||
/// Supports either color OR depth rendering, not both
|
||||
/// </summary>
|
||||
public interface IRenderTarget : IDisposable
|
||||
public interface IRenderTarget : ITexture
|
||||
{
|
||||
/// <summary>
|
||||
/// Width of the render target
|
||||
/// </summary>
|
||||
uint Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Height of the render target
|
||||
/// </summary>
|
||||
uint Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of render target (color or depth)
|
||||
/// </summary>
|
||||
RenderTargetType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target texture (either color or depth based on Type)
|
||||
/// </summary>
|
||||
ITexture Target { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,40 +2,58 @@
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IResourceFactory
|
||||
public interface IResourceAllocator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a render target for off-screen rendering
|
||||
/// </summary>
|
||||
/// <param name="desc">Render target description</param>
|
||||
/// <returns>A new render target instance</returns>
|
||||
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc);
|
||||
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Texture description</param>
|
||||
/// <returns>A new texture handle point to the resource</returns>
|
||||
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc);
|
||||
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Texture description</param>
|
||||
/// <returns>A new texture instance</returns>
|
||||
public ITexture CreateTexture(ref readonly TextureDesc desc);
|
||||
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Buffer description</param>
|
||||
/// <returns>A new buffer handle point to the resource</returns>
|
||||
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc);
|
||||
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer resource
|
||||
/// </summary>
|
||||
/// <param name="desc">Buffer description</param>
|
||||
/// <returns>A new buffer instance</returns>
|
||||
public IBuffer CreateBuffer(ref readonly BufferDesc desc);
|
||||
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
|
||||
|
||||
/// <summary>
|
||||
/// Release a resource given its handle
|
||||
/// </summary>
|
||||
/// <param name="handle">Resource handle</param>
|
||||
public void ReleaseResource(ResourceHandle handle);
|
||||
}
|
||||
|
||||
internal interface IResourceAllocator<T> : IResourceAllocator
|
||||
where T : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the raw gpu resource pointer from a resource handle
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the resource.</typeparam>
|
||||
/// <param name="handle">Resource handle</param>
|
||||
/// <returns>Pointer to the resource</returns>
|
||||
public unsafe T* GetResource(ResourceHandle handle);
|
||||
}
|
||||
@@ -26,13 +26,7 @@ public interface ISwapChain : IDisposable
|
||||
/// Gets the current back buffer texture
|
||||
/// </summary>
|
||||
/// <returns>Current back buffer texture</returns>
|
||||
ITexture GetCurrentBackBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current back buffer as a render target
|
||||
/// </summary>
|
||||
/// <returns>Current back buffer render target</returns>
|
||||
IRenderTarget GetCurrentBackBufferRenderTarget();
|
||||
IRenderTarget GetCurrentBackBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Presents the rendered frame
|
||||
@@ -56,35 +50,29 @@ public struct SwapChainDesc
|
||||
/// <summary>
|
||||
/// Width of the swap chain
|
||||
/// </summary>
|
||||
public uint Width;
|
||||
public uint width;
|
||||
|
||||
/// <summary>
|
||||
/// Height of the swap chain
|
||||
/// </summary>
|
||||
public uint Height;
|
||||
public uint height;
|
||||
|
||||
/// <summary>
|
||||
/// Back buffer format
|
||||
/// </summary>
|
||||
public TextureFormat Format;
|
||||
|
||||
/// <summary>
|
||||
/// Number of back buffers
|
||||
/// </summary>
|
||||
public uint BufferCount;
|
||||
public TextureFormat format;
|
||||
|
||||
/// <summary>
|
||||
/// Target for presentation (window handle or composition target)
|
||||
/// </summary>
|
||||
public SwapChainTarget Target;
|
||||
public SwapChainTarget target;
|
||||
|
||||
public SwapChainDesc(uint width, uint height, SwapChainTarget target, TextureFormat format = TextureFormat.B8G8R8A8_UNorm, uint bufferCount = 2)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Format = format;
|
||||
BufferCount = bufferCount;
|
||||
Target = target;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.format = format;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.Shading;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -113,7 +113,7 @@ float4 PSMain(PixelInput input) : SV_TARGET
|
||||
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
|
||||
];
|
||||
|
||||
public void Initialize(CommandList cmd)
|
||||
public void Initialize(ICommandBuffer cmd)
|
||||
{
|
||||
_mesh = MeshBuilder.CreateCube(0.75f);
|
||||
_mesh.UploadMeshData();
|
||||
@@ -132,14 +132,14 @@ float4 PSMain(PixelInput input) : SV_TARGET
|
||||
for (var i = 0; i < _textures.Length; i++)
|
||||
{
|
||||
var texture = _textures[i];
|
||||
_material.SetTextureIndex($"_TextureIndex{i + 1}", texture);
|
||||
_material.SetTexture($"_TextureIndex{i + 1}", texture);
|
||||
}
|
||||
|
||||
_material.SetMeshBufferIndices(_mesh);
|
||||
_material.SetMeshBuffer(_mesh);
|
||||
_material.UploadMaterialData();
|
||||
}
|
||||
|
||||
public void Execute(CommandList cmd)
|
||||
public void Execute(ICommandBuffer cmd)
|
||||
{
|
||||
cmd.DrawMesh(_mesh!, _material!);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
using System.Collections.Immutable;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public enum GraphicsAPI
|
||||
{
|
||||
Direct3D12
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application-level render system that orchestrates multiple renderers
|
||||
/// and handles frame synchronization
|
||||
/// </summary>
|
||||
public class RenderSystem : IDisposable
|
||||
internal class RenderSystem : IDisposable
|
||||
{
|
||||
private readonly struct FrameResource : IDisposable
|
||||
{
|
||||
public readonly AutoResetEvent CpuReadyEvent;
|
||||
public readonly AutoResetEvent GpuReadyEvent;
|
||||
public readonly AutoResetEvent cpuReadyEvent;
|
||||
public readonly AutoResetEvent gpuReadyEvent;
|
||||
|
||||
public FrameResource()
|
||||
{
|
||||
CpuReadyEvent = new(false);
|
||||
GpuReadyEvent = new(true);
|
||||
cpuReadyEvent = new(false);
|
||||
gpuReadyEvent = new(true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CpuReadyEvent?.Dispose();
|
||||
GpuReadyEvent?.Dispose();
|
||||
cpuReadyEvent?.Dispose();
|
||||
gpuReadyEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private const uint FRAME_COUNT = 2;
|
||||
private const uint _FRAME_COUNT = 2;
|
||||
|
||||
private readonly IRenderDevice _device;
|
||||
private readonly IGraphicsEngine _engine;
|
||||
private readonly FrameResource[] _frameResources;
|
||||
private readonly Thread _renderThread;
|
||||
private readonly AutoResetEvent _shutdownEvent;
|
||||
@@ -41,19 +46,25 @@ public class RenderSystem : IDisposable
|
||||
private bool _isRunning;
|
||||
private bool _disposed;
|
||||
|
||||
public IGraphicsEngine GraphicsEngine => _engine;
|
||||
public uint CPUFenceValue => _cpuFenceValue;
|
||||
public uint GPUFenceValue => _gpuFenceValue;
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public RenderSystem(IRenderDevice device)
|
||||
public RenderSystem(GraphicsAPI api)
|
||||
{
|
||||
_device = device;
|
||||
_engine = api switch
|
||||
{
|
||||
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
|
||||
_ => throw new NotSupportedException($"Graphics API {api} is not supported.")
|
||||
};
|
||||
|
||||
_renderers = new();
|
||||
_shutdownEvent = new(false);
|
||||
|
||||
// Create frame resources for synchronization
|
||||
_frameResources = new FrameResource[FRAME_COUNT];
|
||||
for (var i = 0; i < FRAME_COUNT; i++)
|
||||
_frameResources = new FrameResource[_FRAME_COUNT];
|
||||
for (var i = 0; i < _FRAME_COUNT; i++)
|
||||
{
|
||||
_frameResources[i] = new();
|
||||
}
|
||||
@@ -71,9 +82,11 @@ public class RenderSystem : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void AddRenderer(IRenderer renderer)
|
||||
public IRenderer CreateRenderer()
|
||||
{
|
||||
var renderer = _engine.CreateRenderer();
|
||||
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public void RemoveRenderer(IRenderer renderer)
|
||||
@@ -84,7 +97,9 @@ public class RenderSystem : IDisposable
|
||||
public void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
_renderThread.Start();
|
||||
@@ -93,7 +108,9 @@ public class RenderSystem : IDisposable
|
||||
public void Stop()
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_shutdownEvent.Set();
|
||||
@@ -106,14 +123,14 @@ public class RenderSystem : IDisposable
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
var eventIndex = (int)(_cpuFenceValue % FRAME_COUNT);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
|
||||
return _frameResources[eventIndex].gpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
public void SignalCPUReady()
|
||||
{
|
||||
var eventIndex = (int)(_cpuFenceValue % FRAME_COUNT);
|
||||
_frameResources[eventIndex].CpuReadyEvent.Set();
|
||||
var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
|
||||
_frameResources[eventIndex].cpuReadyEvent.Set();
|
||||
_cpuFenceValue++;
|
||||
}
|
||||
|
||||
@@ -123,11 +140,11 @@ public class RenderSystem : IDisposable
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
_frameIndex = _gpuFenceValue % FRAME_COUNT;
|
||||
_frameIndex = _gpuFenceValue % _FRAME_COUNT;
|
||||
var frameResource = _frameResources[_frameIndex];
|
||||
|
||||
// Wait for either CPU ready signal or shutdown signal
|
||||
waitHandles[0] = frameResource.CpuReadyEvent;
|
||||
waitHandles[0] = frameResource.cpuReadyEvent;
|
||||
var waitResult = WaitHandle.WaitAny(waitHandles);
|
||||
|
||||
// If shutdown was signaled or timeout occurred, exit the loop
|
||||
@@ -146,7 +163,7 @@ public class RenderSystem : IDisposable
|
||||
}
|
||||
|
||||
_gpuFenceValue++;
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
frameResource.gpuReadyEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +171,9 @@ public class RenderSystem : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D.Dxc;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Shading;
|
||||
|
||||
internal readonly struct TextureInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RegisterSlot
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct PropertyInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint CBufferIndex
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint ByteOffset
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct CBufferInfo
|
||||
{
|
||||
public required string Name
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public uint RegisterSlot
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bindless shader implementation using SM 6.6 with ResourceDescriptorHeap
|
||||
/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
|
||||
/// Enhanced to support both bindless and regular texture binding for hybrid materials
|
||||
/// </summary>
|
||||
public unsafe class Shader : IDisposable
|
||||
{
|
||||
private ComPtr<ID3D12PipelineState> _pipelineState;
|
||||
private ComPtr<ID3D12RootSignature> _rootSignature;
|
||||
private ComPtr<ID3D12DescriptorHeap> _samplerHeap;
|
||||
|
||||
private readonly byte[] _vertexShaderBytecode;
|
||||
private readonly byte[] _pixelShaderBytecode;
|
||||
|
||||
private readonly List<CBufferInfo> _constantBuffers = new();
|
||||
private readonly List<PropertyInfo> _properties = new();
|
||||
private readonly Dictionary<string, int> _propertyNameToIdMap = new();
|
||||
private readonly List<TextureInfo> _regularTextures = new(); // Add regular texture support
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal ConstPtr<ID3D12PipelineState> PipelineState => new(_pipelineState.Get());
|
||||
internal ConstPtr<ID3D12RootSignature> RootSignature => new(_rootSignature.Get());
|
||||
internal ConstPtr<ID3D12DescriptorHeap> SamplerHeap => new(_samplerHeap.Get());
|
||||
|
||||
internal IReadOnlyList<CBufferInfo> ConstantBuffers => _constantBuffers;
|
||||
internal IReadOnlyList<PropertyInfo> Properties => _properties;
|
||||
internal IReadOnlyList<TextureInfo> RegularTextures => _regularTextures; // Expose regular textures
|
||||
|
||||
public Shader(string shaderCode)
|
||||
{
|
||||
var (vsBytecode, vsReflection) = CompileShaderDXC(shaderCode, "VSMain", "vs_6_6");
|
||||
var (psBytecode, psReflection) = CompileShaderDXC(shaderCode, "PSMain", "ps_6_6");
|
||||
|
||||
_vertexShaderBytecode = vsBytecode;
|
||||
_pixelShaderBytecode = psBytecode;
|
||||
|
||||
PerformDXCReflection(vsReflection);
|
||||
PerformDXCReflection(psReflection);
|
||||
|
||||
CreateBindlessRootSignature();
|
||||
CreatePipelineState();
|
||||
CreateSamplerHeap();
|
||||
}
|
||||
|
||||
private (byte[] bytecode, ComPtr<IDxcBlob> reflection) CompileShaderDXC(string source, string entryPoint, string profile)
|
||||
{
|
||||
using ComPtr<IDxcCompiler3> compiler = default;
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
|
||||
// Create DXC compiler and utils
|
||||
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
|
||||
// Create source blob
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(source);
|
||||
fixed (byte* sourceBytesPtr = sourceBytes)
|
||||
{
|
||||
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
|
||||
}
|
||||
|
||||
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
|
||||
var argsArray = new string[]
|
||||
{
|
||||
"-T", profile, // Target profile (vs_6_6, ps_6_6)
|
||||
"-E", entryPoint, // Entry point
|
||||
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
|
||||
"-enable-16bit-types", // Enable 16-bit types
|
||||
"-O3", // Optimization level
|
||||
"-Qstrip_debug" // Strip debug info but KEEP reflection
|
||||
};
|
||||
|
||||
// Convert to wide strings (DXC expects LPCWSTR)
|
||||
var wideArgs = new nuint[argsArray.Length];
|
||||
var argPointers = new IntPtr[argsArray.Length];
|
||||
|
||||
for (var i = 0; i < argsArray.Length; i++)
|
||||
{
|
||||
argPointers[i] = Marshal.StringToHGlobalUni(argsArray[i]);
|
||||
wideArgs[i] = (nuint)argPointers[i];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Compile shader
|
||||
using ComPtr<IDxcResult> result = default;
|
||||
fixed (nuint* argsPtr = wideArgs)
|
||||
{
|
||||
var buffer = new DxcBuffer
|
||||
{
|
||||
Ptr = sourceBlob.Get()->GetBufferPointer(),
|
||||
Size = sourceBlob.Get()->GetBufferSize(),
|
||||
Encoding = DXC_CP_UTF8
|
||||
};
|
||||
|
||||
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf());
|
||||
}
|
||||
|
||||
// Check compilation result
|
||||
HResult hrStatus;
|
||||
result.Get()->GetStatus(&hrStatus);
|
||||
if (hrStatus.Failure)
|
||||
{
|
||||
// Get error messages
|
||||
using ComPtr<IDxcBlobEncoding> errorBlob = default;
|
||||
result.Get()->GetErrorBuffer(errorBlob.GetAddressOf());
|
||||
|
||||
if (errorBlob.Get() != null)
|
||||
{
|
||||
var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer());
|
||||
throw new Exception($"DXC shader compilation failed: {errorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("DXC shader compilation failed with unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
// Get compiled bytecode
|
||||
using ComPtr<IDxcBlob> bytecodeBlob = default;
|
||||
result.Get()->GetResult(bytecodeBlob.GetAddressOf());
|
||||
|
||||
if (bytecodeBlob.Get() == null)
|
||||
{
|
||||
throw new Exception("DXC compilation succeeded but no bytecode was produced");
|
||||
}
|
||||
|
||||
// Get reflection data using DXC API
|
||||
using ComPtr<IDxcBlob> reflectionBlob = default;
|
||||
result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null);
|
||||
|
||||
if (reflectionBlob.Get() == null)
|
||||
{
|
||||
throw new Exception("DXC compilation succeeded but no reflection data was produced");
|
||||
}
|
||||
|
||||
// Copy bytecode to managed array
|
||||
var bytecodeSize = (int)bytecodeBlob.Get()->GetBufferSize();
|
||||
var bytecode = new byte[bytecodeSize];
|
||||
|
||||
fixed (byte* bytecodePtr = bytecode)
|
||||
{
|
||||
Buffer.MemoryCopy(bytecodeBlob.Get()->GetBufferPointer(), bytecodePtr, bytecodeSize, bytecodeSize);
|
||||
}
|
||||
|
||||
// Return both bytecode and reflection blob (move ownership)
|
||||
return (bytecode, reflectionBlob.Move());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Free allocated wide strings
|
||||
for (var i = 0; i < argPointers.Length; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(argPointers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateBindlessRootSignature()
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
// Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table
|
||||
var totalRootParams = _constantBuffers.Count + (_regularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler
|
||||
var rootParameters = new RootParameter1[totalRootParams];
|
||||
|
||||
var parameterIndex = 0;
|
||||
|
||||
// Add CBV root parameters
|
||||
foreach (var cbufferInfo in _constantBuffers)
|
||||
{
|
||||
rootParameters[parameterIndex++] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.Cbv,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Add regular texture descriptor table if we have regular textures
|
||||
if (_regularTextures.Count > 0)
|
||||
{
|
||||
var textureRanges = new DescriptorRange1[1];
|
||||
textureRanges[0] = new DescriptorRange1
|
||||
{
|
||||
RangeType = DescriptorRangeType.Srv,
|
||||
NumDescriptors = (uint)_regularTextures.Count,
|
||||
BaseShaderRegister = 0, // Start from t0
|
||||
RegisterSpace = 0,
|
||||
Flags = DescriptorRangeFlags.None,
|
||||
OffsetInDescriptorsFromTableStart = 0
|
||||
};
|
||||
|
||||
fixed (DescriptorRange1* textureRangesPtr = textureRanges)
|
||||
{
|
||||
rootParameters[parameterIndex++] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.DescriptorTable,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Sampler descriptor table (still needed for samplers)
|
||||
var samplerRanges = new DescriptorRange1[1];
|
||||
samplerRanges[0] = new DescriptorRange1
|
||||
{
|
||||
RangeType = DescriptorRangeType.Sampler,
|
||||
NumDescriptors = 1,
|
||||
BaseShaderRegister = 0, // s0
|
||||
RegisterSpace = 0,
|
||||
Flags = DescriptorRangeFlags.None,
|
||||
OffsetInDescriptorsFromTableStart = 0
|
||||
};
|
||||
|
||||
fixed (DescriptorRange1* samplerRangesPtr = samplerRanges)
|
||||
{
|
||||
rootParameters[parameterIndex] = new RootParameter1
|
||||
{
|
||||
ParameterType = RootParameterType.DescriptorTable,
|
||||
ShaderVisibility = ShaderVisibility.All,
|
||||
DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr)
|
||||
};
|
||||
}
|
||||
|
||||
// Create root signature with the modern flag
|
||||
fixed (RootParameter1* rootParamsPtr = rootParameters)
|
||||
{
|
||||
var rootSignatureDesc = new RootSignatureDescription1
|
||||
{
|
||||
NumParameters = (uint)rootParameters.Length,
|
||||
pParameters = rootParamsPtr,
|
||||
NumStaticSamplers = 0,
|
||||
pStaticSamplers = null,
|
||||
// Key difference: Use the modern flag for direct heap indexing
|
||||
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
|
||||
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
|
||||
};
|
||||
|
||||
var versionedDesc = new VersionedRootSignatureDescription
|
||||
{
|
||||
Version = RootSignatureVersion.V1_1,
|
||||
Desc_1_1 = rootSignatureDesc
|
||||
};
|
||||
|
||||
using ComPtr<ID3DBlob> signature = default;
|
||||
using ComPtr<ID3DBlob> error = default;
|
||||
|
||||
D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
|
||||
|
||||
device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), _rootSignature.GetVoidAddressOf());
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePipelineState()
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
fixed (byte* vsPtr = _vertexShaderBytecode)
|
||||
fixed (byte* psPtr = _pixelShaderBytecode)
|
||||
{
|
||||
var psoDesc = new GraphicsPipelineStateDescription
|
||||
{
|
||||
pRootSignature = _rootSignature.Get(),
|
||||
VS = new ShaderBytecode(vsPtr, (nuint)_vertexShaderBytecode.Length),
|
||||
PS = new ShaderBytecode(psPtr, (nuint)_pixelShaderBytecode.Length),
|
||||
InputLayout = D3D12PipelineResource.InputLayoutDescription,
|
||||
RasterizerState = RasterizerDescription.CullNone,
|
||||
BlendState = BlendDescription.Opaque,
|
||||
DepthStencilState = DepthStencilDescription.Default,
|
||||
SampleMask = uint.MaxValue,
|
||||
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
|
||||
NumRenderTargets = 1,
|
||||
SampleDesc = new SampleDescription(1, 0),
|
||||
DSVFormat = Format.Unknown,
|
||||
};
|
||||
|
||||
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
|
||||
|
||||
device->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), _pipelineState.GetVoidAddressOf());
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSamplerHeap()
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
// Create sampler heap
|
||||
var samplerHeapDesc = new DescriptorHeapDescription
|
||||
{
|
||||
Type = DescriptorHeapType.Sampler,
|
||||
NumDescriptors = 1,
|
||||
Flags = DescriptorHeapFlags.ShaderVisible
|
||||
};
|
||||
|
||||
device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof<ID3D12DescriptorHeap>(), _samplerHeap.GetVoidAddressOf());
|
||||
|
||||
// Create default sampler
|
||||
var samplerDesc = new SamplerDescription
|
||||
{
|
||||
Filter = Filter.MinMagMipLinear,
|
||||
AddressU = TextureAddressMode.Wrap,
|
||||
AddressV = TextureAddressMode.Wrap,
|
||||
AddressW = TextureAddressMode.Wrap,
|
||||
MipLODBias = 0,
|
||||
MaxAnisotropy = 1,
|
||||
MinLOD = 0,
|
||||
MaxLOD = float.MaxValue
|
||||
};
|
||||
|
||||
// Set border color manually
|
||||
samplerDesc.BorderColor[0] = 0;
|
||||
samplerDesc.BorderColor[1] = 0;
|
||||
samplerDesc.BorderColor[2] = 0;
|
||||
samplerDesc.BorderColor[3] = 0;
|
||||
|
||||
var samplerHandle = _samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart();
|
||||
device->CreateSampler(&samplerDesc, samplerHandle);
|
||||
}
|
||||
|
||||
private unsafe void PerformDXCReflection(ComPtr<IDxcBlob> reflectionBlob)
|
||||
{
|
||||
// Create DXC utils to parse reflection data
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
|
||||
// Create reflection interface from blob
|
||||
var reflectionData = new DxcBuffer
|
||||
{
|
||||
Ptr = reflectionBlob.Get()->GetBufferPointer(),
|
||||
Size = reflectionBlob.Get()->GetBufferSize(),
|
||||
Encoding = DXC_CP_ACP
|
||||
};
|
||||
|
||||
using ComPtr<ID3D12ShaderReflection> reflection = default;
|
||||
utils.Get()->CreateReflection(&reflectionData, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf());
|
||||
|
||||
if (reflection.Get() == null)
|
||||
{
|
||||
throw new Exception("Failed to create shader reflection from DXC output");
|
||||
}
|
||||
|
||||
ShaderDescription shaderDesc;
|
||||
reflection.Get()->GetDesc(&shaderDesc);
|
||||
|
||||
var cbufferRegistry = _constantBuffers.ToDictionary(cb => cb.Name);
|
||||
var textureRegistry = _regularTextures.ToDictionary(t => t.Name);
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
ShaderInputBindDescription bindDesc;
|
||||
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
|
||||
|
||||
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
|
||||
{
|
||||
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
|
||||
ShaderBufferDescription cbufferDesc;
|
||||
cbuffer->GetDesc(&cbufferDesc);
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = cbufferName,
|
||||
Size = cbufferDesc.Size,
|
||||
RegisterSlot = bindDesc.BindPoint
|
||||
};
|
||||
cbufferRegistry.Add(cbufferName, cbufferInfo);
|
||||
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
ShaderVariableDescription varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
|
||||
if (variableName == null || _propertyNameToIdMap.ContainsKey(variableName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propInfo = new PropertyInfo
|
||||
{
|
||||
Name = variableName,
|
||||
CBufferIndex = cbufferInfo.RegisterSlot,
|
||||
ByteOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
};
|
||||
|
||||
// Add to the list and create the name-to-ID mapping
|
||||
var newId = _properties.Count;
|
||||
_properties.Add(propInfo);
|
||||
_propertyNameToIdMap.Add(variableName, newId);
|
||||
}
|
||||
}
|
||||
else if (bindDesc.Type == ShaderInputType.Texture)
|
||||
{
|
||||
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (textureName == null || textureRegistry.ContainsKey(textureName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ALL texture input slots are regular textures!
|
||||
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
|
||||
var textureInfo = new TextureInfo
|
||||
{
|
||||
Name = textureName,
|
||||
RegisterSlot = bindDesc.BindPoint,
|
||||
RootParameterIndex = (uint)_constantBuffers.Count // Descriptor table comes after CBVs
|
||||
};
|
||||
|
||||
textureRegistry.Add(textureName, textureInfo);
|
||||
}
|
||||
}
|
||||
|
||||
_constantBuffers.Clear();
|
||||
_constantBuffers.AddRange(cbufferRegistry.Values);
|
||||
|
||||
_regularTextures.Clear();
|
||||
_regularTextures.AddRange(textureRegistry.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique, stable ID for a shader property.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property (e.g., "_Color").</param>
|
||||
/// <returns>The integer ID of the property, or -1 if not found.</returns>
|
||||
public int GetPropertyId(string propertyName)
|
||||
{
|
||||
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pipelineState.Dispose();
|
||||
_rootSignature.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
|
||||
_constantBuffers.Clear();
|
||||
_properties.Clear();
|
||||
_propertyNameToIdMap.Clear();
|
||||
_regularTextures.Clear();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
@@ -10,8 +11,9 @@ namespace Ghost.UnitTest.Windows;
|
||||
|
||||
public sealed partial class GraphicsTestWindow : Window
|
||||
{
|
||||
private Renderer? _renderer;
|
||||
private ISwapChainPanelNative _swapChainPanelNative;
|
||||
private RenderSystem? _renderSystem;
|
||||
private IRenderer? _renderer;
|
||||
private ISwapChain? _swapChain;
|
||||
|
||||
public GraphicsTestWindow()
|
||||
{
|
||||
@@ -28,14 +30,12 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
#if DEBUG
|
||||
AllocationManager.EnableDebugLayer();
|
||||
#endif
|
||||
GraphicsPipeline.Initialize();
|
||||
GraphicsPipeline.Start();
|
||||
|
||||
var guid = typeof(ISwapChainPanelNative.Interface).GUID;
|
||||
((IWinRTObject)Panel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle);
|
||||
_swapChainPanelNative = new ISwapChainPanelNative(swapChainPanelNativeHandle);
|
||||
_renderSystem = new (GraphicsAPI.Direct3D12);
|
||||
_renderer = _renderSystem.CreateRenderer();
|
||||
|
||||
//_renderer = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height));
|
||||
_swapChain = _renderSystem.GraphicsEngine.Device.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel)));
|
||||
_renderer.SetSwapChain(_swapChain);
|
||||
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
}
|
||||
@@ -44,11 +44,9 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
{
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
|
||||
GraphicsPipeline.SignalCPUReady();
|
||||
GraphicsPipeline.Shutdown();
|
||||
|
||||
_swapChainPanelNative.Dispose();
|
||||
_swapChain?.Dispose();
|
||||
_renderer?.Dispose();
|
||||
_renderSystem?.Dispose();
|
||||
}
|
||||
|
||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user