diff --git a/Ghost.Core/Ghost.Core.csproj b/Ghost.Core/Ghost.Core.csproj
index 65d5c8c..489a212 100644
--- a/Ghost.Core/Ghost.Core.csproj
+++ b/Ghost.Core/Ghost.Core.csproj
@@ -9,7 +9,7 @@
- ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll
+ ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll
diff --git a/Ghost.Editor/Ghost.Editor.csproj b/Ghost.Editor/Ghost.Editor.csproj
index a28fcaf..a825bb6 100644
--- a/Ghost.Editor/Ghost.Editor.csproj
+++ b/Ghost.Editor/Ghost.Editor.csproj
@@ -102,7 +102,7 @@
- ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll
+ ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll
diff --git a/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs b/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs
index a3d3fb7..e8cb3fb 100644
--- a/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs
+++ b/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs
@@ -1,6 +1,7 @@
using Ghost.Editor.Controls.Internal;
using Ghost.Graphics;
using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinRT;
@@ -9,7 +10,7 @@ namespace Ghost.Editor.View.Pages.EngineEditor;
internal sealed partial class ScenePage : NavigationTabPage
{
- private IRenderer? _renderView;
+ private Renderer? _renderView;
private ISwapChainPanelNative _swapChainPanelNative;
public ScenePage()
diff --git a/Ghost.Engine/EngineCore.cs b/Ghost.Engine/EngineCore.cs
index 9bbaf75..fadca99 100644
--- a/Ghost.Engine/EngineCore.cs
+++ b/Ghost.Engine/EngineCore.cs
@@ -1,7 +1,6 @@
using Ghost.Engine.Models;
using Ghost.Engine.Services;
using Ghost.Graphics;
-using Ghost.Graphics.Data;
namespace Ghost.Engine;
@@ -11,7 +10,7 @@ internal class EngineCore
{
ActivationHandler.Handle(args);
- GraphicsPipeline.Initialize(GraphicsAPI.D3D12);
+ GraphicsPipeline.Initialize();
GraphicsPipeline.Start();
Logger.LogInfo("Engine started successfully.");
diff --git a/Ghost.Engine/Services/Logger.cs b/Ghost.Engine/Services/Logger.cs
index 61a3c83..bc93783 100644
--- a/Ghost.Engine/Services/Logger.cs
+++ b/Ghost.Engine/Services/Logger.cs
@@ -1,5 +1,6 @@
using Ghost.Engine.Models;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
namespace Ghost.Engine.Services;
@@ -89,7 +90,7 @@ public static class Logger
LogExceptionInternal(ex);
}
- public static void Assert(bool condition, object? message = null)
+ public static void Assert([DoesNotReturnIf(false)] bool condition, object? message = null)
{
if (!condition)
{
diff --git a/Ghost.Graphics/Contracts/ICommandBuffer.cs b/Ghost.Graphics/Contracts/ICommandBuffer.cs
deleted file mode 100644
index 6757fed..0000000
--- a/Ghost.Graphics/Contracts/ICommandBuffer.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Ghost.Graphics.Data;
-using Win32.Graphics.Direct3D12;
-
-namespace Ghost.Graphics.Contracts;
-
-public interface ICommandBuffer
-{
- // TODO: They should be internal, maybe an interface ICommandBufferInternal?
- public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState);
- public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress);
-
- public void DrawMesh(Mesh mesh, Material material);
- public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size);
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IDebugLayer.cs b/Ghost.Graphics/Contracts/IDebugLayer.cs
deleted file mode 100644
index d8ddafb..0000000
--- a/Ghost.Graphics/Contracts/IDebugLayer.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Ghost.Graphics.Contracts;
-
-internal interface IDebugLayer : IDisposable
-{
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IGraphicsDevice.cs b/Ghost.Graphics/Contracts/IGraphicsDevice.cs
deleted file mode 100644
index 55fa65f..0000000
--- a/Ghost.Graphics/Contracts/IGraphicsDevice.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Ghost.Graphics.Data;
-
-namespace Ghost.Graphics.Contracts;
-
-internal interface IGraphicsDevice : IDisposable
-{
- public static abstract GraphicsAPI TargetAPI
- {
- get;
- }
-
- public ReadOnlySpan Renderers
- {
- get;
- }
-
- public IRenderer CreateRenderer(in SwapChainPresenter swapChainSurface);
- public void RemoveRenderer(IRenderer renderer);
- public void InitializePendingRenderers();
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IPipelineResource.cs b/Ghost.Graphics/Contracts/IPipelineResource.cs
deleted file mode 100644
index 9068741..0000000
--- a/Ghost.Graphics/Contracts/IPipelineResource.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Ghost.Graphics.Contracts;
-
-internal interface IPipelineResource : IDisposable
-{
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IRenderPass.cs b/Ghost.Graphics/Contracts/IRenderPass.cs
index 184e21e..d3b4fd5 100644
--- a/Ghost.Graphics/Contracts/IRenderPass.cs
+++ b/Ghost.Graphics/Contracts/IRenderPass.cs
@@ -1,7 +1,9 @@
-namespace Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
+
+namespace Ghost.Graphics.Contracts;
internal interface IRenderPass : IDisposable
{
- void Initialize(ICommandBuffer cmb);
- void Execute(ICommandBuffer cmb);
+ void Initialize(CommandList cmd);
+ void Execute(CommandList cmd);
}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IRenderer.cs b/Ghost.Graphics/Contracts/IRenderer.cs
deleted file mode 100644
index 52a4dd7..0000000
--- a/Ghost.Graphics/Contracts/IRenderer.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-namespace Ghost.Graphics.Contracts;
-
-///
-/// Defines the contract for a render view in the graphics pipeline.
-///
-internal interface IRenderer : IDisposable
-{
- public ReadOnlySpan RenderPasses
- {
- get;
- }
-
- ///
- /// Requests a resize of the render view.
- ///
- /// The new width of the render view.
- /// The new height of the render view.
- /// This only submits a resize request without executing it. May overwrite last request if next request issued before next frame.
- public void RequestResize(uint width, uint height);
- ///
- /// Executes any pending resize operations for the current context.
- ///
- public void ExecutePendingResize();
-
- public void Initialize();
- ///
- /// Renders the current content to the output target.
- ///
- public void Render();
-
- ///
- /// Waits for the next frame to be ready for rendering.
- ///
- public void WaitNextFrame();
- ///
- /// Waits for the render view to become idle, ensuring all previous commands have been executed and resources are ready for the next frame.
- ///
- public void WaitIdle();
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IResource.cs b/Ghost.Graphics/Contracts/IResource.cs
deleted file mode 100644
index c062e34..0000000
--- a/Ghost.Graphics/Contracts/IResource.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Misaki.HighPerformance.LowLevel.Collections;
-
-namespace Ghost.Graphics.Contracts;
-
-public unsafe interface IResource : IDisposable
-{
- public ulong GPUAddress
- {
- get;
- }
-
- public string Name
- {
- get;
- set;
- }
-
- public bool TempResource
- {
- get;
- }
-
- public void SetData(Span data)
- where T : unmanaged;
-
- public void SetData(T* data, uint length)
- where T : unmanaged;
-
- public void SetData(void* data, uint size);
-
- public UnsafeArray ReadData(Allocator allocator)
- where T : unmanaged;
-
- public void ReadData(T* ppData, uint* size)
- where T : unmanaged;
-
- public void ReadData(void* ppData, uint* size);
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Contracts/IResourceAllocator.cs b/Ghost.Graphics/Contracts/IResourceAllocator.cs
deleted file mode 100644
index 5df866c..0000000
--- a/Ghost.Graphics/Contracts/IResourceAllocator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Win32.Graphics.Direct3D12;
-
-namespace Ghost.Graphics.Contracts;
-
-internal unsafe interface IResourceAllocator : IDisposable
-{
- public IResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None);
- public IResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None);
-
- public void ReleaseTempResource();
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/CommandList.cs b/Ghost.Graphics/D3D12/CommandList.cs
new file mode 100644
index 0000000..3816838
--- /dev/null
+++ b/Ghost.Graphics/D3D12/CommandList.cs
@@ -0,0 +1,59 @@
+using Ghost.Core;
+using Ghost.Graphics.Data;
+using Win32.Graphics.Direct3D;
+using Win32.Graphics.Direct3D12;
+
+namespace Ghost.Graphics.D3D12;
+
+public unsafe class CommandList
+{
+ private readonly ConstPtr _commandList;
+
+ internal ConstPtr NativeCommandList => _commandList;
+
+ public CommandList(ID3D12GraphicsCommandList10* commandList)
+ {
+ _commandList = commandList;
+ }
+
+ public void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
+ {
+ _commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState);
+ }
+
+ public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
+ {
+ _commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
+ }
+
+ public void DrawMesh(Mesh mesh, Material material)
+ {
+ _commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature);
+ _commandList.Ptr->SetPipelineState(material.Shader.PipelineState);
+
+ // Bind shader-visible descriptor heaps before setting descriptor tables
+ if (material.Shader.Textures.Count > 0)
+ {
+ var shaderVisibleHeaps = GraphicsPipeline.DescriptorAllocator.GetShaderVisibleHeaps();
+ var heapPtrs = stackalloc ID3D12DescriptorHeap*[shaderVisibleHeaps.Length];
+ for (var i = 0; i < shaderVisibleHeaps.Length; i++)
+ {
+ heapPtrs[i] = shaderVisibleHeaps[i].Ptr;
+ }
+ _commandList.Ptr->SetDescriptorHeaps((uint)shaderVisibleHeaps.Length, heapPtrs);
+ }
+
+ material.Bind(this);
+
+ _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
+ _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
+ _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
+
+ _commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0);
+ }
+
+ public void CopyResource(GraphicsResource dstResource, uint dstOffset, GraphicsResource srcResource, uint srcOffset, uint size)
+ {
+ _commandList.Ptr->CopyBufferRegion(dstResource.NativeResource, dstOffset, srcResource.NativeResource, srcOffset, size);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
deleted file mode 100644
index 4ee6a9c..0000000
--- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Ghost.Core;
-using Ghost.Graphics.Contracts;
-using Ghost.Graphics.Data;
-using Win32.Graphics.Direct3D;
-using Win32.Graphics.Direct3D12;
-
-namespace Ghost.Graphics.D3D12;
-
-internal unsafe class D3D12CommandBuffer : ICommandBuffer
-{
- private readonly ConstPtr _commandList;
-
- internal ConstPtr CommandList => _commandList;
-
- public D3D12CommandBuffer(ID3D12GraphicsCommandList10* commandList)
- {
- _commandList = commandList;
- }
-
- public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState)
- {
- var dxResource = (D3D12Resource)resource;
- _commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState);
- }
-
- public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
- {
- _commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
- }
-
- public void DrawMesh(Mesh mesh, Material material)
- {
- _commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature);
- _commandList.Ptr->SetPipelineState(material.Shader.PipelineState);
-
- material.UploadAndBind(this);
-
- _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
- _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
- _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
-
- _commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0);
- }
-
- public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size)
- {
- var dstDXResource = (D3D12Resource)dstResource;
- var srcDXResource = (D3D12Resource)srcResource;
-
- _commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size);
- }
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs b/Ghost.Graphics/D3D12/DebugLayer.cs
similarity index 89%
rename from Ghost.Graphics/D3D12/D3D12DebugLayer.cs
rename to Ghost.Graphics/D3D12/DebugLayer.cs
index 35c145e..182854a 100644
--- a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs
+++ b/Ghost.Graphics/D3D12/DebugLayer.cs
@@ -1,17 +1,16 @@
-using Ghost.Graphics.Contracts;
-using Win32;
+using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12;
-internal unsafe class D3D12DebugLayer : IDebugLayer
+internal unsafe class DebugLayer
{
private readonly ComPtr _d3d12Debug;
private readonly ComPtr _dxgiDebug;
private readonly ComPtr _dxgiInfoQueue;
- public D3D12DebugLayer()
+ public DebugLayer()
{
D3D12GetDebugInterface(__uuidof(), _d3d12Debug.GetVoidAddressOf());
_d3d12Debug.Get()->EnableDebugLayer();
diff --git a/Ghost.Graphics/D3D12/DescriptorAllocator.cs b/Ghost.Graphics/D3D12/DescriptorAllocator.cs
new file mode 100644
index 0000000..5ee7396
--- /dev/null
+++ b/Ghost.Graphics/D3D12/DescriptorAllocator.cs
@@ -0,0 +1,330 @@
+using Ghost.Core;
+using Ghost.Graphics.D3D12.Utilities;
+using Win32.Graphics.Direct3D12;
+
+namespace Ghost.Graphics.D3D12;
+
+///
+/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
+///
+internal class DescriptorAllocator : IDisposable
+{
+ private readonly DescriptorHeapAllocator _rtvHeap;
+ private readonly DescriptorHeapAllocator _dsvHeap;
+ private readonly DescriptorHeapAllocator _srvHeap;
+ private readonly DescriptorHeapAllocator _samplerHeap;
+
+ private bool _disposed;
+
+ public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256)
+ {
+ var device = GraphicsPipeline.GraphicsDevice;
+
+ _rtvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount);
+ _dsvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount);
+ _srvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
+ _samplerHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount);
+ }
+
+ #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 Utility Methods
+
+ ///
+ /// Gets the RTV heap for binding to the command list.
+ ///
+ public ConstPtr GetRTVHeap() => _rtvHeap.Heap;
+
+ ///
+ /// Gets the DSV heap for binding to the command list.
+ ///
+ public ConstPtr GetDSVHeap() => _dsvHeap.Heap;
+
+ ///
+ /// Gets the SRV heap for binding to the command list.
+ ///
+ public ConstPtr GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
+
+ ///
+ /// Gets the sampler heap for binding to the command list.
+ ///
+ public ConstPtr GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
+
+ ///
+ /// Gets the shader visible heaps that need to be bound to the command list.
+ ///
+ public ConstPtr[] GetShaderVisibleHeaps()
+ {
+ return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
+ }
+
+ #endregion
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _rtvHeap.Dispose();
+ _dsvHeap.Dispose();
+ _srvHeap.Dispose();
+ _samplerHeap.Dispose();
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/Descriptors.cs b/Ghost.Graphics/D3D12/Descriptors.cs
new file mode 100644
index 0000000..28e653a
--- /dev/null
+++ b/Ghost.Graphics/D3D12/Descriptors.cs
@@ -0,0 +1,110 @@
+using Win32.Graphics.Direct3D12;
+
+namespace Ghost.Graphics.D3D12;
+
+///
+/// Base class for D3D12 descriptor implementations.
+///
+internal 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;
+
+ ///
+ /// Gets the CPU descriptor handle.
+ ///
+ public abstract CpuDescriptorHandle CpuHandle
+ {
+ get;
+ }
+
+ ///
+ /// Gets the GPU descriptor handle (only valid for shader-visible descriptors).
+ ///
+ public abstract GpuDescriptorHandle GpuHandle
+ {
+ get;
+ }
+}
+
+///
+/// D3D12 implementation of render target view (RTV) descriptor.
+///
+internal sealed class RenderTargetDescriptor : Descriptor
+{
+ private readonly CpuDescriptorHandle _cpuHandle;
+
+ public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle)
+ : base(index, false)
+ {
+ _cpuHandle = cpuHandle;
+ }
+
+ public override CpuDescriptorHandle CpuHandle => _cpuHandle;
+ public override GpuDescriptorHandle GpuHandle => default;
+}
+
+///
+/// D3D12 implementation of depth stencil view (DSV) descriptor.
+///
+internal sealed class DepthStencilDescriptor : Descriptor
+{
+ private readonly CpuDescriptorHandle _cpuHandle;
+
+ public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle)
+ : base(index, false) // DSVs are not shader visible
+ {
+ _cpuHandle = cpuHandle;
+ }
+
+ public override CpuDescriptorHandle CpuHandle => _cpuHandle;
+ public override GpuDescriptorHandle GpuHandle => default;
+}
+
+///
+/// D3D12 implementation of shader resource view (SRV) descriptor.
+///
+internal sealed class ShaderResourceDescriptor : Descriptor
+{
+ private readonly CpuDescriptorHandle _cpuHandle;
+ private readonly GpuDescriptorHandle _gpuHandle;
+
+ public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
+ : base(index, true)
+ {
+ _cpuHandle = cpuHandle;
+ _gpuHandle = gpuHandle;
+ }
+
+ public override CpuDescriptorHandle CpuHandle => _cpuHandle;
+ public override GpuDescriptorHandle GpuHandle => _gpuHandle;
+}
+
+///
+/// D3D12 implementation of sampler descriptor.
+///
+internal sealed class SamplerDescriptor : Descriptor
+{
+ private readonly CpuDescriptorHandle _cpuHandle;
+ private readonly GpuDescriptorHandle _gpuHandle;
+
+ public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
+ : base(index, true)
+ {
+ _cpuHandle = cpuHandle;
+ _gpuHandle = gpuHandle;
+ }
+
+ public override CpuDescriptorHandle CpuHandle => _cpuHandle;
+ public override GpuDescriptorHandle GpuHandle => _gpuHandle;
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs b/Ghost.Graphics/D3D12/GraphicsDevice.cs
similarity index 76%
rename from Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs
rename to Ghost.Graphics/D3D12/GraphicsDevice.cs
index 4935dee..98f8739 100644
--- a/Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs
+++ b/Ghost.Graphics/D3D12/GraphicsDevice.cs
@@ -1,5 +1,4 @@
using Ghost.Core;
-using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data;
using System.Collections.Immutable;
using Win32;
@@ -9,39 +8,38 @@ using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12;
-internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
+internal unsafe class GraphicsDevice
{
#if DEBUG
- private readonly D3D12DebugLayer _debugLayer;
+ private readonly DebugLayer _debugLayer;
#endif
private ComPtr _dxgiFactory;
private ComPtr _device;
private ComPtr _commandQueue;
- private ImmutableArray _initializeQueue;
- private ImmutableArray _renderers;
+ private ImmutableArray _initializeQueue;
+ private ImmutableArray _renderers;
private bool _disposed;
- public static GraphicsAPI TargetAPI => GraphicsAPI.D3D12;
- public ReadOnlySpan InitializeQueue => _initializeQueue.AsSpan();
- public ReadOnlySpan Renderers => _renderers.AsSpan();
+ public ReadOnlySpan InitializeQueue => _initializeQueue.AsSpan();
+ public ReadOnlySpan Renderers => _renderers.AsSpan();
public ConstPtr NativeDevice => new(_device.Get());
public ConstPtr DXGIFactory => new(_dxgiFactory.Get());
public ConstPtr CommandQueue => new(_commandQueue.Get());
- public D3D12GraphicsDevice()
+ public GraphicsDevice()
{
#if DEBUG
- _debugLayer = new D3D12DebugLayer();
+ _debugLayer = new DebugLayer();
#endif
InitializeDevice();
InitializeCommandQueue();
- _initializeQueue = ImmutableArray.Empty;
- _renderers = ImmutableArray.Empty;
+ _initializeQueue = ImmutableArray.Empty;
+ _renderers = ImmutableArray.Empty;
}
private void InitializeDevice()
@@ -67,7 +65,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
continue;
}
- if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_11_0, __uuidof(), _device.GetVoidAddressOf()).Success)
+ if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof(), _device.GetVoidAddressOf()).Success)
{
break;
}
@@ -75,7 +73,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
if (_device.Get() == null)
{
- throw new PlatformNotSupportedException("Cannot create ID3D12Device");
+ throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
}
@@ -94,17 +92,17 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
}
}
- public IRenderer CreateRenderer(in SwapChainPresenter presenter)
+ public Renderer CreateRenderer(in SwapChainPresenter presenter)
{
- var renderView = new D3D12Renderer(this, in presenter);
+ var renderView = new Renderer(this, in presenter);
ImmutableInterlocked.Update(ref _initializeQueue, old => old.Add(renderView));
return renderView;
}
- public void RemoveRenderer(IRenderer renderer)
+ public void RemoveRenderer(Renderer renderer)
{
- if (renderer is D3D12Renderer dx12RenderView)
+ if (renderer is Renderer dx12RenderView)
{
dx12RenderView.Dispose();
@@ -148,13 +146,8 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
renderer.Dispose();
}
- foreach (var renderView in _renderers)
- {
- renderView.Dispose();
- }
-
_commandQueue.Dispose();
- _device.Dispose();
+ _device.Reset();
_dxgiFactory.Dispose();
#if DEBUG
diff --git a/Ghost.Graphics/D3D12/D3D12Resource.cs b/Ghost.Graphics/D3D12/GraphicsResource.cs
similarity index 87%
rename from Ghost.Graphics/D3D12/D3D12Resource.cs
rename to Ghost.Graphics/D3D12/GraphicsResource.cs
index 17e7f46..4aebf72 100644
--- a/Ghost.Graphics/D3D12/D3D12Resource.cs
+++ b/Ghost.Graphics/D3D12/GraphicsResource.cs
@@ -1,5 +1,4 @@
using Ghost.Core;
-using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using Win32;
@@ -7,7 +6,7 @@ using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
-public unsafe class D3D12Resource : IResource
+public unsafe class GraphicsResource
{
private ComPtr _nativeResource;
private string _name = string.Empty;
@@ -33,13 +32,13 @@ public unsafe class D3D12Resource : IResource
get;
}
- internal D3D12Resource(ComPtr nativeResource, bool temp)
+ internal GraphicsResource(ComPtr nativeResource, bool temp = false)
{
_nativeResource = nativeResource;
TempResource = temp;
}
- ~D3D12Resource()
+ ~GraphicsResource()
{
DisposeInternal();
}
@@ -68,12 +67,7 @@ public unsafe class D3D12Resource : IResource
var range = new Win32.Graphics.Direct3D12.Range(0, size);
void* mappedPtr;
- var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr);
- if (hr.Failure)
- {
- var message = hr.ToString();
- throw new InvalidOperationException($"Failed to map resource: {message}");
- }
+ ThrowIfFailed(_nativeResource.Get()->Map(0, &range, &mappedPtr));
Unsafe.CopyBlock(mappedPtr, data, size);
_nativeResource.Get()->Unmap(0, &range);
@@ -99,7 +93,7 @@ public unsafe class D3D12Resource : IResource
public void ReadData(T* pData, uint* size)
where T : unmanaged
{
- ReadData(pData, size);
+ ReadData((void*)pData, size);
}
public void ReadData(void* pData, uint* size)
diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/Renderer.cs
similarity index 88%
rename from Ghost.Graphics/D3D12/D3D12Renderer.cs
rename to Ghost.Graphics/D3D12/Renderer.cs
index c0c404b..822fb80 100644
--- a/Ghost.Graphics/D3D12/D3D12Renderer.cs
+++ b/Ghost.Graphics/D3D12/Renderer.cs
@@ -14,7 +14,7 @@ namespace Ghost.Graphics.D3D12;
// TODO: We should split the renderer and swap chain into different classes to allow for more flexibility in rendering pipelines.
// Each renderer can have a render target (swap chain or texture).
// When render target is null, skip the render pass execution.
-internal unsafe class D3D12Renderer : IRenderer
+internal unsafe class Renderer
{
private struct FrameResource : IDisposable
{
@@ -22,17 +22,17 @@ internal unsafe class D3D12Renderer : IRenderer
public ComPtr commandList;
public ComPtr backBuffer;
- public ICommandBuffer commandBuffer;
- public uint backBufferDescriptorIndexes;
+ public CommandList cmd;
+ public RenderTargetDescriptor rtvDescriptor;
public ulong fenceValue;
- public FrameResource(D3D12Renderer renderer, uint index)
+ public FrameResource(Renderer renderer, uint index)
{
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandAllocator(CommandListType.Direct, __uuidof(), commandAllocator.GetVoidAddressOf());
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandList(0u, CommandListType.Direct, commandAllocator.Get(), null, __uuidof(), commandList.GetVoidAddressOf());
- commandBuffer = new D3D12CommandBuffer(commandList.Get());
- backBufferDescriptorIndexes = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
+ cmd = new(commandList.Get());
+ rtvDescriptor = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
}
public readonly void ResetCommandBuffer()
@@ -58,13 +58,11 @@ internal unsafe class D3D12Renderer : IRenderer
commandAllocator.Dispose();
commandList.Dispose();
backBuffer.Dispose();
+ GraphicsPipeline.DescriptorAllocator.ReleaseRTV(rtvDescriptor);
}
}
- private const int _RENDER_TARGET_VIEW_HEAP_SIZE = 1024;
- private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256;
-
- private readonly D3D12GraphicsDevice _graphicsDevice;
+ private readonly GraphicsDevice _graphicsDevice;
private readonly SwapChainPresenter _swapChainPresenter;
private ComPtr _swapChain = default;
@@ -74,8 +72,6 @@ internal unsafe class D3D12Renderer : IRenderer
private readonly FrameResource[] _frameResources;
private readonly AutoResetEvent _fenceEvent;
- private D3D12DescriptorAllocator _rtvHeap;
-
private ImmutableArray _renderPasses;
private readonly Lock _lock = new();
@@ -89,16 +85,15 @@ internal unsafe class D3D12Renderer : IRenderer
public ReadOnlySpan RenderPasses => _renderPasses.AsSpan();
- public D3D12Renderer(D3D12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
+ public Renderer(GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{
_graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_viewPortWidth = swapChainSurface.Width;
_viewPortHeight = swapChainSurface.Height;
- _rtvHeap = new(_graphicsDevice.NativeDevice, DescriptorHeapType.Rtv, _RENDER_TARGET_VIEW_HEAP_SIZE);
_fenceEvent = new(false);
- _renderPasses = [new MeshRenderPass()];
+ _renderPasses = [new BindlessMeshRenderPass()];
InitializeSwapChain();
InitializeFrameResource(out _frameResources);
@@ -190,14 +185,14 @@ internal unsafe class D3D12Renderer : IRenderer
}
}
- private uint CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
+ private RenderTargetDescriptor CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
{
_swapChain.Get()->GetBuffer(i, __uuidof(), (void**)backBuffer);
(*backBuffer)->SetName($"BackBuffer_{i}");
- var index = _rtvHeap.AllocateDescriptor();
- var rtvHandle = _rtvHeap.GetCpuHandle(index);
+ var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
+ var rtvHandle = rtvDescriptor.CpuHandle;
_graphicsDevice.NativeDevice.Ptr->CreateRenderTargetView(*backBuffer, null, rtvHandle);
- return index;
+ return rtvDescriptor;
}
public void ExecutePendingResize()
@@ -225,7 +220,7 @@ internal unsafe class D3D12Renderer : IRenderer
if (frameResource.backBuffer.Get() is not null)
{
var c = frameResource.backBuffer.Reset();
- _rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes);
+ GraphicsPipeline.DescriptorAllocator.ReleaseRTV(frameResource.rtvDescriptor);
}
frameResource.fenceValue = _frameResources[_backBufferIndex].fenceValue;
@@ -239,7 +234,7 @@ internal unsafe class D3D12Renderer : IRenderer
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
var index = CreateBackBufferResource(i, _frameResources[i].backBuffer.GetAddressOf());
- _frameResources[i].backBufferDescriptorIndexes = index;
+ _frameResources[i].rtvDescriptor = index;
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
@@ -253,7 +248,7 @@ internal unsafe class D3D12Renderer : IRenderer
foreach (var pass in _renderPasses)
{
- pass.Initialize(frameResource.commandBuffer);
+ pass.Initialize(frameResource.cmd);
}
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue);
@@ -265,7 +260,7 @@ internal unsafe class D3D12Renderer : IRenderer
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
ref var frameResource = ref _frameResources[_backBufferIndex];
- var cpuHandle = _rtvHeap.GetCpuHandle(frameResource.backBufferDescriptorIndexes);
+ var cpuHandle = frameResource.rtvDescriptor.CpuHandle;
frameResource.ResetCommandBuffer();
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.Present, ResourceStates.RenderTarget);
@@ -282,7 +277,7 @@ internal unsafe class D3D12Renderer : IRenderer
foreach (var pass in _renderPasses)
{
- pass.Execute(frameResource.commandBuffer);
+ pass.Execute(frameResource.cmd);
}
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.RenderTarget, ResourceStates.Present);
@@ -353,8 +348,6 @@ internal unsafe class D3D12Renderer : IRenderer
_fence.Dispose();
_fenceEvent.Dispose();
- _rtvHeap.Dispose();
-
_backBufferIndex = 0;
_disposed = true;
diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/ResourceAllocator.cs
similarity index 90%
rename from Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs
rename to Ghost.Graphics/D3D12/ResourceAllocator.cs
index c324fcd..ab5dc7a 100644
--- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs
+++ b/Ghost.Graphics/D3D12/ResourceAllocator.cs
@@ -1,23 +1,22 @@
-using Ghost.Graphics.Contracts;
-using Win32;
+using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
-internal unsafe class D3D12ResourceAllocator : IResourceAllocator
+internal unsafe class ResourceAllocator
{
private readonly struct TempResourceAllocInfo
{
- public readonly D3D12Resource resource;
+ public readonly GraphicsResource resource;
public readonly uint cpuFenceValue;
- public TempResourceAllocInfo(D3D12Resource resource, uint cpuFenceValue)
+ public TempResourceAllocInfo(GraphicsResource resource, uint cpuFenceValue)
{
this.resource = resource;
this.cpuFenceValue = cpuFenceValue;
}
- public TempResourceAllocInfo(D3D12Resource resource)
+ public TempResourceAllocInfo(GraphicsResource resource)
: this(resource, GraphicsPipeline.CPUFenceValue + 1)
{
}
@@ -224,29 +223,28 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
// }
//}
- public IResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
+ public GraphicsResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
- var device = GraphicsPipeline.GetGraphicsDevice();
var heapProperties = new HeapProperties(HeapType.Upload);
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
ComPtr buffer = default;
- device.NativeDevice.Ptr->CreateCommittedResource(
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
&heapProperties,
HeapFlags.None,
&resourceDescription,
- ResourceStates.Common,
+ ResourceStates.GenericRead,
null,
__uuidof(),
buffer.GetVoidAddressOf()
);
- var resource = new D3D12Resource(buffer.Move(), tempResource);
+ var resource = new GraphicsResource(buffer.Move(), tempResource);
if (tempResource)
{
_temResources.Enqueue(new(resource));
@@ -255,19 +253,18 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
return resource;
}
- public IResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
+ public GraphicsResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
- var device = GraphicsPipeline.GetGraphicsDevice();
var heapProperties = new HeapProperties(HeapType.Default);
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
ComPtr buffer = default;
- device.NativeDevice.Ptr->CreateCommittedResource(
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
&heapProperties,
HeapFlags.None,
&resourceDescription,
@@ -277,7 +274,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
buffer.GetVoidAddressOf()
);
- var resource = new D3D12Resource(buffer.Move(), tempResource);
+ var resource = new GraphicsResource(buffer.Move(), tempResource);
if (tempResource)
{
_temResources.Enqueue(new(resource));
diff --git a/Ghost.Graphics/D3D12/ResourceUploadBatch.cs b/Ghost.Graphics/D3D12/ResourceUploadBatch.cs
new file mode 100644
index 0000000..fdd774b
--- /dev/null
+++ b/Ghost.Graphics/D3D12/ResourceUploadBatch.cs
@@ -0,0 +1,339 @@
+using System.Runtime.CompilerServices;
+using Win32;
+using Win32.Graphics.Direct3D12;
+
+namespace Ghost.Graphics.D3D12;
+
+///
+/// Resource upload batch for efficiently uploading resources to GPU memory
+///
+internal unsafe class ResourceUploadBatch : IDisposable
+{
+ private struct TrackedResource
+ {
+ public GraphicsResource resource;
+ public ResourceStates state;
+
+ public TrackedResource(GraphicsResource resource, ResourceStates state)
+ {
+ this.resource = resource;
+ this.state = state;
+ }
+ }
+
+ private ComPtr _commandAllocator;
+ private ComPtr _commandList;
+ private ComPtr _fence;
+
+ private readonly GraphicsDevice _device = GraphicsPipeline.GraphicsDevice;
+ private readonly AutoResetEvent _fenceEvent = new(false);
+
+ private ulong _fenceValue;
+ private bool _isRecording;
+ private bool _disposed;
+
+ ///
+ /// Gets whether the batch is currently recording commands
+ ///
+ public bool IsRecording => _isRecording;
+
+ ///
+ /// Creates a new ResourceUploadBatch
+ ///
+ internal ResourceUploadBatch()
+ {
+ Initialize();
+ }
+
+ ~ResourceUploadBatch()
+ {
+ Dispose();
+ }
+
+ private void Initialize()
+ {
+ ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandAllocator(
+ CommandListType.Direct,
+ __uuidof(),
+ _commandAllocator.GetVoidAddressOf()));
+
+ ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandList1(
+ 0,
+ CommandListType.Direct,
+ CommandListFlags.None,
+ __uuidof(),
+ _commandList.GetVoidAddressOf()));
+
+ ThrowIfFailed(_device.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof(), _fence.GetVoidAddressOf()));
+ }
+
+ ///
+ /// Begins recording upload commands
+ ///
+ public void Begin()
+ {
+ if (_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is already recording");
+ }
+
+ ThrowIfFailed(_commandAllocator.Get()->Reset());
+ ThrowIfFailed(_commandList.Get()->Reset(_commandAllocator.Get(), null));
+
+ _isRecording = true;
+ }
+
+ ///
+ /// Uploads buffer data to a resource
+ ///
+ /// Type of data to upload
+ /// Destination resource
+ /// Source data
+ public void Upload(GraphicsResource resource, ReadOnlySpan data)
+ where T : unmanaged
+ {
+ if (!_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is not recording");
+ }
+
+ var sizeInBytes = (uint)(data.Length * sizeof(T));
+
+ // Create upload buffer
+ var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true);
+
+ // Copy data to upload buffer
+ fixed (T* dataPtr = data)
+ {
+ uploadBuffer.SetData(dataPtr, (uint)data.Length);
+ }
+
+ // Copy from upload buffer to destination
+ _commandList.Get()->CopyBufferRegion(
+ resource.NativeResource.Ptr,
+ 0,
+ uploadBuffer.NativeResource.Ptr,
+ 0,
+ sizeInBytes);
+ }
+
+ ///
+ /// Uploads subresource data to a texture
+ ///
+ /// Destination texture resource
+ /// First subresource index
+ /// Subresource data array
+ /// Number of subresources
+ public void Upload(GraphicsResource resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources)
+ {
+ if (!_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is not recording");
+ }
+
+ var resourceDesc = resource.NativeResource.Ptr->GetDesc();
+
+ var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources);
+
+ var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true);
+
+ UpdateSubresources(
+ resource.NativeResource.Ptr,
+ uploadBuffer.NativeResource.Ptr,
+ 0,
+ firstSubresource,
+ numSubresources,
+ subresources);
+ }
+
+ ///
+ /// Adds a resource transition barrier
+ ///
+ /// Resource to transition
+ /// State before transition
+ /// State after transition
+ public void Transition(GraphicsResource resource, ResourceStates stateBefore, ResourceStates stateAfter)
+ {
+ if (!_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is not recording");
+ }
+
+ // Apply the transition immediately
+ var barrier = new ResourceBarrier
+ {
+ Type = ResourceBarrierType.Transition,
+ Flags = ResourceBarrierFlags.None,
+ Anonymous = new ResourceBarrier._Anonymous_e__Union
+ {
+ Transition = new ResourceTransitionBarrier
+ {
+ pResource = resource.NativeResource.Ptr,
+ Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
+ StateBefore = stateBefore,
+ StateAfter = stateAfter
+ }
+ }
+ };
+
+ _commandList.Get()->ResourceBarrier(1, &barrier);
+ }
+
+ ///
+ /// Generates mipmaps for a texture (if supported)
+ ///
+ /// Texture resource
+ public void GenerateMips(GraphicsResource resource)
+ {
+ if (!_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is not recording");
+ }
+
+ // This would require compute shader implementation for mipmap generation
+ // For now, this is a placeholder - DirectXTK12 uses a compute shader approach
+ throw new NotImplementedException("Mipmap generation not yet implemented");
+ }
+
+ ///
+ /// Ends recording and submits the batch for execution
+ ///
+ /// Future that completes when upload is finished
+ public ulong End()
+ {
+ if (!_isRecording)
+ {
+ throw new InvalidOperationException("Upload batch is not recording");
+ }
+
+ ThrowIfFailed(_commandList.Get()->Close());
+ _device.CommandQueue.Ptr->ExecuteCommandLists(1, (ID3D12CommandList**)_commandList.GetAddressOf());
+ ThrowIfFailed(_device.CommandQueue.Ptr->Signal(_fence.Get(), ++_fenceValue));
+
+ _isRecording = false;
+
+ return _fenceValue;
+ }
+
+ ///
+ /// Waits for the upload batch to complete
+ ///
+ /// Fence value to wait for
+ public void WaitForCompletion(ulong fenceValue)
+ {
+ if (_fence.Get()->GetCompletedValue() < fenceValue)
+ {
+ var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
+ ThrowIfFailed(_fence.Get()->SetEventOnCompletion(fenceValue, handle));
+ _fenceEvent.WaitOne();
+ }
+ }
+
+ private ulong GetRequiredIntermediateSize(GraphicsResource destinationResource, uint firstSubresource, uint numSubresources)
+ {
+ var resourceDesc = destinationResource.NativeResource.Ptr->GetDesc();
+
+ ulong requiredSize = 0;
+ var numRows = stackalloc uint[(int)numSubresources];
+ var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
+
+ _device.NativeDevice.Ptr->GetCopyableFootprints(
+ &resourceDesc,
+ firstSubresource,
+ numSubresources,
+ 0,
+ null,
+ numRows,
+ rowSizeInBytes,
+ &requiredSize);
+
+ return requiredSize;
+ }
+
+ private void UpdateSubresources(ID3D12Resource* destinationResource, ID3D12Resource* intermediate, ulong intermediateOffset, uint firstSubresource, uint numSubresources, SubresourceData* pSubresourceData)
+ {
+ var destDesc = destinationResource->GetDesc();
+
+ var layouts = stackalloc PlacedSubresourceFootprint[(int)numSubresources];
+ var numRows = stackalloc uint[(int)numSubresources];
+ var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
+ ulong requiredSize = 0;
+
+ _device.NativeDevice.Ptr->GetCopyableFootprints(
+ &destDesc,
+ firstSubresource,
+ numSubresources,
+ intermediateOffset,
+ layouts,
+ numRows,
+ rowSizeInBytes,
+ &requiredSize);
+
+ void* pMappedData;
+ ThrowIfFailed(intermediate->Map(0, null, &pMappedData));
+ for (uint i = 0; i < numSubresources; i++)
+ {
+ var srcData = pSubresourceData[i];
+ var destLayout = layouts[i];
+ var pDestSlice = (byte*)pMappedData + destLayout.Offset;
+ var pSrcSlice = (byte*)srcData.pData;
+
+ for (uint y = 0; y < numRows[i]; y++)
+ {
+ var pDestRow = pDestSlice + (y * destLayout.Footprint.RowPitch);
+ var pSrcRow = pSrcSlice + (y * srcData.RowPitch);
+
+ Unsafe.CopyBlockUnaligned(pDestRow, pSrcRow, (uint)rowSizeInBytes[i]);
+ }
+ }
+
+ intermediate->Unmap(0, null);
+
+ if (destDesc.Dimension == ResourceDimension.Buffer)
+ {
+ _commandList.Get()->CopyBufferRegion(destinationResource, 0, intermediate, intermediateOffset, requiredSize);
+ }
+ else
+ {
+ for (uint i = 0; i < numSubresources; i++)
+ {
+ var destLocation = new TextureCopyLocation(destinationResource, firstSubresource + i);
+ var srcLocation = new TextureCopyLocation(intermediate, in layouts[i]);
+ var box = new Box
+ {
+ left = 0,
+ top = 0,
+ front = 0,
+ right = (uint)destDesc.Width,
+ bottom = destDesc.Height,
+ back = destDesc.DepthOrArraySize
+ };
+
+ _commandList.Get()->CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &box);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (_isRecording)
+ {
+ _commandList.Get()->Close();
+ _isRecording = false;
+ }
+
+ _fence.Dispose();
+ _commandList.Dispose();
+ _commandAllocator.Dispose();
+
+ _fenceEvent.Dispose();
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs
similarity index 97%
rename from Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs
rename to Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs
index 2cb4f80..88adf3a 100644
--- a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs
+++ b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs
@@ -7,7 +7,7 @@ using DescriptorIndex = System.UInt32;
namespace Ghost.Graphics.D3D12.Utilities;
-internal unsafe struct D3D12DescriptorAllocator : IDisposable
+internal unsafe struct DescriptorHeapAllocator : IDisposable
{
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
@@ -50,7 +50,7 @@ internal unsafe struct D3D12DescriptorAllocator : IDisposable
public readonly ConstPtr Heap => new(_heap.Get());
public readonly ConstPtr ShaderVisibleHeap => new(_shaderVisibleHeap.Get());
- public D3D12DescriptorAllocator(ConstPtr device, DescriptorHeapType type, uint numDescriptors)
+ public DescriptorHeapAllocator(ConstPtr device, DescriptorHeapType type, uint numDescriptors)
{
_device = device;
HeapType = type;
diff --git a/Ghost.Graphics/Data/GraphicsAPI.cs b/Ghost.Graphics/Data/GraphicsAPI.cs
deleted file mode 100644
index d75072f..0000000
--- a/Ghost.Graphics/Data/GraphicsAPI.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Ghost.Graphics.Data;
-
-public enum GraphicsAPI
-{
- None,
- D3D12
-}
\ No newline at end of file
diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs
index 883f9f1..528e703 100644
--- a/Ghost.Graphics/Data/Material.cs
+++ b/Ghost.Graphics/Data/Material.cs
@@ -1,13 +1,15 @@
-using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Ghost.Graphics.Shading;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
-public class Material : IDisposable
+public unsafe class Material : IDisposable
{
- private readonly Dictionary _cbufferCaches;
+ private readonly CBufferCache[] _cbufferCaches;
+ private readonly Texture2D?[] _textures;
+ private readonly Dictionary _textureNameToSlotMap;
private bool _disposed;
@@ -21,10 +23,37 @@ public class Material : IDisposable
{
Shader = shader;
- _cbufferCaches = new();
- foreach (var cbufferInfo in shader.ConstantBuffers.Values)
+ if (shader.ConstantBuffers.Count > 0)
{
- _cbufferCaches.Add(cbufferInfo.Name, new CBufferCache(cbufferInfo.Size));
+ var maxSlot = shader.ConstantBuffers.Max(cb => cb.RegisterSlot);
+ _cbufferCaches = new CBufferCache[maxSlot + 1];
+
+ foreach (var cbufferInfo in shader.ConstantBuffers)
+ {
+ _cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(cbufferInfo.Size);
+ }
+ }
+ else
+ {
+ _cbufferCaches = Array.Empty();
+ }
+
+ // Initialize texture storage
+ if (shader.Textures.Count > 0)
+ {
+ var maxTextureSlot = shader.Textures.Max(t => t.RegisterSlot);
+ _textures = new Texture2D?[maxTextureSlot + 1];
+ _textureNameToSlotMap = new Dictionary();
+
+ foreach (var textureInfo in shader.Textures)
+ {
+ _textureNameToSlotMap.Add(textureInfo.Name, (int)textureInfo.RegisterSlot);
+ }
+ }
+ else
+ {
+ _textures = Array.Empty();
+ _textureNameToSlotMap = new Dictionary();
}
}
@@ -33,52 +62,191 @@ public class Material : IDisposable
Dispose();
}
+ ///
+ /// Sets a float property in the material's constant buffer.
+ ///
+ /// The ID of the property to set.
+ /// The value to set for the property.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetFloat(int propertyId, in float value)
+ {
+ WriteToCache(propertyId, in value);
+ }
+
+ ///
+ /// Sets a float property in the material's constant buffer.
+ ///
+ /// The name of the property to set.
+ /// The value to set for the property.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
- WriteToCache(propertyName, in value);
+ SetFloat(Shader.GetPropertyId(propertyName), in value);
}
+ ///
+ /// Sets a Vector property in the material's constant buffer.
+ ///
+ /// The ID of the property to set.
+ /// The value to set for the property.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetVector(int propertyId, in Vector4 value)
+ {
+ WriteToCache(propertyId, in value);
+ }
+
+ ///
+ /// Sets a Vector property in the material's constant buffer.
+ ///
+ /// The name of the property to set.
+ /// The value to set for the property.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
- WriteToCache(propertyName, in value);
+ SetVector(Shader.GetPropertyId(propertyName), in value);
}
+ ///
+ /// Sets a Matrix property in the material's constant buffer.
+ ///
+ /// The ID of the property to set.
+ /// The value to set for the property.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetMatrix(int propertyId, in Matrix4x4 value)
+ {
+ WriteToCache(propertyId, in value);
+ }
+
+ ///
+ /// Sets a Matrix property in the material's constant buffer.
+ ///
+ /// The name of the property to set.
+ /// The value to set for the property.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
- WriteToCache(propertyName, in value);
+ SetMatrix(Shader.GetPropertyId(propertyName), in value);
}
- private unsafe void WriteToCache(string propertyName, in T value) where T : unmanaged
+ ///
+ /// Sets a texture property in the material.
+ ///
+ /// The ID of the texture to set.
+ /// The texture to set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetTexture(int textureId, Texture2D? texture)
{
- if (!Shader.Properties.TryGetValue(propertyName, out var propInfo))
+ if (textureId == -1)
{
- throw new ArgumentException($"Property '{propertyName}' does not exist in the shader.", nameof(propertyName));
+ throw new ArgumentException("Texture ID is invalid.");
}
+ if (textureId >= _textures.Length)
+ {
+ throw new ArgumentException($"Texture ID {textureId} is out of range.");
+ }
+
+ _textures[textureId] = texture;
+ }
+
+ ///
+ /// Sets a texture property in the material.
+ ///
+ /// The name of the texture to set.
+ /// The texture to set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetTexture(string textureName, Texture2D? texture)
+ {
+ if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
+ {
+ throw new ArgumentException($"Texture '{textureName}' not found in shader.");
+ }
+
+ _textures[slot] = texture;
+ }
+
+ ///
+ /// Gets a texture property from the material.
+ ///
+ /// The ID of the texture to get.
+ /// The texture, or null if not set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Texture2D? GetTexture(int textureId)
+ {
+ if (textureId == -1 || textureId >= _textures.Length)
+ {
+ return null;
+ }
+
+ return _textures[textureId];
+ }
+
+ ///
+ /// Gets a texture property from the material.
+ ///
+ /// The name of the texture to get.
+ /// The texture, or null if not set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Texture2D? GetTexture(string textureName)
+ {
+ if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
+ {
+ return null;
+ }
+
+ return _textures[slot];
+ }
+
+ private unsafe void WriteToCache(int propertyId, in T value) where T : unmanaged
+ {
+ if (propertyId == -1)
+ {
+ throw new ArgumentException("Property ID is invalid.");
+ }
+
+ var propInfo = Shader.Properties[propertyId];
if (propInfo.Size != sizeof(T))
{
- throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.", nameof(propertyName));
+ throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.");
}
- var cache = _cbufferCaches[propInfo.CBufferName];
+ var cache = _cbufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
- public void UploadAndBind(ICommandBuffer cmb)
+ ///
+ /// Uploads the material data to the GPU.
+ ///
+ public void UploadMaterialData()
{
- foreach (var cache in _cbufferCaches.Values)
+ foreach (var cache in _cbufferCaches)
{
cache.UploadToGpu();
}
+ }
- foreach (var (name, cache) in _cbufferCaches)
+ internal void Bind(CommandList cmd)
+ {
+ // Bind constant buffers
+ foreach (var cbufferInfo in Shader.ConstantBuffers)
{
- var cbufferInfo = Shader.ConstantBuffers[name];
- cmb.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
+ var cache = _cbufferCaches[cbufferInfo.RegisterSlot];
+ cmd.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
+ }
+
+ // Bind textures using descriptor table
+ if (Shader.Textures.Count > 0)
+ {
+ // Get the first texture info to determine the root parameter index
+ var textureInfo = Shader.Textures[0];
+ var texture = _textures[0]; // Get the first texture
+
+ if (texture != null)
+ {
+ // Set descriptor table for SRVs
+ cmd.NativeCommandList.Ptr->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, texture.SRVDescriptor.GpuHandle);
+ }
}
}
@@ -89,13 +257,11 @@ public class Material : IDisposable
return;
}
- foreach (var cache in _cbufferCaches.Values)
+ foreach (var cache in _cbufferCaches)
{
cache.Dispose();
}
- _cbufferCaches.Clear();
-
GC.SuppressFinalize(this);
_disposed = true;
diff --git a/Ghost.Graphics/Data/Mesh.cs b/Ghost.Graphics/Data/Mesh.cs
index c08f4d1..30cd4a0 100644
--- a/Ghost.Graphics/Data/Mesh.cs
+++ b/Ghost.Graphics/Data/Mesh.cs
@@ -1,5 +1,5 @@
using Ghost.Core;
-using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Numerics;
@@ -16,8 +16,8 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
private Bounds _boundingBox;
- private IResource? _vertexBuffer;
- private IResource? _indexBuffer;
+ private GraphicsResource? _vertexBuffer;
+ private GraphicsResource? _indexBuffer;
private VertexBufferView _vertexBufferView;
private IndexBufferView _indexBufferView;
@@ -204,8 +204,7 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
///
/// Uploads the mesh data to GPU resources.
///
- /// The command buffer to record the upload commands.
- public unsafe void UploadMeshData(ICommandBuffer cmb)
+ public unsafe void UploadMeshData()
{
if (VertexCount == 0 || IndexCount == 0)
{
@@ -220,17 +219,13 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
_vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize);
_indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize);
- var vertexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(vertexBufferSize, false);
- var indexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(indexBufferSize, false);
-
- vertexUploadBuffer.SetData(_vertices.AsSpan());
- indexUploadBuffer.SetData(_indices.AsSpan());
-
- cmb.CopyResource(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
- cmb.CopyResource(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
-
- cmb.BarrierTransition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
- cmb.BarrierTransition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
+ using var uploadBatch = new ResourceUploadBatch();
+ uploadBatch.Begin();
+ uploadBatch.Upload(_vertexBuffer, _vertices.AsSpan());
+ uploadBatch.Upload(_indexBuffer, _indices.AsSpan());
+ uploadBatch.Transition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
+ uploadBatch.Transition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
+ uploadBatch.WaitForCompletion(uploadBatch.End());
_vertexBufferView = new VertexBufferView
{
diff --git a/Ghost.Graphics/Data/Texture2D.cs b/Ghost.Graphics/Data/Texture2D.cs
new file mode 100644
index 0000000..c77766b
--- /dev/null
+++ b/Ghost.Graphics/Data/Texture2D.cs
@@ -0,0 +1,357 @@
+using Ghost.Graphics.D3D12;
+using Misaki.HighPerformance.LowLevel.Collections;
+using Misaki.HighPerformance.LowLevel.Helpers;
+using System.Runtime.InteropServices;
+using Win32;
+using Win32.Graphics.Direct3D12;
+using Win32.Graphics.Dxgi.Common;
+
+namespace Ghost.Graphics.Data;
+
+public unsafe class Texture2D : IDisposable
+{
+ private UnsafeArray _cpuData;
+
+ private readonly GraphicsResource _resource;
+ private readonly ShaderResourceDescriptor _srvDescriptor;
+
+ private bool _disposed;
+
+ public uint Width
+ {
+ get;
+ }
+
+ public uint Height
+ {
+ get;
+ }
+
+ public Format Format
+ {
+ get;
+ }
+
+ public uint BytesPerPixel
+ {
+ get;
+ }
+
+ public uint Pitch
+ {
+ get;
+ }
+
+ public uint DataSize
+ {
+ get;
+ }
+
+ internal ShaderResourceDescriptor SRVDescriptor => _srvDescriptor;
+
+ public GraphicsResource? Resource => _resource;
+ public ReadOnlySpan CpuData => _cpuData.AsSpan();
+
+ public Texture2D(uint width, uint height, Span data, Format format)
+ {
+ Width = width;
+ Height = height;
+ Format = format;
+ BytesPerPixel = GetBytesPerPixel(format);
+ Pitch = width * BytesPerPixel;
+ DataSize = Pitch * height;
+
+ // Initialize CPU-side data
+ _cpuData = new((int)DataSize, Allocator.Persistent);
+
+ if (!data.IsEmpty)
+ {
+ if (data.Length != DataSize)
+ {
+ throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
+ }
+
+ data.CopyTo(_cpuData.AsSpan());
+ }
+
+ _resource = CreateGpuResource();
+ _srvDescriptor = CreateShaderResourceView();
+ }
+ private static uint GetBytesPerPixel(Format format)
+ {
+ return format switch
+ {
+ Format.R8G8B8A8Unorm => 4,
+ Format.R8G8B8A8UnormSrgb => 4,
+ Format.B8G8R8A8Unorm => 4,
+ Format.B8G8R8A8UnormSrgb => 4,
+ Format.R8G8B8A8Uint => 4,
+ Format.R8G8B8A8Sint => 4,
+ Format.R8G8B8A8Snorm => 4,
+ Format.R8G8Unorm => 2,
+ Format.R8G8Uint => 2,
+ Format.R8G8Sint => 2,
+ Format.R8G8Snorm => 2,
+ Format.R8Unorm => 1,
+ Format.R8Uint => 1,
+ Format.R8Sint => 1,
+ Format.R8Snorm => 1,
+ Format.A8Unorm => 1,
+ Format.R16G16B16A16Float => 8,
+ Format.R16G16B16A16Unorm => 8,
+ Format.R16G16B16A16Uint => 8,
+ Format.R16G16B16A16Sint => 8,
+ Format.R16G16B16A16Snorm => 8,
+ Format.R32G32B32A32Float => 16,
+ Format.R32G32B32A32Uint => 16,
+ Format.R32G32B32A32Sint => 16,
+ Format.R32G32B32Float => 12,
+ Format.R32G32B32Uint => 12,
+ Format.R32G32B32Sint => 12,
+ Format.R32G32Float => 8,
+ Format.R32G32Uint => 8,
+ Format.R32G32Sint => 8,
+ Format.R32Float => 4,
+ Format.R32Uint => 4,
+ Format.R32Sint => 4,
+ Format.R16G16Float => 4,
+ Format.R16G16Unorm => 4,
+ Format.R16G16Uint => 4,
+ Format.R16G16Sint => 4,
+ Format.R16G16Snorm => 4,
+ Format.R16Float => 2,
+ Format.R16Unorm => 2,
+ Format.R16Uint => 2,
+ Format.R16Sint => 2,
+ Format.R16Snorm => 2,
+ _ => throw new NotSupportedException($"Format {format} is not supported.")
+ };
+ }
+
+ private GraphicsResource CreateGpuResource()
+ {
+ var heapProperties = new HeapProperties(HeapType.Default);
+ var resourceDesc = ResourceDescription.Tex2D(Format, Width, Height);
+
+ ComPtr textureResource = default;
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
+ &heapProperties,
+ HeapFlags.None,
+ &resourceDesc,
+ ResourceStates.Common,
+ null,
+ __uuidof(),
+ textureResource.GetVoidAddressOf()
+ );
+
+ return new(textureResource.Move());
+ }
+
+ private ShaderResourceDescriptor CreateShaderResourceView()
+ {
+ var srvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateSRV();
+
+ // Create the actual SRV
+ var srvDesc = new ShaderResourceViewDescription
+ {
+ Format = Format,
+ ViewDimension = SrvDimension.Texture2D,
+ Texture2D = new Texture2DSrv { MipLevels = 1 },
+ Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
+ };
+
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, srvDescriptor.CpuHandle);
+
+ return srvDescriptor;
+ }
+
+ private void CopyBufferToTexture(CommandList cmd, GraphicsResource srcBuffer, GraphicsResource dstTexture)
+ {
+ // Calculate the proper footprint for the placed subresource
+ var resourceDesc = dstTexture.NativeResource.Ptr->GetDesc();
+
+ PlacedSubresourceFootprint footprint = new()
+ {
+ Footprint = new SubresourceFootprint
+ {
+ Format = Format,
+ Width = Width,
+ Height = Height,
+ Depth = 1,
+ RowPitch = (uint)((Pitch + 255) & ~255) // Align to 256 bytes
+ }
+ };
+
+ var srcLocation = new TextureCopyLocation(srcBuffer.NativeResource.Ptr, footprint);
+ var dstLocation = new TextureCopyLocation(dstTexture.NativeResource.Ptr, 0);
+
+ cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
+ }
+
+ ///
+ /// Sets the entire texture data on the CPU side.
+ ///
+ /// The texture data to set
+ public void SetData(ReadOnlySpan data)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ if (data.Length != DataSize)
+ {
+ throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
+ }
+
+ data.CopyTo(_cpuData.AsSpan());
+ }
+
+ ///
+ /// Sets the texture data for a specific region on the CPU side.
+ ///
+ /// The texture data to set
+ /// Starting X coordinate
+ /// Starting Y coordinate
+ /// Width of the region
+ /// Height of the region
+ public void SetData(ReadOnlySpan data, uint x, uint y, uint width, uint height)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ if (x + width > Width || y + height > Height)
+ {
+ throw new ArgumentException("Region extends beyond texture bounds.");
+ }
+
+ var expectedSize = width * height * BytesPerPixel;
+ if (data.Length != expectedSize)
+ {
+ throw new ArgumentException($"Data size mismatch. Expected {expectedSize} bytes, got {data.Length} bytes.");
+ }
+
+ for (uint row = 0; row < height; row++)
+ {
+ var srcOffset = (int)(row * width * BytesPerPixel);
+ var dstOffset = (int)((y + row) * Pitch + x * BytesPerPixel);
+ var rowSize = (int)(width * BytesPerPixel);
+
+ data.Slice(srcOffset, rowSize).CopyTo(_cpuData.AsSpan().Slice(dstOffset, rowSize));
+ }
+ }
+
+ ///
+ /// Sets a single pixel value on the CPU side.
+ ///
+ /// X coordinate of the pixel
+ /// Y coordinate of the pixel
+ /// The color data for the pixel
+ public void SetPixel(uint x, uint y, ReadOnlySpan color)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ if (x >= Width || y >= Height)
+ {
+ throw new ArgumentException("Pixel coordinates are outside texture bounds.");
+ }
+
+ if (color.Length != BytesPerPixel)
+ {
+ throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes.");
+ }
+
+ var offset = (int)(y * Pitch + x * BytesPerPixel);
+ color.CopyTo(_cpuData.AsSpan().Slice(offset, (int)BytesPerPixel));
+ }
+
+ ///
+ /// Sets a single pixel value using generic color types.
+ ///
+ /// The color type (e.g., uint for RGBA32)
+ /// X coordinate of the pixel
+ /// Y coordinate of the pixel
+ /// The color value
+ public void SetPixel(uint x, uint y, T color) where T : unmanaged
+ {
+ if (sizeof(T) != BytesPerPixel)
+ {
+ throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
+ }
+
+ var colorSpan = new ReadOnlySpan(&color, sizeof(T));
+ SetPixel(x, y, colorSpan);
+ }
+
+ ///
+ /// Gets a single pixel value from the CPU side data.
+ ///
+ /// X coordinate of the pixel
+ /// Y coordinate of the pixel
+ /// The pixel color data
+ public ReadOnlySpan GetPixel(uint x, uint y)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ if (x >= Width || y >= Height)
+ {
+ throw new ArgumentException("Pixel coordinates are outside texture bounds.");
+ }
+
+ var offset = (int)(y * Pitch + x * BytesPerPixel);
+ return _cpuData.AsSpan().Slice(offset, (int)BytesPerPixel);
+ }
+
+ ///
+ /// Gets a single pixel value as a generic color type.
+ ///
+ /// The color type (e.g., uint for RGBA32)
+ /// X coordinate of the pixel
+ /// Y coordinate of the pixel
+ /// The pixel color value
+ public T GetPixel(uint x, uint y) where T : unmanaged
+ {
+ if (sizeof(T) != BytesPerPixel)
+ {
+ throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
+ }
+
+ var pixelData = GetPixel(x, y);
+ return MemoryMarshal.Read(pixelData);
+ }
+
+ ///
+ /// Uploads the CPU-side texture data to the GPU resource.
+ ///
+ public void UploadTextureData()
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch);
+ var initData = new SubresourceData()
+ {
+ pData = _cpuData.GetUnsafePtr(),
+ RowPitch = rowPitch,
+ SlicePitch = slicePitch
+ };
+
+ using var uploadBatch = new ResourceUploadBatch();
+ uploadBatch.Begin();
+ uploadBatch.Transition(_resource, ResourceStates.Common, ResourceStates.CopyDest);
+ uploadBatch.Upload(_resource, 0, &initData, 1);
+ uploadBatch.Transition(_resource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
+ uploadBatch.WaitForCompletion(uploadBatch.End());
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _cpuData.Dispose();
+ _resource.Dispose();
+
+ GraphicsPipeline.DescriptorAllocator.ReleaseSRV(_srvDescriptor);
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/Data/Vertex.cs b/Ghost.Graphics/Data/Vertex.cs
index 77e31c9..66d6935 100644
--- a/Ghost.Graphics/Data/Vertex.cs
+++ b/Ghost.Graphics/Data/Vertex.cs
@@ -17,7 +17,7 @@ public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color128
private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL");
private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT");
private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR");
- private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("UV");
+ private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD");
public static byte* PositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
public static byte* NormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
diff --git a/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs b/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs
new file mode 100644
index 0000000..a6e115b
--- /dev/null
+++ b/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs
@@ -0,0 +1,178 @@
+using Ghost.Graphics.D3D12;
+using Win32.Graphics.Dxgi.Common;
+
+namespace Ghost.Graphics.Examples;
+
+///
+/// Example demonstrating how to use the IDescriptorAllocator interface.
+///
+public static class DescriptorAllocatorExample
+{
+ ///
+ /// Example showing basic descriptor allocation and release.
+ ///
+ public static void BasicDescriptorUsage()
+ {
+ // Note: In a real application, you would get the descriptor allocator from the graphics device
+ // var device = GraphicsPipeline.GetGraphicsDevice();
+ // var descriptorAllocator = device.DescriptorAllocator;
+
+ // For demonstration purposes, we'll show the interface usage
+ DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device
+
+ // Allocate a single RTV descriptor
+ var rtvDescriptor = descriptorAllocator.AllocateRTV();
+
+ // Allocate multiple SRV descriptors
+ var srvDescriptors = descriptorAllocator.AllocateSRVs(4);
+
+ // Allocate a DSV descriptor
+ var dsvDescriptor = descriptorAllocator.AllocateDSV();
+
+ // Allocate sampler descriptors
+ var samplerDescriptors = descriptorAllocator.AllocateSamplers(2);
+
+ // Use the descriptors...
+ ProcessDescriptors(rtvDescriptor, srvDescriptors, dsvDescriptor, samplerDescriptors);
+
+ // Release descriptors when done
+ descriptorAllocator.ReleaseRTV(rtvDescriptor);
+ descriptorAllocator.ReleaseSRVs(srvDescriptors);
+ descriptorAllocator.ReleaseDSV(dsvDescriptor);
+ descriptorAllocator.ReleaseSamplers(samplerDescriptors);
+ }
+
+ ///
+ /// Example showing how to work with D3D12-specific descriptor handles.
+ ///
+ public static void D3D12SpecificUsage()
+ {
+ // In D3D12-specific code, you can cast to get the underlying handles
+ DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device
+
+ var srvDescriptor = descriptorAllocator.AllocateSRV();
+
+ // For D3D12, you can cast to get the specific handles
+ if (srvDescriptor is ShaderResourceDescriptor d3d12Srv)
+ {
+ var cpuHandle = d3d12Srv.CpuHandle;
+ var gpuHandle = d3d12Srv.GpuHandle;
+
+ // Use the handles for D3D12 API calls
+ // device.CreateShaderResourceView(resource, &srvDesc, cpuHandle);
+ // commandList.SetGraphicsRootDescriptorTable(0, gpuHandle);
+ }
+
+ descriptorAllocator.ReleaseSRV(srvDescriptor);
+ }
+
+ ///
+ /// Example showing descriptor management in a texture class.
+ ///
+ public static void TextureDescriptorUsage()
+ {
+ // This shows how you might integrate descriptor allocation in a texture class
+ DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device
+
+ // Create a texture with SRV
+ var texture = new ExampleTexture2D(512, 512, Format.R8G8B8A8Unorm, descriptorAllocator);
+
+ // The texture automatically manages its SRV descriptor
+ var srvDescriptor = texture.GetShaderResourceView();
+
+ // Use the texture...
+
+ // Dispose will automatically release the descriptor
+ texture.Dispose();
+ }
+
+ private static void ProcessDescriptors(
+ RenderTargetDescriptor rtv,
+ ShaderResourceDescriptor[] srvs,
+ DepthStencilDescriptor dsv,
+ SamplerDescriptor[] samplers)
+ {
+ // Process the descriptors - check validity, shader visibility, etc.
+ if (rtv.IsValid)
+ {
+ // Use RTV descriptor
+ }
+
+ foreach (var srv in srvs)
+ {
+ if (srv.IsValid && srv.IsShaderVisible)
+ {
+ // Use SRV descriptor in shaders
+ }
+ }
+
+ if (dsv.IsValid)
+ {
+ // Use DSV descriptor
+ }
+
+ foreach (var sampler in samplers)
+ {
+ if (sampler.IsValid && sampler.IsShaderVisible)
+ {
+ // Use sampler descriptor in shaders
+ }
+ }
+ }
+}
+
+///
+/// Example texture class showing descriptor integration.
+///
+internal class ExampleTexture2D : IDisposable
+{
+ private readonly DescriptorAllocator _descriptorAllocator;
+ private readonly ShaderResourceDescriptor _srvDescriptor;
+ private readonly uint _width;
+ private readonly uint _height;
+ private readonly Format _format;
+ private bool _disposed;
+
+ public ExampleTexture2D(uint width, uint height, Format format, DescriptorAllocator descriptorAllocator)
+ {
+ _width = width;
+ _height = height;
+ _format = format;
+ _descriptorAllocator = descriptorAllocator;
+
+ // Allocate SRV descriptor for this texture
+ _srvDescriptor = _descriptorAllocator.AllocateSRV();
+
+ // Create the actual texture resource and SRV...
+ CreateTextureResource();
+ }
+
+ public ShaderResourceDescriptor GetShaderResourceView() => _srvDescriptor;
+
+ private void CreateTextureResource()
+ {
+ // Create D3D12 texture resource
+ // Create SRV using the allocated descriptor
+
+ // For D3D12:
+ if (_srvDescriptor is ShaderResourceDescriptor d3d12Srv)
+ {
+ var cpuHandle = d3d12Srv.CpuHandle;
+ // device.CreateShaderResourceView(textureResource, &srvDesc, cpuHandle);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ // Release the descriptor
+ _descriptorAllocator.ReleaseSRV(_srvDescriptor);
+
+ // Dispose texture resource...
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj
index 33f5a54..dbf9d82 100644
--- a/Ghost.Graphics/Ghost.Graphics.csproj
+++ b/Ghost.Graphics/Ghost.Graphics.csproj
@@ -17,6 +17,8 @@
+
+
@@ -27,14 +29,20 @@
-
- ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll
-
..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll
+
+
True
diff --git a/Ghost.Graphics/GraphicsPipeline.cs b/Ghost.Graphics/GraphicsPipeline.cs
index da3ed0e..70c1720 100644
--- a/Ghost.Graphics/GraphicsPipeline.cs
+++ b/Ghost.Graphics/GraphicsPipeline.cs
@@ -1,196 +1,154 @@
-using Ghost.Core;
-using Ghost.Graphics.Contracts;
-using Ghost.Graphics.D3D12;
-using Ghost.Graphics.Data;
-using System.Runtime.CompilerServices;
+using Ghost.Graphics.D3D12;
namespace Ghost.Graphics;
public static class GraphicsPipeline
{
- internal const int _FRAME_COUNT = 2;
+ internal const uint _FRAME_COUNT = 2;
- private static IGraphicsDevice? _graphicsDevice;
- private static IResourceAllocator? _resourceAllocator;
-
- private static Thread? _renderThread;
- private static AutoResetEvent[]? _cpuReadyEvent;
- private static AutoResetEvent[]? _gpuReadyEvent;
-
- private static uint _cpuFenceValue;
- private static uint _gpuFenceValue;
-
- private static bool _initialized;
- private static bool _isRunning;
-
- internal static uint CPUFenceValue => _cpuFenceValue;
- internal static uint GPUFenceValue => _gpuFenceValue;
- internal static bool IsRunning => _isRunning;
-
- internal static IGraphicsDevice GraphicsDevice
+ private readonly struct FrameResource : IDisposable
{
- get
- {
- if (_graphicsDevice == null)
- {
- throw new InvalidOperationException("Graphics pipeline is not initialized.");
- }
+ public readonly AutoResetEvent cpuReadyEvent;
+ public readonly AutoResetEvent gpuReadyEvent;
- return _graphicsDevice;
+ public FrameResource()
+ {
+ cpuReadyEvent = new(false);
+ gpuReadyEvent = new(true);
+ }
+
+ public void Dispose()
+ {
+ cpuReadyEvent?.Dispose();
+ gpuReadyEvent?.Dispose();
}
}
- internal static IResourceAllocator ResourceAllocator
+ private static GraphicsDevice? s_graphicsDevice;
+ private static DescriptorAllocator? s_descriptorAllocator;
+ private static ResourceAllocator? s_resourceAllocator;
+
+ private static Thread? s_renderThread;
+ private static FrameResource[]? s_frameResources;
+
+ private static uint s_frameIndex;
+ private static uint s_cpuFenceValue;
+ private static uint s_gpuFenceValue;
+
+ private static bool s_initialized;
+ private static bool s_isRunning;
+
+ internal static uint CPUFenceValue => s_cpuFenceValue;
+ internal static uint GPUFenceValue => s_gpuFenceValue;
+ internal static bool IsRunning => s_isRunning;
+
+ internal static GraphicsDevice GraphicsDevice => s_graphicsDevice ?? throw new InvalidOperationException("Graphics device is not initialized.");
+ internal static ResourceAllocator 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 void Initialize()
{
- get
- {
- if (_resourceAllocator == null)
- {
- throw new InvalidOperationException("Resource allocator is not initialized.");
- }
+ s_graphicsDevice = new GraphicsDevice();
+ s_descriptorAllocator = new DescriptorAllocator();
+ s_resourceAllocator = new ResourceAllocator();
- return _resourceAllocator;
- }
- }
-
- public static GraphicsAPI CurrentAPI
- {
- get;
- private set;
- }
-
- internal static void Initialize(GraphicsAPI api)
- {
- switch (api)
- {
- case GraphicsAPI.D3D12:
- _graphicsDevice = new D3D12GraphicsDevice();
- _resourceAllocator = new D3D12ResourceAllocator();
- break;
- default:
- throw new NotSupportedException($"Graphics API {api} is not supported.");
- }
-
- _renderThread = new Thread(RenderLoop)
+ s_renderThread = new Thread(RenderLoop)
{
IsBackground = true,
Name = "Graphics Render Thread",
Priority = ThreadPriority.Normal
};
- _cpuReadyEvent = new AutoResetEvent[_FRAME_COUNT];
- _gpuReadyEvent = new AutoResetEvent[_FRAME_COUNT];
+ s_frameResources = new FrameResource[_FRAME_COUNT];
for (var i = 0; i < _FRAME_COUNT; i++)
{
- _cpuReadyEvent[i] = new(false);
- _gpuReadyEvent[i] = new(true);
+ s_frameResources[i] = new FrameResource();
}
- CurrentAPI = api;
-
- _initialized = true;
+ s_initialized = true;
}
private static void RenderLoop()
{
- while (_isRunning)
+ while (s_isRunning)
{
- if (_graphicsDevice == null)
- {
- throw new ArgumentException("Renderer has been disposed or is not initialized.");
- }
+ s_frameIndex = s_gpuFenceValue % _FRAME_COUNT;
+ var frameResource = s_frameResources![s_frameIndex];
- var eventIndex = (int)(_gpuFenceValue % _FRAME_COUNT);
- _cpuReadyEvent![eventIndex].WaitOne();
+ frameResource.cpuReadyEvent.WaitOne();
- _graphicsDevice.InitializePendingRenderers();
+ s_graphicsDevice!.InitializePendingRenderers();
- foreach (var renderer in _graphicsDevice.Renderers)
+ foreach (var renderer in s_graphicsDevice.Renderers)
{
renderer.ExecutePendingResize();
renderer.Render();
}
- _gpuFenceValue++;
- _gpuReadyEvent![eventIndex].Set();
+ s_gpuFenceValue++;
+ frameResource.gpuReadyEvent.Set();
- _resourceAllocator!.ReleaseTempResource();
+ s_resourceAllocator!.ReleaseTempResource();
}
}
-
internal static bool WaitForGPUReady(int timeOut = -1)
{
- if (_gpuReadyEvent == null)
+ if (s_frameResources == null)
{
throw new InvalidOperationException("Graphics pipeline is not initialized.");
}
- var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
- return _gpuReadyEvent[eventIndex].WaitOne(timeOut);
+ var eventIndex = (int)(s_cpuFenceValue % _FRAME_COUNT);
+ return s_frameResources[eventIndex].gpuReadyEvent.WaitOne(timeOut);
}
internal static void SignalCPUReady()
{
- if (_cpuReadyEvent == null)
+ if (s_frameResources == null)
{
throw new InvalidOperationException("Graphics pipeline is not initialized.");
}
- var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
- _cpuFenceValue++;
- _cpuReadyEvent[eventIndex].Set();
+ var eventIndex = (int)(s_cpuFenceValue % _FRAME_COUNT);
+ s_cpuFenceValue++;
+ s_frameResources[eventIndex].cpuReadyEvent.Set();
}
internal static void Start()
{
- if (_isRunning || !_initialized)
+ if (s_isRunning || !s_initialized)
{
return;
}
- _isRunning = true;
- _renderThread!.Start();
+ s_isRunning = true;
+ s_renderThread!.Start();
}
internal static void Stop()
{
- _isRunning = false;
- _renderThread?.Join();
+ s_isRunning = false;
+ s_renderThread?.Join();
}
internal static void Shutdown()
{
Stop();
- _graphicsDevice?.Dispose();
- _resourceAllocator?.Dispose();
+ s_resourceAllocator?.Dispose();
+ s_descriptorAllocator?.Dispose();
+ s_graphicsDevice?.Dispose();
- _graphicsDevice = null;
- _renderThread = null;
- _initialized = false;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static T GetGraphicsDevice()
- where T : class, IGraphicsDevice
- {
- if (T.TargetAPI != CurrentAPI)
+ if (s_frameResources != null)
{
- throw new InvalidOperationException($"No graphics device of type {typeof(T)} available for the current API.");
+ foreach (var frameResource in s_frameResources)
+ {
+ frameResource.Dispose();
+ }
+ s_frameResources = null;
}
- return Unsafe.As(GraphicsDevice);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Result CheckAPI(GraphicsAPI expectedAPI)
- {
- if (CurrentAPI != expectedAPI)
- {
- return Result.Failure($"Expected API {expectedAPI}, but got {CurrentAPI}.");
- }
-
- return Result.Success();
+ s_initialized = false;
}
}
\ No newline at end of file
diff --git a/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs b/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs
new file mode 100644
index 0000000..6eb35fb
--- /dev/null
+++ b/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs
@@ -0,0 +1,677 @@
+using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
+using Ghost.Graphics.D3D12.Utilities;
+using Ghost.Graphics.Data;
+using Ghost.Graphics.Utilities;
+using StbImageSharp;
+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.RenderPasses;
+
+///
+/// Modern bindless texture implementation using SM 6.6 with ResourceDescriptorHeap
+/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
+///
+internal unsafe class BindlessMeshRenderPass : IRenderPass
+{
+ private const string _HLSL_SOURCE = @"
+cbuffer ConstantBuffer : register(b0)
+{
+ float4 _Color;
+ uint _TextureIndex1;
+ uint _TextureIndex2;
+ uint _TextureIndex3;
+ uint _TextureIndex4;
+};
+
+// SM 6.6 approach - no need for explicit texture arrays
+// ResourceDescriptorHeap is a global descriptor heap access mechanism
+// This allows direct indexing into any texture in the bound descriptor heap
+SamplerState _MainSampler : register(s0);
+
+struct VertexInput
+{
+ float4 position : POSITION;
+ float4 color : COLOR;
+ float4 uv : TEXCOORD0;
+};
+
+struct PixelInput
+{
+ float4 position : SV_POSITION;
+ float4 color : COLOR;
+ float4 uv : TEXCOORD0;
+};
+
+PixelInput VSMain(VertexInput input)
+{
+ PixelInput output;
+ output.position = input.position;
+ output.color = input.color;
+ output.uv = input.uv;
+ return output;
+}
+
+float4 PSMain(PixelInput input) : SV_TARGET
+{
+ // SM 6.6 Modern Bindless Approach:
+ // ResourceDescriptorHeap[index] directly accesses any texture in the heap
+ // No need for explicit texture arrays or descriptor table binding
+ // This is enabled by D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
+ Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
+ Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
+ Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
+ Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
+
+ // Sample the textures normally
+ float4 color1 = tex1.Sample(_MainSampler, input.uv.xy);
+ float4 color2 = tex2.Sample(_MainSampler, input.uv.xy);
+ float4 color3 = tex3.Sample(_MainSampler, input.uv.xy);
+ float4 color4 = tex4.Sample(_MainSampler, input.uv.xy);
+
+ // Blend all textures together (simple average)
+ float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
+
+ return blendedColor * _Color;
+}
+";
+
+ private Mesh? _mesh;
+
+ // Direct D3D12 resources for modern bindless implementation
+ private ComPtr _pipelineState;
+ private ComPtr _rootSignature;
+ private ComPtr _constantBuffer;
+ private void* _constantBufferMappedData;
+
+ // Bindless texture resources
+ private ComPtr[] _textureResources;
+ private ComPtr _bindlessHeap;
+ private ComPtr _samplerHeap;
+ private CpuDescriptorHandle _bindlessHeapStart;
+ private GpuDescriptorHandle _bindlessHeapGpuStart;
+ private uint _descriptorSize;
+
+ // Texture upload resources
+ private ComPtr[] _uploadBuffers;
+ private uint[] _textureWidths;
+ private uint[] _textureHeights;
+ private uint[] _texturePitches;
+ private bool _texturesUploaded = false;
+
+ // Texture file paths for this demo
+ private readonly string[] _textureFiles = [
+ "C:/Users/Misaki/Downloads/Im/Icon.png",
+ "C:/Users/Misaki/Downloads/Im/Backdrop.jpg",
+ "C:/Users/Misaki/Downloads/Im/101167591_p0.png",
+ "C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
+ ];
+
+ // Constant buffer data structure
+ private struct ConstantBufferData
+ {
+ public System.Numerics.Vector4 Color;
+ public uint TextureIndex1;
+ public uint TextureIndex2;
+ public uint TextureIndex3;
+ public uint TextureIndex4;
+ }
+
+ public void Initialize(CommandList cmd)
+ {
+ _mesh = MeshBuilder.CreateCube(0.75f);
+ _mesh.UploadMeshData();
+
+ CreateModernBindlessShader();
+ CreateConstantBuffer();
+ CreateBindlessTextures();
+ UploadTextureData(cmd);
+ UpdateConstantBuffer();
+ }
+
+ private void CreateModernBindlessShader()
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ var vsCode = CompileShaderDXC(_HLSL_SOURCE, "VSMain", "vs_6_6");
+ var psCode = CompileShaderDXC(_HLSL_SOURCE, "PSMain", "ps_6_6");
+
+ CreateModernBindlessRootSignature();
+ CreatePipelineState(vsCode, psCode);
+ }
+
+ private byte[] CompileShaderDXC(string source, string entryPoint, string profile)
+ {
+ using ComPtr compiler = default;
+ using ComPtr utils = default;
+
+ // Create DXC compiler and utils
+ DxcCreateInstance(CLSID_DxcCompiler, __uuidof(), compiler.GetVoidAddressOf());
+ DxcCreateInstance(CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf());
+
+ // Create source blob
+ using ComPtr 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
+ 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 for release
+ "-Qstrip_reflect" // Strip reflection info for release
+ };
+
+ // 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 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(), result.GetVoidAddressOf());
+ }
+
+ // Check compilation result
+ HResult hrStatus;
+ result.Get()->GetStatus(&hrStatus);
+ if (hrStatus.Failure)
+ {
+ // Get error messages
+ using ComPtr 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 bytecodeBlob = default;
+ result.Get()->GetResult(bytecodeBlob.GetAddressOf());
+
+ if (bytecodeBlob.Get() == null)
+ {
+ throw new Exception("DXC compilation succeeded but no bytecode 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 bytecode;
+ }
+ finally
+ {
+ // Free allocated wide strings
+ for (var i = 0; i < argPointers.Length; i++)
+ {
+ Marshal.FreeHGlobal(argPointers[i]);
+ }
+ }
+ }
+
+ private void CreateModernBindlessRootSignature()
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ // Modern approach: Only CBV + Sampler, no descriptor tables needed
+ // The CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED flag allows direct heap access
+ var rootParameters = new RootParameter1[2];
+
+ // CBV for constant buffer
+ rootParameters[0] = new RootParameter1
+ {
+ ParameterType = RootParameterType.Cbv,
+ ShaderVisibility = ShaderVisibility.All,
+ Descriptor = new RootDescriptor1(0, 0), // b0
+ };
+
+ // 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[1] = 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 signature = default;
+ using ComPtr error = default;
+
+ D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
+
+ device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf());
+ }
+ }
+
+ private void CreatePipelineState(byte[] vsCode, byte[] psCode)
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ fixed (byte* vsPtr = vsCode)
+ fixed (byte* psPtr = psCode)
+ {
+
+ var psoDesc = new GraphicsPipelineStateDescription
+ {
+ pRootSignature = _rootSignature.Get(),
+ VS = new ShaderBytecode(vsPtr, (nuint)vsCode.Length),
+ PS = new ShaderBytecode(psPtr, (nuint)psCode.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,
+ };
+
+ // Fix: Use the correct swap chain back buffer format
+ psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
+
+ device->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf());
+ }
+ }
+
+ private void CreateConstantBuffer()
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ // Create constant buffer
+ var cbSize = (uint)((sizeof(ConstantBufferData) + 255) & ~255); // Align to 256 bytes
+ var heapProps = new HeapProperties(HeapType.Upload);
+ var bufferDesc = ResourceDescription.Buffer(cbSize);
+
+ device->CreateCommittedResource(
+ &heapProps,
+ HeapFlags.None,
+ &bufferDesc,
+ ResourceStates.GenericRead,
+ null,
+ __uuidof(),
+ _constantBuffer.GetVoidAddressOf()
+ );
+
+ // Map constant buffer
+ fixed (void** mappedDataPtr = &_constantBufferMappedData)
+ {
+ _constantBuffer.Get()->Map(0, null, mappedDataPtr);
+ }
+ }
+
+ private void UpdateConstantBuffer()
+ {
+ var cbData = new ConstantBufferData
+ {
+ Color = new(1.0f, 1.0f, 1.0f, 1.0f),
+ TextureIndex1 = 0,
+ TextureIndex2 = 1,
+ TextureIndex3 = 2,
+ TextureIndex4 = 3
+ };
+
+ *(ConstantBufferData*)_constantBufferMappedData = cbData;
+ }
+
+ private void CreateBindlessTextures()
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+ var textureCount = _textureFiles.Length;
+
+ // Initialize arrays
+ _textureResources = new ComPtr[textureCount];
+ _uploadBuffers = new ComPtr[textureCount];
+ _textureWidths = new uint[textureCount];
+ _textureHeights = new uint[textureCount];
+ _texturePitches = new uint[textureCount];
+
+ // Create bindless descriptor heap
+ var bindlessHeapDesc = new DescriptorHeapDescription
+ {
+ Type = DescriptorHeapType.CbvSrvUav,
+ NumDescriptors = 1000,
+ Flags = DescriptorHeapFlags.ShaderVisible
+ };
+
+ device->CreateDescriptorHeap(&bindlessHeapDesc, __uuidof(), _bindlessHeap.GetVoidAddressOf());
+
+ // Create sampler heap
+ var samplerHeapDesc = new DescriptorHeapDescription
+ {
+ Type = DescriptorHeapType.Sampler,
+ NumDescriptors = 1,
+ Flags = DescriptorHeapFlags.ShaderVisible
+ };
+
+ device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof(), _samplerHeap.GetVoidAddressOf());
+
+ // Get descriptor handles
+ _bindlessHeapStart = _bindlessHeap.Get()->GetCPUDescriptorHandleForHeapStart();
+ _bindlessHeapGpuStart = _bindlessHeap.Get()->GetGPUDescriptorHandleForHeapStart();
+ _descriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
+
+ // Create 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);
+
+ // Load and create textures
+ for (var i = 0; i < textureCount; i++)
+ {
+ CreateTextureResource(i);
+ }
+ }
+
+ private void CreateTextureResource(int index)
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ // Load image data
+ using var stream = new FileStream(_textureFiles[index], FileMode.Open, FileAccess.Read);
+ var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
+
+ var width = (uint)image.Width;
+ var height = (uint)image.Height;
+ uint bytesPerPixel = 4;
+ var pitch = width * bytesPerPixel;
+
+ // Store texture dimensions
+ _textureWidths[index] = width;
+ _textureHeights[index] = height;
+ _texturePitches[index] = (pitch + 255) & ~255u;
+
+ // Create texture resource
+ var textureDesc = new ResourceDescription
+ {
+ Dimension = ResourceDimension.Texture2D,
+ Alignment = 0,
+ Width = width,
+ Height = height,
+ DepthOrArraySize = 1,
+ MipLevels = 1,
+ Format = Format.R8G8B8A8Unorm,
+ SampleDesc = new SampleDescription(1, 0),
+ Layout = TextureLayout.Unknown,
+ Flags = ResourceFlags.None
+ };
+
+ var heapProps = new HeapProperties(HeapType.Default);
+ device->CreateCommittedResource(
+ &heapProps,
+ HeapFlags.None,
+ &textureDesc,
+ ResourceStates.CopyDest,
+ null,
+ __uuidof(),
+ _textureResources[index].GetVoidAddressOf()
+ );
+
+ // Create upload buffer
+ var uploadBufferSize = GetRequiredIntermediateSize(_textureResources[index].Get(), 0, 1);
+ var uploadHeapProps = new HeapProperties(HeapType.Upload);
+ var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize);
+
+ device->CreateCommittedResource(
+ &uploadHeapProps,
+ HeapFlags.None,
+ &uploadBufferDesc,
+ ResourceStates.GenericRead,
+ null,
+ __uuidof(),
+ _uploadBuffers[index].GetVoidAddressOf()
+ );
+
+ // Map and copy texture data
+ void* mappedData = null;
+ _uploadBuffers[index].Get()->Map(0, null, &mappedData);
+
+ var srcData = image.Data.AsSpan();
+ var dstSpan = new Span(mappedData, (int)uploadBufferSize);
+
+ // Copy row by row with proper alignment
+ for (var y = 0; y < height; y++)
+ {
+ var srcRow = srcData.Slice(y * (int)pitch, (int)pitch);
+ var dstRow = dstSpan.Slice(y * (int)_texturePitches[index], (int)pitch);
+ srcRow.CopyTo(dstRow);
+ }
+
+ _uploadBuffers[index].Get()->Unmap(0, null);
+
+ // Create SRV in the bindless heap
+ var srvDesc = new ShaderResourceViewDescription
+ {
+ Format = Format.R8G8B8A8Unorm,
+ ViewDimension = Win32.Graphics.Direct3D12.SrvDimension.Texture2D,
+ Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
+ Texture2D = new Texture2DSrv
+ {
+ MostDetailedMip = 0,
+ MipLevels = 1,
+ PlaneSlice = 0,
+ ResourceMinLODClamp = 0.0f
+ }
+ };
+
+ // Calculate SRV handle for this texture
+ var srvHandle = new CpuDescriptorHandle
+ {
+ ptr = _bindlessHeapStart.ptr + (nuint)(index * _descriptorSize)
+ };
+
+ device->CreateShaderResourceView(_textureResources[index].Get(), &srvDesc, srvHandle);
+ }
+
+ private void UploadTextureData(CommandList cmd)
+ {
+ var commandList = cmd.NativeCommandList.Ptr;
+
+ for (var i = 0; i < _textureResources.Length; i++)
+ {
+ // Copy from upload buffer to texture
+ var srcLocation = new TextureCopyLocation(_uploadBuffers[i].Get(), new PlacedSubresourceFootprint
+ {
+ Offset = 0,
+ Footprint = new SubresourceFootprint
+ {
+ Format = Format.R8G8B8A8Unorm,
+ Width = _textureWidths[i],
+ Height = _textureHeights[i],
+ Depth = 1,
+ RowPitch = _texturePitches[i]
+ }
+ });
+
+ var dstLocation = new TextureCopyLocation(_textureResources[i].Get(), 0);
+
+ commandList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
+
+ // Transition texture to shader resource
+ var barrier = new ResourceBarrier
+ {
+ Type = ResourceBarrierType.Transition,
+ Flags = ResourceBarrierFlags.None,
+ Transition = new ResourceTransitionBarrier
+ {
+ pResource = _textureResources[i].Get(),
+ StateBefore = ResourceStates.CopyDest,
+ StateAfter = ResourceStates.PixelShaderResource,
+ Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES
+ }
+ };
+ commandList->ResourceBarrier(1, &barrier);
+ }
+
+ _texturesUploaded = true;
+ }
+
+ private static ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+ var resourceDesc = destinationResource->GetDesc();
+
+ ulong requiredSize = 0;
+ device->GetCopyableFootprints(&resourceDesc, firstSubresource, numSubresources, 0, null, null, null, &requiredSize);
+
+ return requiredSize;
+ }
+
+ public void Execute(CommandList cmd)
+ {
+ var commandList = cmd.NativeCommandList.Ptr;
+
+ // Set root signature and pipeline state
+ commandList->SetGraphicsRootSignature(_rootSignature.Get());
+ commandList->SetPipelineState(_pipelineState.Get());
+
+ // Set descriptor heaps - this is crucial for modern bindless
+ var heaps = stackalloc ID3D12DescriptorHeap*[2];
+ heaps[0] = _bindlessHeap.Get(); // CBV_SRV_UAV heap for direct indexing
+ heaps[1] = _samplerHeap.Get(); // Sampler heap
+ commandList->SetDescriptorHeaps(2, heaps);
+
+ // Bind constant buffer
+ commandList->SetGraphicsRootConstantBufferView(0, _constantBuffer.Get()->GetGPUVirtualAddress());
+
+ // Bind sampler descriptor table
+ var samplerGpuHandle = _samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart();
+ commandList->SetGraphicsRootDescriptorTable(1, samplerGpuHandle);
+
+ // No need to explicitly bind texture descriptor table!
+ // The textures are accessed directly via ResourceDescriptorHeap[index]
+
+ // Draw mesh
+ commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
+ commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView);
+ commandList->IASetIndexBuffer(_mesh.IndexBufferView);
+ commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0);
+ }
+
+ public void Dispose()
+ {
+ _mesh?.Dispose();
+
+ // Unmap constant buffer
+ if (_constantBuffer.Get() != null)
+ {
+ _constantBuffer.Get()->Unmap(0, null);
+ }
+
+ _pipelineState.Dispose();
+ _rootSignature.Dispose();
+ _constantBuffer.Dispose();
+
+ // Dispose texture resources
+ if (_textureResources != null)
+ {
+ for (var i = 0; i < _textureResources.Length; i++)
+ {
+ _textureResources[i].Dispose();
+ }
+ }
+
+ // Dispose upload buffers
+ if (_uploadBuffers != null)
+ {
+ for (var i = 0; i < _uploadBuffers.Length; i++)
+ {
+ _uploadBuffers[i].Dispose();
+ }
+ }
+
+ _bindlessHeap.Dispose();
+ _samplerHeap.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs
index dd2cc73..1d45f50 100644
--- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs
+++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs
@@ -1,9 +1,13 @@
using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data;
using Ghost.Graphics.Shading;
using Ghost.Graphics.Utilities;
-using System.Drawing;
-using System.Numerics;
+using StbImageSharp;
+using Win32;
+using Win32.Graphics.Direct3D;
+using Win32.Graphics.Direct3D12;
+using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RenderPasses;
@@ -15,29 +19,35 @@ cbuffer ConstantBuffer : register(b0)
float4 _Color;
};
+Texture2D _MainTex : register(t0);
+SamplerState _MainSampler : register(s0);
+
struct VertexInput
{
- float3 position : POSITION;
+ float4 position : POSITION;
float4 color : COLOR;
+ float4 uv : TEXCOORD0;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
+ float4 uv : TEXCOORD0;
};
PixelInput VSMain(VertexInput input)
{
PixelInput output;
- output.position = float4(input.position, 1.0f);
+ output.position = input.position;
output.color = input.color;
+ output.uv = input.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
- return float4(_Color.xyz, 1.0);
+ return _MainTex.Sample(_MainSampler, input.uv.xy);
}
";
@@ -45,21 +55,218 @@ float4 PSMain(PixelInput input) : SV_TARGET
private Shader? _shader;
private Material? _material;
- public void Initialize(ICommandBuffer cmb)
+ // Direct D3D12 resources for texture
+ private ComPtr _textureResource;
+ private ComPtr _srvHeap;
+ private CpuDescriptorHandle _srvHandle;
+ private GpuDescriptorHandle _srvGpuHandle;
+ private uint _srvDescriptorSize;
+
+ // Additional fields for deferred texture upload
+ private ComPtr _uploadBuffer;
+ private uint _textureWidth;
+ private uint _textureHeight;
+ private uint _texturePitch;
+ private bool _textureUploaded = false;
+
+ public void Initialize(CommandList cmd)
{
_mesh = MeshBuilder.CreateCube(0.25f);
- _mesh.UploadMeshData(cmb);
+ _mesh.UploadMeshData();
_shader = new(_HLSL_SOURCE);
_material = new(_shader);
- var color = new Vector4(Color.Brown.R / 255f, Color.Brown.G / 255f, Color.Brown.B / 255f, 1.0f);
- _material.SetVector("_Color", ref color);
+ // Create direct D3D12 texture resources
+ CreateTextureDirectly();
+
+ _material.UploadMaterialData();
+
+ // Copy from upload buffer to texture
+ var srcLocation = new TextureCopyLocation(_uploadBuffer.Get(), new PlacedSubresourceFootprint
+ {
+ Offset = 0,
+ Footprint = new SubresourceFootprint
+ {
+ Format = Format.R8G8B8A8Unorm,
+ Width = _textureWidth,
+ Height = _textureHeight,
+ Depth = 1,
+ RowPitch = _texturePitch
+ }
+ });
+
+ var dstLocation = new TextureCopyLocation(_textureResource.Get(), 0);
+
+ cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
+
+ // Transition texture to shader resource
+ var barrier = new ResourceBarrier
+ {
+ Type = ResourceBarrierType.Transition,
+ Flags = ResourceBarrierFlags.None,
+ Transition = new ResourceTransitionBarrier
+ {
+ pResource = _textureResource.Get(),
+ StateBefore = ResourceStates.CopyDest,
+ StateAfter = ResourceStates.PixelShaderResource,
+ Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES
+ }
+ };
+ cmd.NativeCommandList.Ptr->ResourceBarrier(1, &barrier);
}
- public void Execute(ICommandBuffer cmb)
+ private void CreateTextureDirectly()
{
- cmb.DrawMesh(_mesh!, _material!);
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+
+ // Load image data
+ using var stream = new FileStream("C:/Users/Misaki/Downloads/Im/Icon.png", FileMode.Open, FileAccess.Read);
+ var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
+
+ var width = (uint)image.Width;
+ var height = (uint)image.Height;
+ uint bytesPerPixel = 4; // RGBA
+ var pitch = width * bytesPerPixel;
+ var textureSize = pitch * height;
+
+ // Create the texture resource
+ var textureDesc = new ResourceDescription
+ {
+ Dimension = ResourceDimension.Texture2D,
+ Alignment = 0,
+ Width = width,
+ Height = height,
+ DepthOrArraySize = 1,
+ MipLevels = 1,
+ Format = Format.R8G8B8A8Unorm,
+ SampleDesc = new SampleDescription(1, 0),
+ Layout = TextureLayout.Unknown,
+ Flags = ResourceFlags.None
+ };
+
+ var heapProps = new HeapProperties(HeapType.Default);
+ device->CreateCommittedResource(
+ &heapProps,
+ HeapFlags.None,
+ &textureDesc,
+ ResourceStates.CopyDest,
+ null,
+ __uuidof(),
+ _textureResource.GetVoidAddressOf()
+ );
+
+ // Create upload buffer
+ var uploadBufferSize = GetRequiredIntermediateSize(_textureResource.Get(), 0, 1);
+ var uploadHeapProps = new HeapProperties(HeapType.Upload);
+ var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize);
+
+ ComPtr uploadBuffer = default;
+ device->CreateCommittedResource(
+ &uploadHeapProps,
+ HeapFlags.None,
+ &uploadBufferDesc,
+ ResourceStates.GenericRead,
+ null,
+ __uuidof(),
+ uploadBuffer.GetVoidAddressOf()
+ );
+
+ // Map and copy texture data
+ void* mappedData = null;
+ uploadBuffer.Get()->Map(0, null, &mappedData);
+
+ // Copy image data to upload buffer
+ var srcData = image.Data.AsSpan();
+ var dstSpan = new Span(mappedData, (int)uploadBufferSize);
+
+ // Copy row by row with proper alignment
+ var alignedPitch = (pitch + 255) & ~255u; // Align to 256 bytes
+ for (var y = 0; y < height; y++)
+ {
+ var srcRow = srcData.Slice(y * (int)pitch, (int)pitch);
+ var dstRow = dstSpan.Slice(y * (int)alignedPitch, (int)pitch);
+ srcRow.CopyTo(dstRow);
+ }
+
+ uploadBuffer.Get()->Unmap(0, null);
+
+ // We'll copy the texture data during Execute phase when we have access to command list
+ // Store the upload buffer for later use
+ _uploadBuffer = uploadBuffer.Move();
+ _textureWidth = width;
+ _textureHeight = height;
+ _texturePitch = alignedPitch;
+
+ // Create SRV descriptor heap
+ var srvHeapDesc = new DescriptorHeapDescription
+ {
+ Type = DescriptorHeapType.CbvSrvUav,
+ NumDescriptors = 1,
+ Flags = DescriptorHeapFlags.ShaderVisible
+ };
+
+ device->CreateDescriptorHeap(&srvHeapDesc, __uuidof(), _srvHeap.GetVoidAddressOf());
+
+ // Get descriptor handles
+ _srvHandle = _srvHeap.Get()->GetCPUDescriptorHandleForHeapStart();
+ _srvGpuHandle = _srvHeap.Get()->GetGPUDescriptorHandleForHeapStart();
+ _srvDescriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
+
+ // Create SRV
+ var srvDesc = new ShaderResourceViewDescription
+ {
+ Format = Format.R8G8B8A8Unorm,
+ ViewDimension = Win32.Graphics.Direct3D12.SrvDimension.Texture2D,
+ Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
+ Texture2D = new Texture2DSrv
+ {
+ MostDetailedMip = 0,
+ MipLevels = 1,
+ PlaneSlice = 0,
+ ResourceMinLODClamp = 0.0f
+ }
+ };
+
+ device->CreateShaderResourceView(_textureResource.Get(), &srvDesc, _srvHandle);
+ }
+
+ private static ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
+ {
+ var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
+ var resourceDesc = destinationResource->GetDesc();
+
+ ulong requiredSize = 0;
+ device->GetCopyableFootprints(&resourceDesc, firstSubresource, numSubresources, 0, null, null, null, &requiredSize);
+
+ return requiredSize;
+ }
+
+ public void Execute(CommandList cmd)
+ {
+ var commandList = cmd.NativeCommandList.Ptr;
+
+ // Set root signature and pipeline state
+ commandList->SetGraphicsRootSignature(_material!.Shader.RootSignature);
+ commandList->SetPipelineState(_material.Shader.PipelineState);
+
+ // Set descriptor heap
+ var heaps = stackalloc ID3D12DescriptorHeap*[1];
+ heaps[0] = _srvHeap.Get();
+ commandList->SetDescriptorHeaps(1, heaps);
+
+ // Bind texture descriptor table directly
+ if (_material.Shader.Textures.Count > 0)
+ {
+ var textureInfo = _material.Shader.Textures[0];
+ commandList->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, _srvGpuHandle);
+ }
+
+ // Draw mesh
+ commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
+ commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView);
+ commandList->IASetIndexBuffer(_mesh.IndexBufferView);
+ commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0);
}
public void Dispose()
@@ -67,5 +274,9 @@ float4 PSMain(PixelInput input) : SV_TARGET
_mesh?.Dispose();
_shader?.Dispose();
_material?.Dispose();
+
+ _textureResource.Dispose();
+ _uploadBuffer.Dispose();
+ _srvHeap.Dispose();
}
}
diff --git a/Ghost.Graphics/Shading/CBufferCache.cs b/Ghost.Graphics/Shading/CBufferCache.cs
index 0d8c895..bd6f11d 100644
--- a/Ghost.Graphics/Shading/CBufferCache.cs
+++ b/Ghost.Graphics/Shading/CBufferCache.cs
@@ -1,4 +1,4 @@
-using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
@@ -10,9 +10,10 @@ internal struct CBufferCache : IDisposable
public UnsafeArray CpuData
{
get;
+ set;
}
- public IResource GpuResource
+ public GraphicsResource GpuResource
{
get;
}
@@ -21,9 +22,9 @@ internal struct CBufferCache : IDisposable
public unsafe CBufferCache(uint bufferSize)
{
- CpuData = new((int)bufferSize, Allocator.Persistent);
-
_alignedSize = (bufferSize + 255u) & ~255u;
+
+ CpuData = new((int)_alignedSize, Allocator.Persistent);
GpuResource = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(_alignedSize);
GpuResource.Name = "Material_CBufferCache";
diff --git a/Ghost.Graphics/Shading/Shader.cs b/Ghost.Graphics/Shading/Shader.cs
index 4b799bf..3262483 100644
--- a/Ghost.Graphics/Shading/Shader.cs
+++ b/Ghost.Graphics/Shading/Shader.cs
@@ -1,7 +1,7 @@
using Ghost.Core;
-using Ghost.Graphics.D3D12;
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel.Helpers;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -13,6 +13,24 @@ 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
@@ -20,7 +38,7 @@ internal readonly struct PropertyInfo
get; init;
}
- public required string CBufferName
+ public uint CBufferIndex
{
get; init;
}
@@ -62,23 +80,28 @@ public unsafe class Shader : IDisposable
private readonly byte[] _vertexShaderBytecode;
private readonly byte[] _pixelShaderBytecode;
- private readonly Dictionary _constantBuffers = new();
- private readonly Dictionary _properties = new();
+ private readonly List _constantBuffers = new();
+ private readonly List _properties = new();
+ private readonly Dictionary _propertyNameToIdMap = new();
+
+ private readonly List _textures = new();
+ private readonly Dictionary _textureNameToIdMap = new();
private bool _disposed;
internal ConstPtr PipelineState => new(_pipelineState.Get());
internal ConstPtr RootSignature => new(_rootSignature.Get());
- internal IReadOnlyDictionary ConstantBuffers => _constantBuffers;
- internal IReadOnlyDictionary Properties => _properties;
+ internal IReadOnlyList ConstantBuffers => _constantBuffers;
+ internal IReadOnlyList Properties => _properties;
+ internal IReadOnlyList Textures => _textures;
//public Shader(string shaderPath)
//{
//}
- public Shader(string shaderCode)
+ internal Shader(string shaderCode)
{
_vertexShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "VSMain", "vs_5_0");
_pixelShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "PSMain", "ps_5_0");
@@ -103,7 +126,7 @@ public unsafe class Shader : IDisposable
/// The shader model to target (e.g., "vs_5_0", "ps_5_0").
/// A byte array containing the compiled shader bytecode.
/// Thrown if shader compilation fails.
- public static unsafe byte[] CompileShader(byte[] sourceCodeBytes, string entryPoint, string shaderProfile)
+ public static unsafe byte[] CompileShader(ReadOnlySpan sourceCodeBytes, string entryPoint, string shaderProfile)
{
using ComPtr bytecodeBlob = default;
using ComPtr errorBlob = default;
@@ -111,29 +134,14 @@ public unsafe class Shader : IDisposable
var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint);
var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile);
- // Call the D3DCompile function
- var hr = D3DCompile(
- sourceCodeBytes.AsSpan(),
+ ThrowIfFailed(D3DCompile(
+ sourceCodeBytes,
entryPointBytes.AsSpan(),
shaderProfileBytes.AsSpan(),
CompileFlags.EnableStrictness | CompileFlags.Debug,
bytecodeBlob.GetAddressOf(),
errorBlob.GetAddressOf()
- );
-
- if (hr.Failure)
- {
- var errorMessage = "Shader compilation failed.";
- if (errorBlob.Get() is not null)
- {
- errorMessage += "\n" + Encoding.ASCII.GetString(
- (byte*)errorBlob.Get()->GetBufferPointer(),
- (int)errorBlob.Get()->GetBufferSize()
- );
- }
-
- throw new Exception(errorMessage);
- }
+ ));
var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()];
Unsafe.CopyBlock(bytecode.AsSpan().GetPointer(), bytecodeBlob.Get()->GetBufferPointer(), (uint)bytecode.Length);
@@ -143,11 +151,18 @@ public unsafe class Shader : IDisposable
private void CreateRootSignature()
{
- var rootParameters = new RootParameter1[_constantBuffers.Values.Count];
+ // Calculate total root parameters: CBVs + 1 descriptor table for SRVs (if any textures)
+ var totalRootParameters = _constantBuffers.Count + (_textures.Count > 0 ? 1 : 0);
+ var rootParameters = new RootParameter1[totalRootParameters];
- var i = 0;
- foreach (var cbufferInfo in _constantBuffers.Values)
+ var parameterIndex = 0;
+
+ // Add CBV root parameters
+ for (var i = 0; i < _constantBuffers.Count; i++)
{
+ var cbufferInfo = _constantBuffers[i];
+ Debug.Assert(i == cbufferInfo.RegisterSlot);
+
var rootParameter = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
@@ -155,10 +170,68 @@ public unsafe class Shader : IDisposable
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
};
- rootParameters[i++] = rootParameter;
+ rootParameters[parameterIndex++] = rootParameter;
}
- var rootSignatureDesc = new RootSignatureDescription((uint)rootParameters.Length, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0]))
+ // Add descriptor table for SRVs if we have textures
+ if (_textures.Count > 0)
+ {
+ var ranges = new DescriptorRange1[1];
+ ranges[0] = new DescriptorRange1
+ {
+ RangeType = DescriptorRangeType.Srv,
+ NumDescriptors = (uint)_textures.Count,
+ BaseShaderRegister = 0, // Start from t0
+ RegisterSpace = 0,
+ Flags = DescriptorRangeFlags.None,
+ OffsetInDescriptorsFromTableStart = 0
+ };
+
+ fixed (DescriptorRange1* rangesPtr = ranges)
+ {
+ var rootParameter = new RootParameter1
+ {
+ ParameterType = RootParameterType.DescriptorTable,
+ ShaderVisibility = ShaderVisibility.All,
+ DescriptorTable = new RootDescriptorTable1(1, rangesPtr)
+ };
+
+ rootParameters[parameterIndex++] = rootParameter;
+ }
+ }
+
+ // Create static samplers for textures
+ var staticSamplers = new StaticSamplerDescription[_textures.Count];
+ for (var i = 0; i < _textures.Count; i++)
+ {
+ staticSamplers[i] = new StaticSamplerDescription
+ {
+ Filter = Filter.MinMagMipLinear,
+ AddressU = TextureAddressMode.Wrap,
+ AddressV = TextureAddressMode.Wrap,
+ AddressW = TextureAddressMode.Wrap,
+ MipLODBias = 0,
+ MaxAnisotropy = 1,
+ BorderColor = StaticBorderColor.OpaqueWhite,
+ MinLOD = 0,
+ MaxLOD = 0,
+ ShaderRegister = (uint)i, // s0, s1, etc.
+ RegisterSpace = 0,
+ ShaderVisibility = ShaderVisibility.All
+ };
+ }
+
+ var parameterCount = (uint)rootParameters.Length;
+ var parameters = parameterCount > 0
+ ? (RootParameter*)Unsafe.AsPointer(ref rootParameters[0])
+ : null;
+
+ var samplerCount = (uint)staticSamplers.Length;
+ var samplers = samplerCount > 0
+ ? (StaticSamplerDescription*)Unsafe.AsPointer(ref staticSamplers[0])
+ : null;
+
+ var rootSignatureDesc = new RootSignatureDescription(parameterCount, parameters, samplerCount, samplers)
{
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout
};
@@ -166,14 +239,9 @@ public unsafe class Shader : IDisposable
using ComPtr signature = default;
using ComPtr error = default;
- var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf());
- if (hr.Failure)
- {
- var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize());
- throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}");
- }
+ ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf()));
- GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf());
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf());
}
private void CreatePipelineStateObject()
@@ -202,7 +270,7 @@ public unsafe class Shader : IDisposable
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
// Create the PSO
- GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf());
+ GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf());
}
}
catch (Exception ex)
@@ -222,6 +290,9 @@ public unsafe class Shader : IDisposable
ShaderDescription shaderDesc;
reflection.Get()->GetDesc(&shaderDesc);
+ var cbufferRegistry = _constantBuffers.ToDictionary(cb => cb.Name);
+ var textureRegistry = _textures.ToDictionary(t => t.Name);
+
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
ShaderInputBindDescription bindDesc;
@@ -230,7 +301,7 @@ public unsafe class Shader : IDisposable
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
{
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
- if (cbufferName == null || _constantBuffers.ContainsKey(cbufferName))
+ if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
{
continue;
}
@@ -239,12 +310,13 @@ public unsafe class Shader : IDisposable
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
- _constantBuffers.Add(cbufferName, new CBufferInfo
+ var cbufferInfo = new CBufferInfo
{
Name = cbufferName,
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
- });
+ };
+ cbufferRegistry.Add(cbufferName, cbufferInfo);
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
@@ -253,24 +325,80 @@ public unsafe class Shader : IDisposable
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
- if (variableName == null || _properties.ContainsKey(variableName))
+ if (variableName == null || _propertyNameToIdMap.ContainsKey(variableName))
{
continue;
}
- _properties.Add(variableName, new PropertyInfo
+ var propInfo = new PropertyInfo
{
Name = variableName,
- CBufferName = cbufferName,
+ 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;
+ }
+
+ // The root parameter index for textures is after all CBVs
+ var textureInfo = new TextureInfo
+ {
+ Name = textureName,
+ RegisterSlot = bindDesc.BindPoint,
+ RootParameterIndex = (uint)_constantBuffers.Count // Descriptor table comes after CBVs
+ };
+
+ textureRegistry.Add(textureName, textureInfo);
+
+ // Add to the texture name-to-ID mapping
+ var newId = _textures.Count;
+ _textureNameToIdMap.Add(textureName, newId);
+ }
}
+
+ _constantBuffers.Clear();
+ _constantBuffers.AddRange(cbufferRegistry.Values);
+
+ _textures.Clear();
+ _textures.AddRange(textureRegistry.Values);
+
reflection.Dispose();
}
+ ///
+ /// Gets a unique, stable ID for a shader property.
+ /// This should be called once and the ID cached for repeated use.
+ ///
+ /// The name of the property (e.g., "_Color").
+ /// The integer ID of the property, or -1 if not found.
+ public int GetPropertyId(string propertyName)
+ {
+ return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
+ }
+
+ ///
+ /// Gets a unique, stable ID for a texture property.
+ /// This should be called once and the ID cached for repeated use.
+ ///
+ /// The name of the texture (e.g., "_MainTex").
+ /// The integer ID of the texture, or -1 if not found.
+ public int GetTextureId(string textureName)
+ {
+ return _textureNameToIdMap.TryGetValue(textureName, out var id) ? id : -1;
+ }
+
public void Dispose()
{
if (_disposed)
@@ -283,6 +411,8 @@ public unsafe class Shader : IDisposable
_constantBuffers.Clear();
_properties.Clear();
+ _textures.Clear();
+ _textureNameToIdMap.Clear();
GC.SuppressFinalize(this);
diff --git a/Ghost.Graphics/Utilities/MeshBuilder.cs b/Ghost.Graphics/Utilities/MeshBuilder.cs
index a3f3214..aeb0d62 100644
--- a/Ghost.Graphics/Utilities/MeshBuilder.cs
+++ b/Ghost.Graphics/Utilities/MeshBuilder.cs
@@ -8,17 +8,17 @@ public static class MeshBuilder
///
/// Creates a unit cube centered at the origin with size 1.
///
- public static Mesh CreateCube(float size = 1.0f, Color128 color = default, Vector3 offset = default)
+ public static Mesh CreateCube(float size = 1.0f, Color128 color = default)
{
var half = size * 0.5f;
var mesh = new Mesh(24, 36);
- var corners = new Vector3[]
+ var corners = new Vector4[]
{
- new Vector3(-half, -half, -half) + offset, new Vector3( half, -half, -half) + offset,
- new Vector3( half, half, -half) + offset, new Vector3(-half, half, -half) + offset,
- new Vector3(-half, -half, half) + offset, new Vector3( half, -half, half) + offset,
- new Vector3( half, half, half) + offset, new Vector3(-half, half, half) + offset
+ new (-half, -half, -half, 1.0f), new (half, -half, -half, 1.0f),
+ new (half, half, -half, 1.0f), new (-half, half, -half, 1.0f),
+ new (-half, -half, half, 1.0f), new (half, -half, half, 1.0f),
+ new (half, half, half, 1.0f), new (-half, half, half, 1.0f)
};
int[][] faces =
@@ -40,13 +40,13 @@ public static class MeshBuilder
{
var vertex = new Vertex
{
- Position = corners[face[i]].AsVector4(),
+ Position = corners[face[i]],
Normal = Vector4.Zero,
Tangent = Vector4.Zero,
Color = color,
UV = uvs[i].AsVector4()
};
- mesh.AddVertex(new(corners[face[i]].AsVector4(), Vector4.Zero, Vector4.Zero, color, uvs[i].AsVector4()));
+ mesh.AddVertex(vertex);
}
mesh.AddTriangle((int)baseIndex + 0, (int)baseIndex + 1, (int)baseIndex + 2);
diff --git a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs
index cf3aa1f..8a3ff5a 100644
--- a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs
+++ b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs
@@ -1,14 +1,16 @@
using Ghost.Graphics;
using Ghost.Graphics.Contracts;
+using Ghost.Graphics.D3D12;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
+using Misaki.HighPerformance.LowLevel.Buffer;
using WinRT;
namespace Ghost.UnitTest;
public sealed partial class UnitTestAppWindow : Window
{
- private IRenderer? _renderView;
+ private Renderer? _renderer;
private ISwapChainPanelNative _swapChainPanelNative;
public UnitTestAppWindow()
@@ -25,31 +27,36 @@ public sealed partial class UnitTestAppWindow : Window
{
if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0)
{
- _renderView?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height);
+ _renderer?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height);
}
}
private void UnitTestAppWindow_Activated(object sender, WindowActivatedEventArgs args)
{
- GraphicsPipeline.Initialize(Graphics.Data.GraphicsAPI.D3D12);
+#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);
- _renderView = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height));
+ _renderer = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height));
CompositionTarget.Rendering += OnRendering;
}
private void UnitTestAppWindow_Closed(object sender, WindowEventArgs args)
{
+ CompositionTarget.Rendering -= OnRendering;
+
GraphicsPipeline.SignalCPUReady();
GraphicsPipeline.Shutdown();
- CompositionTarget.Rendering -= OnRendering;
+
_swapChainPanelNative.Dispose();
- _renderView?.Dispose();
+ _renderer?.Dispose();
}
private void OnRendering(object? sender, object e)