Refactor GPU resource management and rendering pipeline

- Introduced `Handle<T>` and `Identifier<T>` for lightweight, strongly-typed resource identifiers.
- Replaced `BitSet` with `UnsafeBitSet` for improved performance and memory safety.
- Refactored `Mesh` and `Material` into `MeshClass` and `MaterialClass` for better GPU resource handling.
- Added `D3D12ResourceDatabase` to centralize GPU resource tracking and lifecycle management.
- Updated `D3D12ShaderCompiler` to load shaders from disk and dynamically populate constant buffers and textures.
- Enhanced `ICommandBuffer` with new upload operations for buffers and textures.
- Refactored `Vertex` struct for simplified memory layout and better performance.
- Updated `MeshBuilder` and rendering logic to align with new resource and shader structures.
- Added `BindlessDescriptor` support to `TextureHandle` and `BufferHandle`.
- Removed unused classes and performed general cleanup.
- Updated unit tests and demos to reflect the new architecture.
This commit is contained in:
2025-09-19 23:20:15 +09:00
parent 6a504cefc8
commit a39f377533
39 changed files with 1669 additions and 826 deletions

88
Ghost.Core/Handle.cs Normal file
View File

@@ -0,0 +1,88 @@
namespace Ghost.Core;
public readonly struct Handle<T>
{
public readonly int id;
public readonly int generation;
public Handle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public static Handle<T> Invalid => new(-1, -1);
public readonly override int GetHashCode()
{
return id.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is Handle<T> id && Equals(id);
}
public readonly bool Equals(Handle<T> other)
{
return id == other.id;
}
public readonly int CompareTo(Handle<T> other)
{
return id.CompareTo(other.id);
}
public static bool operator ==(Handle<T> a, Handle<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Handle<T> a, Handle<T> b)
{
return !a.Equals(b);
}
}
public readonly struct Identifier<T>
{
public readonly int value;
public Identifier(int value)
{
this.value = value;
}
public static Identifier<T> Invalid => new(-1);
public readonly override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is Identifier<T> id && Equals(id);
}
public readonly bool Equals(Identifier<T> other)
{
return value == other.value;
}
public readonly int CompareTo(Identifier<T> other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(Identifier<T> a, Identifier<T> b)
{
return a.Equals(b);
}
public static bool operator !=(Identifier<T> a, Identifier<T> b)
{
return !a.Equals(b);
}
}

View File

@@ -472,7 +472,7 @@ internal struct ComponentStorage : IDisposable
private int _currentCapacity = 16; private int _currentCapacity = 16;
private IComponentPool?[] _componentPools = new IComponentPool[16]; private IComponentPool?[] _componentPools = new IComponentPool[16];
private BitSet?[] _componentEntityMasks = new BitSet[16]; private UnsafeBitSet?[] _componentEntityMasks = new UnsafeBitSet?[16];
private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16); private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16);
private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16); private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16);
@@ -487,7 +487,7 @@ internal struct ComponentStorage : IDisposable
} }
internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools; internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools;
internal readonly IReadOnlyList<BitSet?> ComponentEntityMasks => _componentEntityMasks; internal readonly IReadOnlyList<UnsafeBitSet?> ComponentEntityMasks => _componentEntityMasks;
internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool; internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -621,7 +621,7 @@ internal struct ComponentStorage : IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out BitSet? bitSet) public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out UnsafeBitSet? bitSet)
{ {
if (!_typeIDMap.TryGetValue(typeHandle, out var id) if (!_typeIDMap.TryGetValue(typeHandle, out var id)
|| id >= _currentCapacity) || id >= _currentCapacity)
@@ -631,17 +631,17 @@ internal struct ComponentStorage : IDisposable
} }
bitSet = _componentEntityMasks[id]; bitSet = _componentEntityMasks[id];
return bitSet != null; return bitSet.HasValue;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryGetMask<T>([NotNullWhen(true)] out BitSet? bitSet) public readonly bool TryGetMask<T>([NotNullWhen(true)] out UnsafeBitSet? bitSet)
where T : unmanaged, IComponentData where T : unmanaged, IComponentData
{ {
return TryGetMask(TypeHandle.Get<T>(), out bitSet); return TryGetMask(TypeHandle.Get<T>(), out bitSet);
} }
public BitSet GetOrCreateMask<T>() public UnsafeBitSet GetOrCreateMask<T>()
where T : unmanaged, IComponentData where T : unmanaged, IComponentData
{ {
var typeHandle = TypeHandle.Get<T>(); var typeHandle = TypeHandle.Get<T>();
@@ -658,12 +658,12 @@ internal struct ComponentStorage : IDisposable
} }
ref var set = ref _componentEntityMasks[id]; ref var set = ref _componentEntityMasks[id];
set ??= new BitSet(); set ??= new UnsafeBitSet();
return set; return set.Value;
} }
public BitSet GetOrCreateMask(Type type) public UnsafeBitSet GetOrCreateMask(Type type)
{ {
var typeHandle = TypeHandle.Get(type); var typeHandle = TypeHandle.Get(type);
if (!_typeIDMap.TryGetValue(typeHandle, out var id)) if (!_typeIDMap.TryGetValue(typeHandle, out var id))
@@ -679,9 +679,9 @@ internal struct ComponentStorage : IDisposable
} }
ref var set = ref _componentEntityMasks[id]; ref var set = ref _componentEntityMasks[id];
set ??= new BitSet(); set ??= new UnsafeBitSet();
return set; return set.Value;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -704,7 +704,11 @@ internal struct ComponentStorage : IDisposable
pool?.Dispose(); pool?.Dispose();
} }
Array.Clear(_componentPools); foreach (var bitSet in _componentEntityMasks)
{
bitSet?.Dispose();
}
_scriptComponentPool.Dispose(); _scriptComponentPool.Dispose();
} }
} }

View File

@@ -186,7 +186,7 @@ public readonly struct EntityManager : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasComponent(Entity entity, TypeHandle typeHandle) public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
{ {
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.IsSet(entity.ID); return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
} }
/// <summary> /// <summary>

View File

@@ -25,25 +25,20 @@ internal struct QueryFilter()
internal List<TypeHandle> _absent = new(6); internal List<TypeHandle> _absent = new(6);
internal List<TypeHandle> _disabled = new(6); internal List<TypeHandle> _disabled = new(6);
public readonly BitSet ComputeFilterBitMask(World world) public readonly UnsafeBitSet ComputeFilterBitMask(World world)
{ {
BitSet? allMask = null; UnsafeBitSet? allMask = null;
BitSet? anyMask = null; UnsafeBitSet? anyMask = null;
BitSet? absentMask = null; UnsafeBitSet? absentMask = null;
var hasAll = false;
var hasAny = false;
var hasAbsent = false;
foreach (var typeHandle in _all) foreach (var typeHandle in _all)
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAll) if (!allMask.HasValue)
{ {
allMask = new BitSet(mask.Length); allMask = new UnsafeBitSet(mask.Length);
allMask.SetAll(); allMask.Value.SetAll();
hasAll = true;
} }
allMask &= mask; allMask &= mask;
@@ -53,10 +48,9 @@ internal struct QueryFilter()
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAny) if (!anyMask.HasValue)
{ {
anyMask = new BitSet(mask.Length); anyMask = new UnsafeBitSet(mask.Length);
hasAny = true;
} }
anyMask |= mask; anyMask |= mask;
@@ -66,31 +60,30 @@ internal struct QueryFilter()
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!hasAbsent) if (!absentMask.HasValue)
{ {
absentMask = new BitSet(mask.Length); absentMask = new UnsafeBitSet(mask.Length);
hasAbsent = true;
} }
absentMask |= mask; absentMask |= mask;
} }
var result = new BitSet(world.EntityManager.EntityCount); var result = new UnsafeBitSet(world.EntityManager.EntityCount);
result.SetAll(); result.SetAll();
if (hasAll) if (allMask.HasValue)
{ {
result &= allMask!; result &= allMask.Value;
} }
if (hasAny) if (anyMask.HasValue)
{ {
result &= anyMask!; result &= anyMask.Value;
} }
if (hasAbsent) if (absentMask.HasValue)
{ {
result &= ~absentMask!; result &= ~absentMask.Value;
} }
return result; return result;

View File

@@ -42,7 +42,7 @@ public struct QueryEnumerable<T0>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0> Current public QueryItem<T0> Current
{ {
@@ -213,7 +213,7 @@ public struct QueryEnumerable<T0, T1>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1> Current public QueryItem<T0, T1> Current
{ {
@@ -389,7 +389,7 @@ public struct QueryEnumerable<T0, T1, T2>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2> Current public QueryItem<T0, T1, T2> Current
{ {
@@ -570,7 +570,7 @@ public struct QueryEnumerable<T0, T1, T2, T3>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3> Current public QueryItem<T0, T1, T2, T3> Current
{ {
@@ -756,7 +756,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4> Current public QueryItem<T0, T1, T2, T3, T4> Current
{ {
@@ -947,7 +947,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5> Current public QueryItem<T0, T1, T2, T3, T4, T5> Current
{ {
@@ -1143,7 +1143,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5, T6> Current public QueryItem<T0, T1, T2, T3, T4, T5, T6> Current
{ {
@@ -1344,7 +1344,7 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<T0, T1, T2, T3, T4, T5, T6, T7> Current public QueryItem<T0, T1, T2, T3, T4, T5, T6, T7> Current
{ {

View File

@@ -66,7 +66,7 @@ public struct QueryEnumerable<<#= generics #>>
private int _index; private int _index;
private readonly int _count; private readonly int _count;
private BitSet _filterMask; private UnsafeBitSet _filterMask;
public QueryItem<<#= generics #>> Current public QueryItem<<#= generics #>> Current
{ {

View File

@@ -33,7 +33,7 @@ public unsafe class CommandList
/// </summary> /// </summary>
/// <param name="mesh">The mesh to draw</param> /// <param name="mesh">The mesh to draw</param>
/// <param name="material">The bindless material to use</param> /// <param name="material">The bindless material to use</param>
public void DrawMesh(Mesh mesh, Material material) public void DrawMesh(MeshClass mesh, MaterialClass material)
{ {
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps) // Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
material.Bind(this); material.Bind(this);
@@ -62,7 +62,7 @@ public unsafe class CommandList
_commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle); _commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle);
} }
public void ClearRenderTarget(RenderTexture renderTarget, Color16 color) public void ClearRenderTarget(RenderTexture renderTarget, Color128 color)
{ {
renderTarget.ClearColor(this, color); renderTarget.ClearColor(this, color);
} }

View File

@@ -1,5 +1,6 @@
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using Win32; using Win32;
using Win32.Graphics.Direct3D; using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12; using Win32.Graphics.Direct3D12;
@@ -16,23 +17,35 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private ComPtr<ID3D12GraphicsCommandList10> _commandList; private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private readonly D3D12PipelineStateController _stateController; private readonly D3D12PipelineStateController _stateController;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly CommandBufferType _type; private readonly CommandBufferType _type;
private bool _isRecording; private bool _isRecording;
private bool _disposed; private bool _disposed;
public CommandBufferType Type => _type;
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get(); public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
public D3D12CommandBuffer(D3D12RenderDevice device, D3D12PipelineStateController stateController, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type) public D3D12CommandBuffer(
D3D12RenderDevice device,
D3D12PipelineStateController stateController,
D3D12ResourceDatabase resourceDatabase,
D3D12ResourceAllocator resourceAllocator,
D3D12DescriptorAllocator descriptorAllocator,
CommandBufferType type)
{ {
_type = type; _type = type;
var commandListType = ConvertCommandBufferType(type); var commandListType = ConvertCommandBufferType(type);
device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf()); device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf()); device.NativeDevice->CreateCommandList1(0u, commandListType, CommandListFlags.None, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
_stateController = stateController; _stateController = stateController;
_resourceDatabase = resourceDatabase;
_resourceAllocator = resourceAllocator;
_descriptorAllocator = descriptorAllocator; _descriptorAllocator = descriptorAllocator;
// Command lists are created in recording state, so close it // Command lists are created in recording state, so close it
@@ -40,8 +53,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_isRecording = false; _isRecording = false;
} }
~D3D12CommandBuffer()
{
Dispose();
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
private void ThrowIfNotRecording()
{
if (!_isRecording)
{
throw new InvalidOperationException("Command buffer is not recording");
}
}
public void Begin() public void Begin()
{ {
ThrowIfDisposed();
if (_isRecording) if (_isRecording)
{ {
throw new InvalidOperationException("Command buffer is already recording"); throw new InvalidOperationException("Command buffer is already recording");
@@ -54,16 +87,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void End() public void End()
{ {
if (!_isRecording) ThrowIfDisposed();
{ ThrowIfNotRecording();
throw new InvalidOperationException("Command buffer is not recording");
}
_commandList.Get()->Close(); _commandList.Get()->Close();
_isRecording = false; _isRecording = false;
} }
public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor) public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor)
{ {
// TODO: Implement render pass begin // TODO: Implement render pass begin
throw new NotImplementedException(); throw new NotImplementedException();
@@ -77,18 +108,27 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void SetViewport(ViewportDesc viewport) public void SetViewport(ViewportDesc viewport)
{ {
var d3d12Viewport = new Viewport(viewport.Width, viewport.Height, viewport.X, viewport.Y, viewport.MinDepth, viewport.MaxDepth); ThrowIfDisposed();
ThrowIfNotRecording();
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport); _commandList.Get()->RSSetViewports(1, &d3d12Viewport);
} }
public void SetScissorRect(RectDesc rect) public void SetScissorRect(RectDesc rect)
{ {
ThrowIfDisposed();
ThrowIfNotRecording();
var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom); var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect); _commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
} }
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after) public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after)
{ {
ThrowIfDisposed();
ThrowIfNotRecording();
if (resource is D3D12Texture d3d12Texture) if (resource is D3D12Texture d3d12Texture)
{ {
_commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource, _commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource,
@@ -118,8 +158,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
} }
// TODO: Batch draw calls by material to minimize state changes // TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(Mesh mesh, Material material) public void DrawMesh(MeshClass mesh, MaterialClass material)
{ {
ThrowIfDisposed();
ThrowIfNotRecording();
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps) // Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader); var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline) if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
@@ -128,8 +171,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
} }
// Set root signature and pipeline state // Set root signature and pipeline state
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get()); _commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 // Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
var heaps = stackalloc ID3D12DescriptorHeap*[2]; var heaps = stackalloc ID3D12DescriptorHeap*[2];
@@ -142,7 +185,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
foreach (var cbufferInfo in material.Shader.ConstantBuffers) foreach (var cbufferInfo in material.Shader.ConstantBuffers)
{ {
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot]; var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress); var resource = _resourceDatabase.GetResource(cache.GpuResource.ResourceHandle);
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
} }
// Bind sampler descriptor table (last root parameter) // Bind sampler descriptor table (last root parameter)
@@ -163,9 +207,67 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1) public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1)
{ {
ThrowIfDisposed();
ThrowIfNotRecording();
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); _commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
} }
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
ThrowIfNotRecording();
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadHandle = _resourceAllocator.CreateUploadBuffer(sizeInBytes);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
void* mappedData;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
{
MemoryUtilities.MemCpy(mappedData, dataPtr, sizeInBytes);
}
uploadResource->Unmap(0, null);
var resource = _resourceDatabase.GetResource(buffer.ResourceHandle);
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(resource, 0, uploadResource, 0, sizeInBytes);
}
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();
var textureResource = _resourceDatabase.GetResource(texture.ResourceHandle);
var resourceDesc = textureResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(textureResource, firstSubresource, numSubresources);
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var d3d12Subresources = new SubresourceData
{
pData = subresources.pData,
RowPitch = subresources.rowPitch,
SlicePitch = subresources.slicePitch
};
UpdateSubresources(
(ID3D12GraphicsCommandList*)_commandList.Get(),
textureResource,
uploadResource,
0,
firstSubresource,
numSubresources,
&d3d12Subresources);
}
private static CommandListType ConvertCommandBufferType(CommandBufferType type) private static CommandListType ConvertCommandBufferType(CommandBufferType type)
{ {
return type switch return type switch
@@ -197,11 +299,20 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public void Dispose() public void Dispose()
{ {
if (_isRecording)
{
throw new InvalidOperationException("Command buffer is still recording");
}
if (_disposed) if (_disposed)
{
return; return;
}
_commandList.Dispose(); _commandList.Dispose();
_allocator.Dispose(); _allocator.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this);
} }
} }

View File

@@ -42,6 +42,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf()); pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
} }
~D3D12CommandQueue()
{
Dispose();
}
public void Submit(ICommandBuffer commandBuffer) public void Submit(ICommandBuffer commandBuffer)
{ {
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer) if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
@@ -113,12 +118,16 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_fenceEvent?.Dispose(); _fenceEvent?.Dispose();
_fence.Dispose(); _fence.Dispose();
_queue.Dispose(); _queue.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this);
} }
} }

View File

@@ -9,12 +9,17 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#endif #endif
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator; private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12PipelineStateController _stateController; private readonly D3D12CommandBuffer _copyCommandBuffer;
public IRenderDevice Device => _device; public IRenderDevice Device => _device;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator; public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IPipelineStateController PipelineStateController => _stateController; public IPipelineStateController PipelineStateController => _stateController;
@@ -24,12 +29,25 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#if DEBUG #if DEBUG
_debugLayer = new(); _debugLayer = new();
#endif #endif
_device = new(); _device = new();
_descriptorAllocator = new(_device); _descriptorAllocator = new(_device);
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator);
_stateController = new(_device); _resourceDatabase = new(_descriptorAllocator);
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator, _resourceDatabase);
_stateController = new(_device, _resourceDatabase);
_copyCommandBuffer = new(
_device,
_stateController,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
CommandBufferType.Copy);
}
~D3D12GraphicsEngine()
{
Dispose();
} }
public IRenderer CreateRenderer() public IRenderer CreateRenderer()
@@ -39,7 +57,13 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
{ {
return new D3D12CommandBuffer(_device, _stateController, _descriptorAllocator, type); return new D3D12CommandBuffer(
_device,
_stateController,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
type);
} }
public ISwapChain CreateSwapChain(SwapChainDesc desc) public ISwapChain CreateSwapChain(SwapChainDesc desc)
@@ -47,15 +71,30 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc); return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
} }
public void BeginFrame()
{
throw new NotImplementedException();
}
public void EndFrame()
{
throw new NotImplementedException();
}
public void Dispose() public void Dispose()
{ {
_copyCommandBuffer.Dispose();
_stateController.Dispose(); _stateController.Dispose();
_descriptorAllocator.Dispose();
_resourceAllocator.Dispose();
_device.Dispose();
_resourceAllocator.Dispose();
_resourceDatabase.Dispose();
_descriptorAllocator.Dispose();
_device.Dispose();
#if DEBUG #if DEBUG
_debugLayer.Dispose(); _debugLayer.Dispose();
#endif #endif
GC.SuppressFinalize(this);
} }
} }

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.D3D12.Utilities; using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Win32; using Win32;
@@ -8,7 +9,7 @@ using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal class D3D12ShaderPipeline : IShaderPipeline internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{ {
public ComPtr<ID3D12RootSignature> rootSignature; public ComPtr<ID3D12RootSignature> rootSignature;
public ComPtr<ID3D12PipelineState> pipelineState; public ComPtr<ID3D12PipelineState> pipelineState;
@@ -21,42 +22,51 @@ internal class D3D12ShaderPipeline : IShaderPipeline
{ {
get; init; get; init;
} }
public void Dispose()
{
rootSignature.Dispose();
pipelineState.Dispose();
samplerHeap.Dispose();
vsResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
} }
internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable
{ {
private readonly ID3D12Device14* _device; private readonly ID3D12Device14* _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly Dictionary<Shader, D3D12ShaderPipeline> _shaderPipelines; private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineStateController(D3D12RenderDevice device) public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
{ {
_device = device.NativeDevice; _device = device.NativeDevice;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new(); _shaderPipelines = new();
} }
// TODO: Support compute shaders public void CompileShader(Identifier<Shader> id, string shaderPath)
public void ColectionShader(ReadOnlySpan<Shader> shaders)
{ {
foreach (var shader in shaders) var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
{ var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
_shaderPipelines.TryAdd(shader, new()
{
Type = PipelineType.Graphics
});
}
}
public void CompileCollected() var shader = _resourceDatabase.GetShader(id);
{
foreach (var kvp in _shaderPipelines)
{
var vsResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(kvp.Key, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
kvp.Value.vsResult = vsResult; D3D12ShaderCompiler.PerformDXCReflection(shader, vsResult.reflection.Get());
kvp.Value.psResult = psResult; D3D12ShaderCompiler.PerformDXCReflection(shader, psResult.reflection.Get());
}
var shaderPipeline = new D3D12ShaderPipeline
{
Type = PipelineType.Graphics,
vsResult = vsResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
} }
private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline) private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline)
@@ -219,31 +229,29 @@ internal unsafe class D3D12PipelineStateController : IPipelineStateController, I
{ {
foreach (var kvp in _shaderPipelines) foreach (var kvp in _shaderPipelines)
{ {
CreateRootSignature(kvp.Key, kvp.Value); var shader = _resourceDatabase.GetShader(kvp.Key);
CreateRootSignature(shader, kvp.Value);
CreatePipelineStateObject(kvp.Value); CreatePipelineStateObject(kvp.Value);
CreateSamplerHeap(kvp.Value); CreateSamplerHeap(kvp.Value);
} }
} }
public IShaderPipeline GetShaderPipeline(Shader shader) public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{ {
if (_shaderPipelines.TryGetValue(shader, out var pipeline)) if (_shaderPipelines.TryGetValue(id, out var pipeline))
{ {
return pipeline; return pipeline;
} }
throw new KeyNotFoundException($"Shader pipeline not found for shader: {shader}"); throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
} }
public void Dispose() public void Dispose()
{ {
foreach (var kvp in _shaderPipelines) foreach (var kvp in _shaderPipelines)
{ {
kvp.Value.rootSignature.Dispose(); kvp.Value.Dispose();
kvp.Value.pipelineState.Dispose();
kvp.Value.samplerHeap.Dispose();
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
} }
} }
} }

View File

@@ -169,7 +169,7 @@ public unsafe class D3D12Renderer : IRenderer
private void RenderScene(IRenderTarget target, ICommandBuffer cmd) private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
{ {
var clearColor = new Color16 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f }; var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
cmd.BeginRenderPass(target, clearColor); cmd.BeginRenderPass(target, clearColor);
@@ -207,7 +207,7 @@ public unsafe class D3D12Renderer : IRenderer
// 3. Apply post-processing effects (tone mapping, gamma correction, etc.) // 3. Apply post-processing effects (tone mapping, gamma correction, etc.)
// For now, just clear the destination (this should be replaced with actual blit) // For now, just clear the destination (this should be replaced with actual blit)
var clearColor = new Color16 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f }; var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
cmd.BeginRenderPass(destination, clearColor); cmd.BeginRenderPass(destination, clearColor);
cmd.EndRenderPass(); cmd.EndRenderPass();

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.Data; using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -10,32 +11,8 @@ using static Win32.Graphics.D3D12MemoryAllocator.Apis;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource>, IDisposable internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{ {
private readonly struct AllocationInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public bool Allocated => allocation.IsNotNull;
public AllocationInfo(in Allocation allocation, uint cpuFenceValue)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
}
public void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u; private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u; private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u; private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
@@ -45,8 +22,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
private readonly Allocator _allocator; private readonly Allocator _allocator;
private readonly RenderSystem _renderSystem; private readonly RenderSystem _renderSystem;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UnsafeSlotMap<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL private Guid* IID_NULL
@@ -60,7 +37,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
} }
} }
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator) public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
{ {
var desc = new AllocatorDesc var desc = new AllocatorDesc
{ {
@@ -74,6 +51,12 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
_device = device.NativeDevice; _device = device.NativeDevice;
_renderSystem = renderSystem; _renderSystem = renderSystem;
_descriptorAllocator = descriptorAllocator; _descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase;
}
~D3D12ResourceAllocator()
{
Dispose();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -94,11 +77,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
} }
} }
private ResourceHandle TrackResource(ref readonly Allocation allocation, bool isTemp) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private ResourceHandle TrackResource(ref readonly Allocation allocation, ResourceStates state, bool isTemp)
{ {
var id = _allocations.Add(new(in allocation, _renderSystem.CPUFenceValue), out var generation); var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state));
var handle = new ResourceHandle(id, generation);
if (isTemp) if (isTemp)
{ {
_temResources.Enqueue(handle); _temResources.Enqueue(handle);
@@ -107,18 +90,40 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return handle; return handle;
} }
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false) public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
{ {
CheckTexture2DSize(desc.Width, desc.Height); CheckTexture2DSize(desc.Width, desc.Height);
var resourceDesc = ResourceDescription.Tex2D( var d3d12Format = ConvertTextureFormat(desc.Format);
ConvertTextureFormat(desc.Format), var mipLevels = desc.MipLevels == 0 ? (ushort)(1 + Math.Floor(Math.Log2(Math.Max(desc.Width, desc.Height)))) : (ushort)desc.MipLevels;
desc.Width,
desc.Height, var resourceDesc = desc.Dimension switch
mipLevels: (ushort)desc.MipLevels, {
arraySize: (ushort)desc.Slice, TextureDimension.Texture2D => ResourceDescription.Tex2D(
flags: ConvertTextureUsage(desc.Usage) d3d12Format,
); desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: ConvertTextureUsage(desc.Usage)),
TextureDimension.Texture3D => ResourceDescription.Tex3D(
d3d12Format,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCube:
// break;
TextureDimension.Texture2DArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCubeArray:
// break;
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
};
var allocationDesc = new AllocationDesc var allocationDesc = new AllocationDesc
{ {
@@ -131,10 +136,58 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
Allocation allocation = default; Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null)); ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
return new(TrackResource(in allocation, tempResource)); var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.CreationFlags.HasFlag(TextureCreationFlags.Bindless))
{
var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = d3d12Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
switch (desc.Dimension)
{
case TextureDimension.Texture2D:
srvDesc.ViewDimension = SrvDimension.Texture2D;
srvDesc.Texture2D = new Texture2DSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.Texture3D:
srvDesc.ViewDimension = SrvDimension.Texture3D;
srvDesc.Texture3D = new Texture3DSrv
{
MipLevels = 0,
};
break;
case TextureDimension.Texture2DArray:
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
srvDesc.Texture2DArray = new Texture2DArraySrv
{
MipLevels = mipLevels,
ArraySize = desc.Slice,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {desc.Dimension}");
}
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
}
return new(handle);
} }
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false) public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
{
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return CreateTexture(ref textureDesc, tempResource);
}
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
{ {
CheckBufferSize((uint)desc.Size); CheckBufferSize((uint)desc.Size);
@@ -150,12 +203,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
Allocation allocation = default; Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null)); ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, tempResource); var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless)) if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless))
{ {
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw); var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
var descriptorHandle = _descriptorAllocator.AllocateBindless(); var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription var srvDesc = new ShaderResourceViewDescription
@@ -189,23 +241,75 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return new(handle); return new(handle);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferHandle CreateUploadBuffer(ulong size, bool temp = true)
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
{ {
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc); var desc = new BufferDesc
return D3D12RenderTarget.Create(CreateTextureHandle(in textureDesc), in desc); {
Size = size,
Usage = BufferUsage.Upload,
MemoryType = MemoryType.Upload,
};
return CreateBuffer(in desc, temp);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public Identifier<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
{ {
return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc); var vertexBufferDesc = new BufferDesc
{
Size = (ulong)(vertices.Length * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
};
var indexBufferDesc = new BufferDesc
{
Size = (ulong)(indices.Length * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
};
var vertexBuffer = CreateBuffer(ref vertexBufferDesc, true);
var indexBuffer = CreateBuffer(ref indexBufferDesc, true);
var data = new Mesh(vertices, indices, vertexBuffer, indexBuffer);
return _resourceDatabase.AddMesh(in data);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public Identifier<Material> CreateMaterial(Identifier<Shader> shader)
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
{ {
return new D3D12Buffer(CreateBufferHandle(in desc, tempResource), in desc, this); var materialData = new Material
{
Shader = shader,
};
var shaderResource = _resourceDatabase.GetShader(shader);
if (shaderResource.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
materialData._cBufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
{
var desc = new BufferDesc
{
Size = cbufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
};
var buffer = CreateBuffer(in desc);
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
return _resourceDatabase.AddMaterial(in materialData);
} }
#region Conversion Methods #region Conversion Methods
@@ -319,6 +423,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return ResourceStates.Common; return ResourceStates.Common;
} }
private static ResourceState D3D12StatesToRHIState(ResourceStates states)
{
switch (states)
{
//case ResourceStates.None:
//case ResourceStates.Present:
case ResourceStates.Common:
return ResourceState.Common;
case ResourceStates.VertexAndConstantBuffer:
return ResourceState.VertexAndConstantBuffer;
case ResourceStates.IndexBuffer:
return ResourceState.IndexBuffer;
case ResourceStates.RenderTarget:
return ResourceState.RenderTarget;
case ResourceStates.UnorderedAccess:
return ResourceState.UnorderedAccess;
case ResourceStates.DepthWrite:
return ResourceState.DepthWrite;
case ResourceStates.DepthRead:
return ResourceState.DepthRead;
case ResourceStates.PixelShaderResource:
return ResourceState.PixelShaderResource;
//case ResourceStates.Predication:
case ResourceStates.IndirectArgument:
return ResourceState.IndirectArgument;
case ResourceStates.CopyDest:
return ResourceState.CopyDest;
case ResourceStates.CopySource:
return ResourceState.CopySource;
case ResourceStates.GenericRead:
return ResourceState.GenericRead;
default:
return ResourceState.Common;
}
}
#endregion #endregion
public void ReleaseTempResource() public void ReleaseTempResource()
@@ -326,9 +466,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
while (_temResources.Count > 0) while (_temResources.Count > 0)
{ {
var handle = _temResources.Peek(); var handle = _temResources.Peek();
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (_allocations.TryGetElementAt(handle.id, handle.generation, out var info) if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue)
&& info.Allocated)
{ {
break; break;
} }
@@ -338,22 +477,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
} }
} }
public ID3D12Resource* GetResource(ResourceHandle handle)
{
if (!handle.IsValid)
{
throw new InvalidOperationException("Invalid resource handle.");
}
var info = _allocations.GetElementAt(handle.id, handle.generation);
if (!info.Allocated)
{
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
}
return info.allocation.Resource;
}
public void ReleaseResource(ResourceHandle handle) public void ReleaseResource(ResourceHandle handle)
{ {
if (!handle.IsValid) if (!handle.IsValid)
@@ -361,7 +484,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
return; return;
} }
ref var info = ref _allocations.GetElementReferenceAt(handle.id, handle.generation, out var exist); ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated) if (!exist || !info.Allocated)
{ {
@@ -369,24 +492,26 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator<ID3D12Resource
} }
info.Dispose(); info.Dispose();
_allocations.Remove(handle.id, handle.generation); _resourceDatabase.RemoveResource(handle);
} }
public void Dispose() public void Dispose()
{ {
#if DEBUG #if DEBUG
if (_allocations.Count > 0) if (_temResources.Count > 0)
{ {
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing."); throw new InvalidOperationException($"ResourceAllocator is being disposed with {_temResources.Count} temp allocations still registered. Ensure all resources are released before disposing.");
} }
#endif #endif
foreach (var info in _allocations)
foreach (var handle in _temResources)
{ {
info.Dispose(); ReleaseResource(handle);
} }
_allocations.Dispose();
_temResources.Dispose(); _temResources.Dispose();
_allocator.Release(); _allocator.Release();
GC.SuppressFinalize(this);
} }
} }

View File

@@ -1,11 +1,338 @@
using System; using Ghost.Core;
using System.Collections.Generic; using Ghost.Graphics.Data;
using System.Linq; using Ghost.Graphics.RHI;
using System.Text; using Misaki.HighPerformance.Collections;
using System.Threading.Tasks; using Misaki.HighPerformance.LowLevel.Collections;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{ {
} private struct Slot<T>
{
public T value;
public bool isValid;
}
public struct ResourceInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public ResourceState state;
public readonly bool Allocated => allocation.IsNotNull;
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
}
public readonly void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
}
private UnsafeSlotMap<ResourceInfo> _resources;
// NOTE: We use a simple list for shaders since they are not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Mesh>> _meshDatas;
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_meshDatas = new(64);
_shaders = new(16);
_descriptorAllocator = descriptorAllocator;
}
~D3D12ResourceDatabase()
{
Dispose();
}
public ResourceHandle AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState), out var generation);
return new ResourceHandle(id, generation);
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
return ref info;
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
}
public unsafe T* GetResource<T>(ResourceHandle handle)
where T : unmanaged
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (typeof(T) != typeof(ID3D12Resource))
{
return null;
}
var info = GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return (T*)info.allocation.Resource;
}
public unsafe ID3D12Resource* GetResource(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var info = GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return info.allocation.Resource;
}
public ResourceState GetResourceState(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).state;
}
public void SetResourceState(ResourceHandle handle, ResourceState state)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
info.state = state;
}
public void RemoveResource(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
{
return;
}
info.Dispose();
_resources.Remove(handle.id, handle.generation);
}
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Mesh>(_meshDatas.Count);
_meshDatas.Add(new()
{
value = mesh,
isValid = true
});
return id;
}
public bool HasMesh(Identifier<Mesh> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid;
}
public Mesh GetMesh(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
}
return _meshDatas[id.value].value;
}
public ref Mesh GetMeshReference(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
}
return ref _meshDatas[id.value].value;
}
public void RemoveMesh(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
return;
}
ref var meshSlot = ref _meshDatas[id.value];
if (!meshSlot.isValid)
{
return;
}
ref var mesh = ref meshSlot.value;
mesh.ReleaseCpuResources();
RemoveResource(mesh.vertexBuffer.ResourceHandle);
RemoveResource(mesh.indexBuffer.ResourceHandle);
_descriptorAllocator.Release(mesh.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.indexBuffer.BindlessDescriptor);
}
public Identifier<Shader> AddShader(ref readonly Shader shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Shader>(_shaders.Count);
_shaders.Add(new()
{
value = shader,
isValid = true
});
return id;
}
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].isValid;
}
public Shader GetShader(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
}
return _shaders[id.value].value;
}
public ref Shader GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
}
return ref _shaders[id.value].value;
}
public void RemoveShader(Identifier<Shader> id)
{
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[id.value];
shader.value.Dispose();
shader.value = default;
shader.isValid = false;
}
public void Dispose()
{
if (_disposed)
{
return;
}
#if DEBUG
if (_resources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_resources.Count} allocations still registered. Ensure all resources are released before disposing.");
}
if (_meshDatas.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshDatas.Count} meshes still registered. Ensure all meshes are released before disposing.");
}
if (_shaders.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_shaders.Count} shaders still registered. Ensure all shaders are released before disposing.");
}
#endif
_resources.Dispose();
foreach (var mesh in _meshDatas)
{
if (!mesh.isValid)
{
continue;
}
mesh.value.ReleaseCpuResources();
RemoveResource(mesh.value.vertexBuffer.ResourceHandle);
RemoveResource(mesh.value.indexBuffer.ResourceHandle);
_descriptorAllocator.Release(mesh.value.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.value.indexBuffer.BindlessDescriptor);
}
foreach (var shader in _shaders)
{
if (!shader.isValid)
{
continue;
}
shader.value.Dispose();
}
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -65,32 +65,36 @@ internal unsafe static class D3D12ShaderCompiler
}; };
} }
public static CompileResult Compile(Shader shader, ShaderStage stage, CompilerVersion version) public static CompileResult Compile(string shaderPath, ShaderStage stage, CompilerVersion version)
{ {
using ComPtr<IDxcCompiler3> compiler = default; using ComPtr<IDxcCompiler3> compiler = default;
using ComPtr<IDxcUtils> utils = default; using ComPtr<IDxcUtils> utils = default;
using ComPtr<IDxcIncludeHandler> includeHandler = default;
// Create DXC compiler and utils // Create DXC compiler and utils
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf()); DxcCreateInstance(in CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()); DxcCreateInstance(in CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
// Create source blob // Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default; using ComPtr<IDxcBlobEncoding> sourceBlob = default;
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shader.Source); //var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath);
fixed (byte* sourceBytesPtr = sourceBytes)
fixed (char* pShaderPath = shaderPath.AsSpan())
{ {
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf()); utils.Get()->LoadFile(pShaderPath, null, sourceBlob.GetAddressOf());
//utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
} }
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data // Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
var argsArray = new string[] var argsArray = new string[]
{ {
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6) "-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
"-E", GetEntryPoint(stage), // Entry point "-E", GetEntryPoint(stage), // Entry point
"-HV", "2021", // HLSL version 2021 (required for SM 6.6) "-HV", "2021", // HLSL version 2021 (required for SM 6.6)
"-enable-16bit-types", // Enable 16-bit types "-enable-16bit-types", // Enable 16-bit types
"-O3", // Optimization level "-O3", // Optimization level
"-Qstrip_debug" // Strip debug info but KEEP reflection "-Qstrip_debug" // Strip debug info but KEEP reflection
}; };
// Convert to wide strings (DXC expects LPCWSTR) // Convert to wide strings (DXC expects LPCWSTR)
@@ -116,7 +120,7 @@ internal unsafe static class D3D12ShaderCompiler
Encoding = DXC_CP_UTF8 Encoding = DXC_CP_UTF8
}; };
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf()); compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
} }
// Check compilation result // Check compilation result
@@ -203,85 +207,92 @@ internal unsafe static class D3D12ShaderCompiler
ShaderDescription shaderDesc; ShaderDescription shaderDesc;
reflection.Get()->GetDesc(&shaderDesc); reflection.Get()->GetDesc(&shaderDesc);
var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name); var cbufferRegistry = new Dictionary<string, CBufferInfo>();
var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name); var textureRegistry = new Dictionary<string, TextureInfo>();
for (uint i = 0; i < shaderDesc.BoundResources; i++) for (uint i = 0; i < shaderDesc.BoundResources; i++)
{ {
ShaderInputBindDescription bindDesc; ShaderInputBindDescription bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc); reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
if (bindDesc.Type == ShaderInputType.ConstantBuffer) switch (bindDesc.Type)
{ {
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); case ShaderInputType.ConstantBuffer:
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
{ {
continue; var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
} if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
var cbufferInfo = new CBufferInfo
{
Name = cbufferName,
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
};
cbufferRegistry.Add(cbufferName, cbufferInfo);
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
{ {
continue; continue;
} }
var propInfo = new PropertyInfo var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
var cbufferInfo = new CBufferInfo
{ {
Name = variableName, Size = cbufferDesc.Size,
CBufferIndex = cbufferInfo.RegisterSlot, RegisterSlot = bindDesc.BindPoint
ByteOffset = varDesc.StartOffset, };
Size = varDesc.Size cbufferRegistry.Add(cbufferName, cbufferInfo);
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
{
continue;
}
var propInfo = new PropertyInfo
{
CBufferIndex = cbufferInfo.RegisterSlot,
ByteOffset = varDesc.StartOffset,
Size = varDesc.Size
};
shader.AddProperty(variableName, propInfo);
}
break;
}
case ShaderInputType.Texture:
{
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
{
continue;
}
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
{
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
}; };
// Add to the list and create the name-to-ID mapping textureRegistry.Add(textureName, textureInfo);
var newId = shader.Properties.Count; break;
shader.Properties.Add(propInfo);
shader.PropertyNameToIdMap.Add(variableName, newId);
} }
} }
else if (bindDesc.Type == ShaderInputType.Texture)
{
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
{
continue;
}
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
{
Name = textureName,
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
};
textureRegistry.Add(textureName, textureInfo);
}
} }
shader.ConstantBuffers.Clear(); shader.ConstantBuffers.Clear();
shader.ConstantBuffers.AddRange(cbufferRegistry.Values); foreach (var cbuf in cbufferRegistry.Values)
{
shader.ConstantBuffers.Add(cbuf);
}
shader.RegularTextures.Clear(); shader.RegularTextures.Clear();
shader.RegularTextures.AddRange(textureRegistry.Values); foreach (var tex in textureRegistry.Values)
{
shader.RegularTextures.Add(tex);
}
} }
} }

View File

@@ -10,11 +10,11 @@ internal unsafe static class D3D12PipelineResource
public const int BACK_BUFFER_COUNT = 2; public const int BACK_BUFFER_COUNT = 2;
private readonly static InputElementDescription[] s_inputElementDescs = [ private readonly static InputElementDescription[] s_inputElementDescs = [
new InputElementDescription{ SemanticName = Vertex.Semantic.pPositionName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new InputElementDescription{ SemanticName = Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pNormalName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new InputElementDescription{ SemanticName = Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pTangentName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new InputElementDescription{ SemanticName = Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pColorName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new InputElementDescription{ SemanticName = Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.pUVName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 } new InputElementDescription{ SemanticName = Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 },
]; ];
public const Format SWAP_CHAIN_BACK_BUFFER_FORMAT = Format.B8G8R8A8Unorm; public const Format SWAP_CHAIN_BACK_BUFFER_FORMAT = Format.B8G8R8A8Unorm;

View File

@@ -1,12 +1,9 @@
using Ghost.Graphics.D3D12; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
internal struct CBufferCache : IDisposable internal struct CBufferCache
{ {
public UnsafeArray<byte> CpuData public UnsafeArray<byte> CpuData
{ {
@@ -14,33 +11,18 @@ internal struct CBufferCache : IDisposable
set; set;
} }
public GraphicsBuffer GpuResource public BufferHandle GpuResource
{ {
get; get;
} }
private readonly uint _alignedSize; private readonly uint _alignedSize;
internal unsafe CBufferCache(uint bufferSize) internal unsafe CBufferCache(BufferHandle buffer, uint bufferSize)
{ {
_alignedSize = (bufferSize + 255u) & ~255u; _alignedSize = (bufferSize + 255u) & ~255u;
CpuData = new((int)_alignedSize, Allocator.Persistent); CpuData = new((int)_alignedSize, Allocator.Persistent);
GpuResource = GraphicsBuffer.Create(_alignedSize, GraphicsBuffer.Usage.Constant); GpuResource = buffer;
GpuResource.Name = "Material_CBufferCache";
UploadToGpu();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadToGpu()
{
GpuResource.SetData(CpuData.AsSpan(), 0);
}
public readonly void Dispose()
{
CpuData.Dispose();
GpuResource.Dispose();
} }
} }

View File

@@ -7,14 +7,14 @@ namespace Ghost.Graphics.Data;
/// Represents a color with 4 bytes components. /// Represents a color with 4 bytes components.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4)] [StructLayout(LayoutKind.Sequential, Size = 4)]
public struct Color4 : IEquatable<Color4> public struct Color32 : IEquatable<Color32>
{ {
public byte r; public byte r;
public byte g; public byte g;
public byte b; public byte b;
public byte a; public byte a;
public Color4(byte r, byte g, byte b, byte a) public Color32(byte r, byte g, byte b, byte a)
{ {
this.r = r; this.r = r;
this.g = g; this.g = g;
@@ -22,24 +22,24 @@ public struct Color4 : IEquatable<Color4>
this.a = a; this.a = a;
} }
public Color4(Color color) public Color32(Color color)
: this(color.R, color.G, color.B, color.A) : this(color.R, color.G, color.B, color.A)
{ {
} }
public Color4(Color16 color128) public Color32(Color128 color128)
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f)) : this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
{ {
} }
public readonly bool Equals(Color4 other) public readonly bool Equals(Color32 other)
{ {
return r == other.r && g == other.g && b == other.b && a == other.a; return r == other.r && g == other.g && b == other.b && a == other.a;
} }
public override readonly bool Equals(object? obj) public override readonly bool Equals(object? obj)
{ {
return obj is Color4 color && Equals(color); return obj is Color32 color && Equals(color);
} }
public override readonly int GetHashCode() public override readonly int GetHashCode()
@@ -47,12 +47,12 @@ public struct Color4 : IEquatable<Color4>
return HashCode.Combine(r, g, b, a); return HashCode.Combine(r, g, b, a);
} }
public static bool operator ==(Color4 left, Color4 right) public static bool operator ==(Color32 left, Color32 right)
{ {
return left.Equals(right); return left.Equals(right);
} }
public static bool operator !=(Color4 left, Color4 right) public static bool operator !=(Color32 left, Color32 right)
{ {
return !(left == right); return !(left == right);
} }
@@ -62,14 +62,14 @@ public struct Color4 : IEquatable<Color4>
/// Represents a color with 16 bytes components. /// Represents a color with 16 bytes components.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)] [StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Color16 : IEquatable<Color16> public struct Color128 : IEquatable<Color128>
{ {
public float r; public float r;
public float g; public float g;
public float b; public float b;
public float a; public float a;
public Color16(float r, float g, float b, float a) public Color128(float r, float g, float b, float a)
{ {
this.r = r; this.r = r;
this.g = g; this.g = g;
@@ -77,24 +77,24 @@ public struct Color16 : IEquatable<Color16>
this.a = a; this.a = a;
} }
public Color16(Color color) public Color128(Color color)
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f) : this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
{ {
} }
public Color16(Color4 color32) public Color128(Color32 color32)
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f) : this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
{ {
} }
public readonly bool Equals(Color16 other) public readonly bool Equals(Color128 other)
{ {
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a); return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
} }
public override readonly bool Equals(object? obj) public override readonly bool Equals(object? obj)
{ {
return obj is Color16 color && Equals(color); return obj is Color128 color && Equals(color);
} }
public readonly override int GetHashCode() public readonly override int GetHashCode()
@@ -102,12 +102,12 @@ public struct Color16 : IEquatable<Color16>
return HashCode.Combine(r, g, b, a); return HashCode.Combine(r, g, b, a);
} }
public static bool operator ==(Color16 left, Color16 right) public static bool operator ==(Color128 left, Color128 right)
{ {
return left.Equals(right); return left.Equals(right);
} }
public static bool operator !=(Color16 left, Color16 right) public static bool operator !=(Color128 left, Color128 right)
{ {
return !(left == right); return !(left == right);
} }

View File

@@ -1,54 +0,0 @@
namespace Ghost.Graphics.Data;
public struct BatchMaterialID : IEquatable<BatchMaterialID>
{
public uint value;
public static BatchMaterialID Null => new() { value = 0 };
public override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is BatchMaterialID id && Equals(id);
}
public readonly bool Equals(BatchMaterialID other)
{
return value == other.value;
}
public readonly int CompareTo(BatchMaterialID other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(BatchMaterialID a, BatchMaterialID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMaterialID a, BatchMaterialID b)
{
return !a.Equals(b);
}
}
internal class GraphicsResourceManager
{
private readonly Dictionary<BatchMaterialID, Material> _materials = new();
private readonly Dictionary<BatchMeshID, MeshHandle> _meshes = new();
public Material GetMaterial(BatchMaterialID id)
{
return _materials.TryGetValue(id, out var material) ? material : Material.Null;
}
public MeshHandle GetMesh(BatchMeshID id)
{
return _meshes.TryGetValue(id, out var mesh) ? mesh : MeshHandle.Invalid;
}
}

View File

@@ -1,204 +0,0 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.Data;
/// <summary>
/// Material implementation for bindless rendering with SM 6.6 support
/// </summary>
public unsafe class Material : IDisposable
{
private readonly CBufferCache[] _cbufferCaches;
private bool _disposed;
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches;
public Shader Shader
{
get; set;
}
public Material(Shader shader)
{
Shader = shader;
if (shader.ConstantBuffers.Count > 0)
{
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<CBufferCache>();
}
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
SetFloat(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
SetVector(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTexture(string propertyName, Texture2D texture)
{
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBuffer(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
private unsafe void WriteToCache<T>(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 '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _cbufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Uploads the material data to the GPU.
/// </summary>
public void UploadMaterialData()
{
foreach (var cache in _cbufferCaches)
{
cache.UploadToGpu();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var cache in _cbufferCaches)
{
cache.Dispose();
}
// NOTE: We don't dispose the textures here as they might be shared
// The user is responsible for disposing BindlessTexture2D instances
GC.SuppressFinalize(this);
_disposed = true;
}
}

View File

@@ -0,0 +1,376 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
public struct Material
{
internal UnsafeArray<CBufferCache> _cBufferCaches;
public Identifier<Shader> Shader
{
get; internal set;
}
}
public ref struct MaterialAccessor
{
private ref Material _materialData;
private ref Shader _shader;
public MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
{
_materialData = ref materialData;
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
}
private readonly unsafe void WriteToCache<T>(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 '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
SetFloat(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
SetVector(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTexture(string propertyName, Texture2D texture)
{
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
/// <summary>
/// Uploads all cached material data to the GPU using the specified command buffer.
/// </summary>
/// <param name="cmb">The command buffer used to perform the upload operations to the GPU. Cannot be null.</param>
public readonly void UploadMaterialData(ICommandBuffer cmb)
{
foreach (var cache in _materialData._cBufferCaches)
{
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
}
}
}
/// <summary>
/// Material implementation for bindless rendering with SM 6.6 support
/// </summary>
public unsafe class MaterialClass : IDisposable
{
private readonly UnsafeArray<CBufferCache> _cbufferCaches;
private bool _disposed;
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches.AsSpan();
public Identifier<Shader> Shader
{
get; set;
}
public MaterialClass(Identifier<Shader> shader, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{
Shader = shader;
var shaderResource = resourceDatabase.GetShader(shader);
if (shaderResource.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
_cbufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Allocator.Persistent);
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
{
var desc = new BufferDesc
{
Size = cbufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
};
var buffer = resourceAllocator.CreateBuffer(in desc);
_cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFloat(string propertyName, in float value)
{
SetFloat(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetVector(string propertyName, in Vector4 value)
{
SetVector(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(string propertyName, in Matrix4x4 value)
{
SetMatrix(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTexture(string propertyName, Texture2D texture)
{
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
private unsafe void WriteToCache<T>(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 '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var cache = _cbufferCaches[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Uploads the material data to the GPU.
/// </summary>
public void UploadMaterialData()
{
foreach (var cache in _cbufferCaches)
{
cache.UploadToGpu();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var cache in _cbufferCaches)
{
cache.Dispose();
}
// NOTE: We don't dispose the textures here as they might be shared
// The user is responsible for disposing BindlessTexture2D instances
GC.SuppressFinalize(this);
_disposed = true;
}
}

View File

@@ -2,82 +2,68 @@
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers; using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry; using Misaki.HighPerformance.Mathematics.Geometry;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12; using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common; using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
/// <summary> public struct Mesh
/// The CPU-side mesh data structure.
/// </summary>
public struct MeshData
{ {
public UnsafeList<Vertex> vertices; public UnsafeList<Vertex> vertices;
public UnsafeList<int> indices; public UnsafeList<uint> indices;
public AABB boundingBox; public AABB boundingBox;
public MeshHandle handle;
public MeshData()
{
handle = MeshHandle.Invalid;
}
}
/// <summary>
/// The GPU-side mesh handle containing buffer references.
/// </summary>
public struct MeshHandle
{
public BufferHandle vertexBuffer; public BufferHandle vertexBuffer;
public BufferHandle indexBuffer; public BufferHandle indexBuffer;
public static MeshHandle Invalid => new() { vertexBuffer = BufferHandle.Invalid, indexBuffer = BufferHandle.Invalid }; public Mesh()
public readonly bool IsValid => vertexBuffer.IsValid && indexBuffer.IsValid;
}
public struct BatchMeshID : IEquatable<BatchMeshID>
{
public int value;
public static BatchMeshID Null => new() { value = -1 };
public readonly override int GetHashCode()
{ {
return value.GetHashCode(); vertexBuffer = BufferHandle.Invalid;
indexBuffer = BufferHandle.Invalid;
} }
public readonly override bool Equals(object? obj) internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, BufferHandle vertexBuffer, BufferHandle indexBuffer)
{ {
return obj is BatchMeshID id && Equals(id); this.vertices = new(vertices.Length, Allocator.Persistent);
this.indices = new(indices.Length, Allocator.Persistent);
this.vertices.CopyFrom(vertices);
this.indices.CopyFrom(indices);
this.vertexBuffer = vertexBuffer;
this.indexBuffer = indexBuffer;
ComputeBounds();
} }
public readonly bool Equals(BatchMeshID other) public void ComputeBounds()
{ {
return value == other.value; if (vertices.Count == 0)
{
return;
}
var min = new float3(float.MaxValue);
var max = new float3(float.MinValue);
foreach (var vertex in vertices)
{
var pos = vertex.position.xyz;
min = math.min(min, pos);
max = math.max(max, pos);
}
boundingBox = new AABB(min, max);
} }
public readonly int CompareTo(BatchMeshID other) public void ReleaseCpuResources()
{ {
return value.CompareTo(other.value); vertices.Dispose();
} indices.Dispose();
public static bool operator ==(BatchMeshID a, BatchMeshID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMeshID a, BatchMeshID b)
{
return !a.Equals(b);
} }
} }
public unsafe sealed class Mesh : IDisposable public unsafe sealed class MeshClass : IDisposable
{ {
private UnsafeList<Vertex> _vertices; private UnsafeList<Vertex> _vertices;
private UnsafeList<int> _indices; private UnsafeList<int> _indices;
@@ -132,13 +118,13 @@ public unsafe sealed class Mesh : IDisposable
} }
} }
public Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512) public MeshClass(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
{ {
_vertices = new(initialVertexCapacity, Allocator.Persistent); _vertices = new(initialVertexCapacity, Allocator.Persistent);
_indices = new(initialIndexCapacity, Allocator.Persistent); _indices = new(initialIndexCapacity, Allocator.Persistent);
} }
public Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices) public MeshClass(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices)
: this(vertices.Length, indices.Length) : this(vertices.Length, indices.Length)
{ {
_vertices = new(vertices.Length, Allocator.Persistent); _vertices = new(vertices.Length, Allocator.Persistent);
@@ -148,7 +134,7 @@ public unsafe sealed class Mesh : IDisposable
_indices.CopyFrom(indices); _indices.CopyFrom(indices);
} }
~Mesh() ~MeshClass()
{ {
Dispose(); Dispose();
} }
@@ -233,18 +219,18 @@ public unsafe sealed class Mesh : IDisposable
var v1 = _vertices[i1]; var v1 = _vertices[i1];
var v2 = _vertices[i2]; var v2 = _vertices[i2];
var edge1 = v1.Position - v0.Position; var edge1 = v1.position - v0.position;
var edge2 = v2.Position - v0.Position; var edge2 = v2.position - v0.position;
var faceNormal = Vector3.Cross(edge1.AsVector3(), edge2.AsVector3()); var faceNormal = math.cross(edge1.xyz, edge2.xyz);
_vertices[i0].Normal += faceNormal.AsVector4(); _vertices[i0].normal.xyz += faceNormal;
_vertices[i1].Normal += faceNormal.AsVector4(); _vertices[i1].normal.xyz += faceNormal;
_vertices[i2].Normal += faceNormal.AsVector4(); _vertices[i2].normal.xyz += faceNormal;
} }
for (var i = 0; i < _vertices.Count; i++) for (var i = 0; i < _vertices.Count; i++)
{ {
_vertices[i].Normal = Vector4.Normalize(_vertices[i].Normal); _vertices[i].normal = math.normalize(_vertices[i].normal);
} }
} }
@@ -256,7 +242,7 @@ public unsafe sealed class Mesh : IDisposable
/// </remarks> /// </remarks>
public void ComputeTangents() public void ComputeTangents()
{ {
var bitangents = new Vector4[_vertices.Count]; var bitangents = new float4[_vertices.Count];
for (var i = 0; i < _indices.Count; i += 3) for (var i = 0; i < _indices.Count; i += 3)
{ {
@@ -268,29 +254,24 @@ public unsafe sealed class Mesh : IDisposable
var v1 = _vertices[i1]; var v1 = _vertices[i1];
var v2 = _vertices[i2]; var v2 = _vertices[i2];
var uv0 = _vertices[i0].UV; var uv0 = _vertices[i0].uv;
var uv1 = _vertices[i1].UV; var uv1 = _vertices[i1].uv;
var uv2 = _vertices[i2].UV; var uv2 = _vertices[i2].uv;
var deltaPos1 = v1.Position - v0.Position; var deltaPos1 = v1.position - v0.position;
var deltaPos2 = v2.Position - v0.Position; var deltaPos2 = v2.position - v0.position;
var deltaUV1 = uv1 - uv0; var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0; var deltaUV2 = uv2 - uv0;
var r = 1.0f / (deltaUV1.X * deltaUV2.Y - deltaUV1.Y * deltaUV2.X); var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = (deltaPos1 * deltaUV2.Y - deltaPos2 * deltaUV1.Y) * r; var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
var bitangent = (deltaPos2 * deltaUV1.X - deltaPos1 * deltaUV2.X) * r; var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
for (var j = 0; j < 3; j++) for (var j = 0; j < 3; j++)
{ {
var idx = _indices[i + j]; var idx = _indices[i + j];
var t = _vertices[idx].Tangent; var t = _vertices[idx].tangent;
_vertices[idx].Tangent = new Vector4( _vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
t.X + tangent.X,
t.Y + tangent.Y,
t.Z + tangent.Z,
0.0f // well fill w later
);
bitangents[idx] += bitangent; bitangents[idx] += bitangent;
} }
@@ -298,18 +279,16 @@ public unsafe sealed class Mesh : IDisposable
for (var i = 0; i < _vertices.Count; i++) for (var i = 0; i < _vertices.Count; i++)
{ {
var n = _vertices[i].Normal; var n = _vertices[i].normal;
var t = _vertices[i].Tangent; var t = _vertices[i].tangent;
var n3 = n.AsVector3();
var t3 = t.AsVector3();
var proj = n3 * Vector3.Dot(n3, t3); var proj = n * math.dot(n, t);
t3 = Vector3.Normalize(t3 - proj); t = math.normalize(t - proj);
var b = bitangents[i]; var b = bitangents[i];
var w = Vector3.Dot(Vector3.Cross(n3, t3), b.AsVector3()) < 0.0f ? -1.0f : 1.0f; var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
_vertices[i].Tangent = new Vector4(t3.X, t3.Y, t3.Z, w); _vertices[i].tangent = new float4(t.x, t.y, t.z, w);
} }
} }
@@ -320,20 +299,20 @@ public unsafe sealed class Mesh : IDisposable
{ {
if (_vertices.Count == 0) if (_vertices.Count == 0)
{ {
_boundingBox = Bounds.Zero; _boundingBox = AABB.Zero;
return; return;
} }
var min = new Vector3(float.MaxValue); var min = new float3(float.MaxValue);
var max = new Vector3(float.MinValue); var max = new float3(float.MinValue);
foreach (var vertex in _vertices) foreach (var vertex in _vertices)
{ {
var pos = vertex.Position.AsVector3(); var pos = vertex.position.xyz;
min = Vector3.Min(min, pos); min = math.min(min, pos);
max = Vector3.Max(max, pos); max = math.max(max, pos);
} }
_boundingBox = new Bounds(min, max); _boundingBox = new AABB(min, max);
} }
/// <summary> /// <summary>

View File

@@ -206,7 +206,7 @@ public unsafe class RenderTexture : Texture
/// </summary> /// </summary>
/// <param name="commandList">Command list to record clear commands</param> /// <param name="commandList">Command list to record clear commands</param>
/// <param name="clearColor">Color to clear to</param> /// <param name="clearColor">Color to clear to</param>
public void ClearColor(CommandList commandList, Color16 clearColor) public void ClearColor(CommandList commandList, Color128 clearColor)
{ {
ThrowIfDisposed(); ThrowIfDisposed();

View File

@@ -29,7 +29,7 @@ public readonly struct ResourceHandle : IEquatable<ResourceHandle>
{ {
unchecked unchecked
{ {
return (id * 397) ^ (int)generation; return (id * 397) ^ generation;
} }
} }
@@ -52,6 +52,7 @@ public readonly struct ResourceHandle : IEquatable<ResourceHandle>
public readonly struct TextureHandle : IEquatable<TextureHandle> public readonly struct TextureHandle : IEquatable<TextureHandle>
{ {
private readonly ResourceHandle _resourceHandle; private readonly ResourceHandle _resourceHandle;
private readonly BindlessDescriptor _bindlessDescriptor;
public ResourceHandle ResourceHandle => _resourceHandle; public ResourceHandle ResourceHandle => _resourceHandle;
public static TextureHandle Invalid => new(ResourceHandle.Invalid); public static TextureHandle Invalid => new(ResourceHandle.Invalid);
@@ -59,13 +60,20 @@ public readonly struct TextureHandle : IEquatable<TextureHandle>
internal TextureHandle(ResourceHandle resourceHandle) internal TextureHandle(ResourceHandle resourceHandle)
{ {
_resourceHandle = resourceHandle; _resourceHandle = resourceHandle;
_bindlessDescriptor = BindlessDescriptor.Invalid;
}
internal TextureHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor)
{
_resourceHandle = resourceHandle;
_bindlessDescriptor = descriptor;
} }
public bool IsValid => _resourceHandle.IsValid; public bool IsValid => _resourceHandle.IsValid;
public bool Equals(TextureHandle other) public bool Equals(TextureHandle other)
{ {
return _resourceHandle.Equals(other._resourceHandle); return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@@ -75,7 +83,7 @@ public readonly struct TextureHandle : IEquatable<TextureHandle>
public override int GetHashCode() public override int GetHashCode()
{ {
return _resourceHandle.GetHashCode(); return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
} }
public static bool operator ==(TextureHandle left, TextureHandle right) public static bool operator ==(TextureHandle left, TextureHandle right)
@@ -115,7 +123,7 @@ public readonly struct BufferHandle : IEquatable<BufferHandle>
public bool Equals(BufferHandle other) public bool Equals(BufferHandle other)
{ {
return _resourceHandle.Equals(other._resourceHandle); return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@@ -125,7 +133,7 @@ public readonly struct BufferHandle : IEquatable<BufferHandle>
public override int GetHashCode() public override int GetHashCode()
{ {
return _resourceHandle.GetHashCode(); return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
} }
public static bool operator ==(BufferHandle left, BufferHandle right) public static bool operator ==(BufferHandle left, BufferHandle right)

View File

@@ -1,12 +1,10 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
internal readonly struct TextureInfo internal readonly struct TextureInfo
{ {
public required string Name
{
get; init;
}
public uint RegisterSlot public uint RegisterSlot
{ {
get; init; get; init;
@@ -20,11 +18,6 @@ internal readonly struct TextureInfo
internal readonly struct PropertyInfo internal readonly struct PropertyInfo
{ {
public required string Name
{
get; init;
}
public uint CBufferIndex public uint CBufferIndex
{ {
get; init; get; init;
@@ -43,11 +36,6 @@ internal readonly struct PropertyInfo
internal readonly struct CBufferInfo internal readonly struct CBufferInfo
{ {
public required string Name
{
get; init;
}
public uint Size public uint Size
{ {
get; init; get; init;
@@ -64,36 +52,44 @@ internal readonly struct CBufferInfo
/// </summary> /// </summary>
// TODO: Multi pass and keyword support // TODO: Multi pass and keyword support
public unsafe class Shader : IDisposable public struct Shader : IDisposable
{ {
private readonly string _source; private UnsafeList<CBufferInfo> _constantBuffers;
private UnsafeList<PropertyInfo> _properties;
private readonly List<CBufferInfo> _constantBuffers = new(); private UnsafeList<TextureInfo> _regularTextures; // Add regular texture support
private readonly List<PropertyInfo> _properties = new(); // TODO: Possible to move this to unmanaged heap?
private readonly List<TextureInfo> _regularTextures = new(); // Add regular texture support private Dictionary<string, int> _propertyNameToIdMap;
private readonly Dictionary<string, int> _propertyNameToIdMap = new();
private bool _disposed; private bool _disposed;
internal string Source => _source; internal readonly UnsafeList<CBufferInfo> ConstantBuffers => _constantBuffers;
internal readonly UnsafeList<PropertyInfo> Properties => _properties;
internal readonly UnsafeList<TextureInfo> RegularTextures => _regularTextures;
internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
internal List<CBufferInfo> ConstantBuffers => _constantBuffers; public Shader()
internal List<PropertyInfo> Properties => _properties;
internal List<TextureInfo> RegularTextures => _regularTextures;
internal Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
// TODO: In real production, we should not load the shader source code directly.
internal Shader(string shaderCode)
{ {
_source = shaderCode; _constantBuffers = new(8, Allocator.Persistent);
_properties = new(8, Allocator.Persistent);
_regularTextures = new(8, Allocator.Persistent);
_propertyNameToIdMap = new(8);
_disposed = false;
}
internal void AddProperty(string name, PropertyInfo propertyInfo)
{
var id = _properties.Count;
_properties.Add(propertyInfo);
_propertyNameToIdMap[name] = id;
} }
/// <summary> /// <summary>
/// Gets a unique, stable ID for a shader property. /// Gets a unique, stable ID for a shader property.
/// </summary> /// </summary>
/// <param name="propertyName">The name of the property (e.g., "_Color").</param> /// <param name="propertyName">The name of the shader property.</param>
/// <returns>The integer ID of the property, or -1 if not found.</returns> /// <returns>The integer ID of the property, or -1 if not found.</returns>
public int GetPropertyId(string propertyName) public readonly int GetPropertyId(string propertyName)
{ {
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1; return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
} }
@@ -105,12 +101,12 @@ public unsafe class Shader : IDisposable
return; return;
} }
_constantBuffers.Clear(); _constantBuffers.Dispose();
_properties.Clear(); _properties.Dispose();
_propertyNameToIdMap.Clear(); _regularTextures.Dispose();
_regularTextures.Clear();
GC.SuppressFinalize(this); _propertyNameToIdMap.Clear();
_propertyNameToIdMap = null!;
_disposed = true; _disposed = true;
} }

View File

@@ -1,7 +1,6 @@
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using Win32.Graphics.Dxgi.Common; using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
@@ -9,59 +8,21 @@ namespace Ghost.Graphics.Data;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct Vertex public struct Vertex
{ {
public unsafe struct Semantic public unsafe static class Semantic
{ {
public const Format ALIGNED_FORMAT = Format.R32G32B32A32Float; public const Format ALIGNED_FORMAT = Format.R32G32B32A32Float;
public const int COUNT = 5;
private static readonly byte[] s_positionBytes = Encoding.UTF8.GetBytes("POSITION"); public static readonly FixedString32 position = new("POSITION");
private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL"); public static readonly FixedString32 normal = new("NORMAL");
private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT"); public static readonly FixedString32 tangent = new("TANGENT");
private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR"); public static readonly FixedString32 uv = new("TEXCOORD");
private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD"); public static readonly FixedString32 color = new("COLOR");
public static byte* pPositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
public static byte* pNormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
public static byte* pTangentName => (byte*)Unsafe.AsPointer(ref s_tangentBytes[0]);
public static byte* pColorName => (byte*)Unsafe.AsPointer(ref s_colorBytes[0]);
public static byte* pUVName => (byte*)Unsafe.AsPointer(ref s_uvBytes[0]);
} }
public float4 Position public float4 position;
{ public float4 normal;
get; public float4 tangent;
set; public float4 uv;
} public Color128 color;
public float4 Normal
{
get;
set;
}
public float4 Tangent
{
get;
set;
}
public Color16 Color
{
get;
set;
}
public float4 UV
{
get;
set;
}
public Vertex(float4 position, float4 normal, float4 tangent, Color16 color, float4 uv)
{
Position = position;
Normal = normal;
Tangent = tangent;
Color = color;
UV = uv;
}
} }

View File

@@ -30,6 +30,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Misaki.HighPerformance">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance\bin\Release\net9.0\Misaki.HighPerformance.dll</HintPath>
</Reference>
<Reference Include="Misaki.HighPerformance.Image"> <Reference Include="Misaki.HighPerformance.Image">
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll</HintPath> <HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll</HintPath>
</Reference> </Reference>

View File

@@ -1,4 +1,7 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Drawing;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -7,6 +10,11 @@ namespace Ghost.Graphics.RHI;
/// </summary> /// </summary>
public interface ICommandBuffer : IDisposable public interface ICommandBuffer : IDisposable
{ {
public CommandBufferType Type
{
get;
}
/// <summary> /// <summary>
/// Begins recording commands into this command buffer /// Begins recording commands into this command buffer
/// </summary> /// </summary>
@@ -22,7 +30,7 @@ public interface ICommandBuffer : IDisposable
/// </summary> /// </summary>
/// <param name="renderTarget">Render target to render into</param> /// <param name="renderTarget">Render target to render into</param>
/// <param name="clearColor">Color to clear the render target with</param> /// <param name="clearColor">Color to clear the render target with</param>
public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor); public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor);
/// <summary> /// <summary>
/// Ends the current render pass /// Ends the current render pass
@@ -66,7 +74,7 @@ public interface ICommandBuffer : IDisposable
/// </summary> /// </summary>
/// <param name="mesh">The mesh to be drawn. Must not be null.</param> /// <param name="mesh">The mesh to be drawn. Must not be null.</param>
/// <param name="material">The material to use for rendering the mesh. Must not be null.</param> /// <param name="material">The material to use for rendering the mesh. Must not be null.</param>
public void DrawMesh(Mesh mesh, Material material); public void DrawMesh(MeshClass mesh, MaterialClass material);
/// <summary> /// <summary>
/// Dispatches compute threads /// Dispatches compute threads
@@ -75,6 +83,26 @@ public interface ICommandBuffer : IDisposable
/// <param name="threadGroupCountY">Thread groups in Y dimension</param> /// <param name="threadGroupCountY">Thread groups in Y dimension</param>
/// <param name="threadGroupCountZ">Thread groups in Z dimension</param> /// <param name="threadGroupCountZ">Thread groups in Z dimension</param>
public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1); public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1);
/// <summary>
/// Uploads the specified data to the buffer represented by the given handle.
/// </summary>
/// <typeparam name="T">The unmanaged value type of the elements to upload to the buffer.</typeparam>
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of type
/// <typeparamref name="T"/>.</param>
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
where T : unmanaged;
/// <summary>
/// Uploads texture data to the specified texture resource starting at the given subresource index.
/// </summary>
/// <param name="texture">The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle.</param>
/// <param name="firstSubresource">The index of the first subresource in the texture to receive data. Must be less than the total number of subresources in the texture.</param>
/// <param name="subresources">A reference to the structure containing the subresource data to upload. The data must match the format and layout expected by the texture.</param>
/// <param name="numSubresources">The number of subresources to upload, starting from <paramref name="firstSubresource"/>.
/// Must be greater than zero and not exceed the remaining subresources in the texture.</param>
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources);
} }
/// <summary> /// <summary>
@@ -82,21 +110,21 @@ public interface ICommandBuffer : IDisposable
/// </summary> /// </summary>
public struct ViewportDesc public struct ViewportDesc
{ {
public float X; public float x;
public float Y; public float y;
public float Width; public float width;
public float Height; public float height;
public float MinDepth; public float minDepth;
public float MaxDepth; public float maxDepth;
public ViewportDesc(float width, float height) public ViewportDesc(float width, float height)
{ {
X = 0; x = 0;
Y = 0; y = 0;
Width = width; this.width = width;
Height = height; this.height = height;
MinDepth = 0.0f; minDepth = 0.0f;
MaxDepth = 1.0f; maxDepth = 1.0f;
} }
} }
@@ -122,17 +150,28 @@ public struct RectDesc
/// <summary> /// <summary>
/// Resource states /// Resource states
/// </summary> /// </summary>
[Flags]
public enum ResourceState public enum ResourceState
{ {
Common = 0, Common = 0,
VertexAndConstantBuffer = 0x1, VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 0x2, IndexBuffer = 1 << 1,
RenderTarget = 0x4, RenderTarget = 1 << 2,
UnorderedAccess = 0x8, UnorderedAccess = 1 << 3,
DepthWrite = 0x10, DepthWrite = 1 << 4,
DepthRead = 0x20, DepthRead = 1 << 5,
PixelShaderResource = 0x80, PixelShaderResource = 1 << 6,
CopyDest = 0x400, CopyDest = 1 << 7,
CopySource = 0x800, CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
Present = 0, Present = 0,
} }
public struct SubResourceData
{
public unsafe void* pData;
public nint rowPitch;
public nint slicePitch;
}

View File

@@ -7,6 +7,11 @@ public interface IGraphicsEngine : IDisposable
get; get;
} }
public IResourceDatabase ResourceDatabase
{
get;
}
public IResourceAllocator ResourceAllocator public IResourceAllocator ResourceAllocator
{ {
get; get;
@@ -27,4 +32,14 @@ public interface IGraphicsEngine : IDisposable
/// <param name="desc">Swap chain description</param> /// <param name="desc">Swap chain description</param>
/// <returns>A new swap chain instance</returns> /// <returns>A new swap chain instance</returns>
public ISwapChain CreateSwapChain(SwapChainDesc desc); public ISwapChain CreateSwapChain(SwapChainDesc desc);
/// <summary>
/// Begins a new rendering frame, preparing the graphics context for drawing operations.
/// </summary>
public void BeginFrame();
/// <summary>
/// Completes the current rendering frame and performs any necessary finalization steps.
/// </summary>
public void EndFrame();
} }

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -15,11 +16,9 @@ public interface IShaderPipeline
public interface IPipelineStateController public interface IPipelineStateController
{ {
public void ColectionShader(ReadOnlySpan<Shader> shaders); public void CompileShader(Identifier<Shader> id, string shaderPath);
public void CompileCollected();
public void PreCookPipelineState(); public void PreCookPipelineState();
public IShaderPipeline GetShaderPipeline(Shader shader); public IShaderPipeline GetShaderPipeline(Identifier<Shader> id);
} }

View File

@@ -31,7 +31,7 @@ public interface IRenderDevice : IDisposable
} }
/// <summary> /// <summary>
/// Command buffer types matching D3D12 command list types /// Command buffer types
/// </summary> /// </summary>
public enum CommandBufferType public enum CommandBufferType
{ {

View File

@@ -173,7 +173,8 @@ public struct RenderTargetDesc
Format = desc.Format, Format = desc.Format,
Dimension = desc.Dimension, Dimension = desc.Dimension,
MipLevels = desc.MipLevels, MipLevels = desc.MipLevels,
Usage = usage Usage = usage,
CreationFlags = TextureCreationFlags.Bindless
}; };
} }
} }
@@ -245,6 +246,15 @@ public struct TextureDesc
get; get;
set; set;
} }
/// <summary>
/// Texture creation flags
/// </summary>
public TextureCreationFlags CreationFlags
{
get;
set;
}
} }
/// <summary> /// <summary>
@@ -341,3 +351,10 @@ public enum MemoryType
Upload, // CPU-to-GPU memory Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory Readback // GPU-to-CPU memory
} }
[Flags]
public enum TextureCreationFlags
{
None = 0,
Bindless = 1 << 0
}

View File

@@ -4,40 +4,26 @@ namespace Ghost.Graphics.RHI;
public interface IResourceAllocator public interface IResourceAllocator
{ {
/// <summary>
/// Creates a render target for off-screen rendering
/// </summary>
/// <param name="desc">Render target description</param>
/// <returns>A new render target instance</returns>
public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
/// <summary> /// <summary>
/// Creates a texture resource /// Creates a texture resource
/// </summary> /// </summary>
/// <param name="desc">Texture description</param> /// <param name="desc">Texture description</param>
/// <returns>A new texture handle point to the resource</returns> /// <returns>A new texture handle point to the resource</returns>
public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false); public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false);
/// <summary> /// <summary>
/// Creates a texture resource /// Creates a render target for off-screen rendering
/// </summary> /// </summary>
/// <param name="desc">Texture description</param> /// <param name="desc">Render target description</param>
/// <returns>A new texture instance</returns> /// <returns>A new render target instance</returns>
public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false); public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false);
/// <summary> /// <summary>
/// Creates a buffer resource /// Creates a buffer resource
/// </summary> /// </summary>
/// <param name="desc">Buffer description</param> /// <param name="desc">Buffer description</param>
/// <returns>A new buffer handle point to the resource</returns> /// <returns>A new buffer handle point to the resource</returns>
public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false); public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
/// <summary>
/// Creates a buffer resource
/// </summary>
/// <param name="desc">Buffer description</param>
/// <returns>A new buffer instance</returns>
public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false);
/// <summary> /// <summary>
/// Release a resource given its handle /// Release a resource given its handle

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.Data; using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -32,4 +33,24 @@ public interface IResourceDatabase
/// </summary> /// </summary>
/// <param name="handle">The handle of the resource to be removed.</param> /// <param name="handle">The handle of the resource to be removed.</param>
public void RemoveResource(ResourceHandle handle); public void RemoveResource(ResourceHandle handle);
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh);
public bool HasMesh(Identifier<Mesh> id);
public Mesh GetMesh(Identifier<Mesh> id);
public ref Mesh GetMeshReference(Identifier<Mesh> id);
public void RemoveMesh(Identifier<Mesh> id);
public Identifier<Shader> AddShader(ref readonly Shader shader);
public bool HasShader(Identifier<Shader> id);
public Shader GetShader(Identifier<Shader> id);
public ref Shader GetShaderReference(Identifier<Shader> id);
public void RemoveShader(Identifier<Shader> id);
} }

View File

@@ -100,9 +100,9 @@ float4 PSMain(PixelInput input) : SV_TARGET
"; ";
// High-level bindless objects // High-level bindless objects
private Mesh? _mesh; private MeshClass? _mesh;
private Shader? _shader; private Shader? _shader;
private Material? _material; private MaterialClass? _material;
private Texture2D[]? _textures; private Texture2D[]? _textures;
// Texture file paths for this demo // Texture file paths for this demo
@@ -118,7 +118,7 @@ float4 PSMain(PixelInput input) : SV_TARGET
_mesh = MeshBuilder.CreateCube(0.75f); _mesh = MeshBuilder.CreateCube(0.75f);
_mesh.UploadMeshData(); _mesh.UploadMeshData();
_shader = new Shader(_HLSL_SOURCE); _shader = new ShaderData(_HLSL_SOURCE);
_material = new Material(_shader); _material = new Material(_shader);
_textures = new Texture2D[_textureFiles.Length]; _textures = new Texture2D[_textureFiles.Length];

View File

@@ -34,7 +34,7 @@ internal class RenderSystem
private const uint _FRAME_COUNT = 2; private const uint _FRAME_COUNT = 2;
private readonly IGraphicsEngine _engine = null!; private readonly IGraphicsEngine _graphicsEngine = null!;
private readonly FrameResource[] _frameResources = null!; private readonly FrameResource[] _frameResources = null!;
private readonly Thread _renderThread = null!; private readonly Thread _renderThread = null!;
private readonly AutoResetEvent _shutdownEvent = null!; private readonly AutoResetEvent _shutdownEvent = null!;
@@ -47,14 +47,14 @@ internal class RenderSystem
private bool _isRunning; private bool _isRunning;
private bool _disposed; private bool _disposed;
public IGraphicsEngine GraphicsEngine => _engine; public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public uint CPUFenceValue => _cpuFenceValue; public uint CPUFenceValue => _cpuFenceValue;
public uint GPUFenceValue => _gpuFenceValue; public uint GPUFenceValue => _gpuFenceValue;
public bool IsRunning => _isRunning; public bool IsRunning => _isRunning;
public RenderSystem(GraphicsAPI api) public RenderSystem(GraphicsAPI api)
{ {
_engine = api switch _graphicsEngine = api switch
{ {
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this), GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
_ => throw new NotSupportedException($"Graphics API {api} is not supported.") _ => throw new NotSupportedException($"Graphics API {api} is not supported.")
@@ -84,7 +84,7 @@ internal class RenderSystem
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
var renderer = _engine.CreateRenderer(); var renderer = _graphicsEngine.CreateRenderer();
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer)); ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
return renderer; return renderer;
} }
@@ -166,12 +166,16 @@ internal class RenderSystem
// Only proceed if CPU ready event was signaled // Only proceed if CPU ready event was signaled
if (waitResult == 0) if (waitResult == 0)
{ {
_graphicsEngine.BeginFrame();
foreach (var renderer in _renderers) foreach (var renderer in _renderers)
{ {
renderer.ExecutePendingResize(); renderer.ExecutePendingResize();
renderer.Render(); renderer.Render();
} }
_graphicsEngine.EndFrame();
_gpuFenceValue++; _gpuFenceValue++;
frameResource.gpuReadyEvent.Set(); frameResource.gpuReadyEvent.Set();
} }

View File

@@ -8,10 +8,10 @@ public static class MeshBuilder
/// <summary> /// <summary>
/// Creates a unit cube centered at the origin with size 1. /// Creates a unit cube centered at the origin with size 1.
/// </summary> /// </summary>
public static Mesh CreateCube(float size = 1.0f, Color16 color = default) public static MeshClass CreateCube(float size = 1.0f, Color128 color = default)
{ {
var half = size * 0.5f; var half = size * 0.5f;
var mesh = new Mesh(24, 36); var mesh = new MeshClass(24, 36);
var corners = new Vector4[] var corners = new Vector4[]
{ {
@@ -40,11 +40,11 @@ public static class MeshBuilder
{ {
var vertex = new Vertex var vertex = new Vertex
{ {
Position = corners[face[i]], position = corners[face[i]],
Normal = Vector4.Zero, normal = Vector4.Zero,
Tangent = Vector4.Zero, tangent = Vector4.Zero,
Color = color, color = color,
UV = uvs[i].AsVector4() uv = uvs[i].AsVector4()
}; };
mesh.AddVertex(vertex); mesh.AddVertex(vertex);
} }
@@ -61,11 +61,11 @@ public static class MeshBuilder
/// <summary> /// <summary>
/// Creates a plane on the XZ axis centered at the origin. /// Creates a plane on the XZ axis centered at the origin.
/// </summary> /// </summary>
public static Mesh CreatePlane(float width = 1.0f, float depth = 1.0f, Color16 color = default) public static MeshClass CreatePlane(float width = 1.0f, float depth = 1.0f, Color128 color = default)
{ {
var hw = width * 0.5f; var hw = width * 0.5f;
var hd = depth * 0.5f; var hd = depth * 0.5f;
var mesh = new Mesh(4, 6); var mesh = new MeshClass(4, 6);
mesh.AddVertex(new(new(-hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f))); mesh.AddVertex(new(new(-hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f)));
mesh.AddVertex(new(new(hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 0.0f, 0.0f, 0.0f))); mesh.AddVertex(new(new(hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 0.0f, 0.0f, 0.0f)));
@@ -83,9 +83,9 @@ public static class MeshBuilder
/// <summary> /// <summary>
/// Creates a UV sphere centered at the origin. /// Creates a UV sphere centered at the origin.
/// </summary> /// </summary>
public static Mesh CreateSphere(int latitudeSegments = 16, int longitudeSegments = 24, float radius = 0.5f, Color16 color = default) public static MeshClass CreateSphere(int latitudeSegments = 16, int longitudeSegments = 24, float radius = 0.5f, Color128 color = default)
{ {
var mesh = new Mesh((latitudeSegments + 1) * (longitudeSegments + 1), latitudeSegments * longitudeSegments * 6); var mesh = new MeshClass((latitudeSegments + 1) * (longitudeSegments + 1), latitudeSegments * longitudeSegments * 6);
// Vertices // Vertices
for (var lat = 0; lat <= latitudeSegments; lat++) for (var lat = 0; lat <= latitudeSegments; lat++)
@@ -107,11 +107,11 @@ public static class MeshBuilder
mesh.AddVertex( mesh.AddVertex(
new() new()
{ {
Position = new Vector4(x, y, z, 0.0f) * radius, position = new Vector4(x, y, z, 0.0f) * radius,
Normal = Vector4.Zero, normal = Vector4.Zero,
Tangent = Vector4.Zero, tangent = Vector4.Zero,
Color = color, color = color,
UV = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f) uv = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
}); });
} }
} }

View File

@@ -8,7 +8,7 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here --> <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/DensityStyles/Compact.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Other app resources here --> <!-- Other app resources here -->
</ResourceDictionary> </ResourceDictionary>

View File

@@ -34,7 +34,7 @@ public sealed partial class GraphicsTestWindow : Window
_renderSystem = new (GraphicsAPI.Direct3D12); _renderSystem = new (GraphicsAPI.Direct3D12);
_renderer = _renderSystem.CreateRenderer(); _renderer = _renderSystem.CreateRenderer();
_swapChain = _renderSystem.GraphicsEngine.Device.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel))); _swapChain = _renderSystem.GraphicsEngine.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel)));
_renderer.SetSwapChain(_swapChain); _renderer.SetSwapChain(_swapChain);
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;