From a39f377533a0693b814127a126d09299d17f1d18 Mon Sep 17 00:00:00 2001 From: Misaki Date: Fri, 19 Sep 2025 23:20:15 +0900 Subject: [PATCH] Refactor GPU resource management and rendering pipeline - Introduced `Handle` and `Identifier` 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. --- Ghost.Core/Handle.cs | 88 ++++ Ghost.Entities/Components/ComponentStorage.cs | 28 +- Ghost.Entities/EntityManager.cs | 2 +- Ghost.Entities/Query/QueryFilter.cs | 43 +- Ghost.Entities/Template/QueryEnumerable.cs | 16 +- Ghost.Entities/Template/QueryEnumerable.tt | 2 +- Ghost.Graphics/D3D12/CommandList.cs | 4 +- Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 133 ++++++- Ghost.Graphics/D3D12/D3D12CommandQueue.cs | 9 + Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs | 55 ++- .../D3D12/D3D12PipelineStateController.cs | 74 ++-- Ghost.Graphics/D3D12/D3D12Renderer.cs | 4 +- .../D3D12/D3D12ResourceAllocator.cs | 285 +++++++++---- Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs | 341 +++++++++++++++- Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs | 153 +++---- .../D3D12/Utilities/D3D12PipelineResource.cs | 10 +- Ghost.Graphics/Data/CBufferCache.cs | 28 +- Ghost.Graphics/Data/Color.cs | 32 +- .../Data/GraphicsResourceManager.cs | 54 --- Ghost.Graphics/Data/Material.cs | 204 ---------- Ghost.Graphics/Data/MaterialClass.cs | 376 ++++++++++++++++++ Ghost.Graphics/Data/{Mesh.cs => MeshClass.cs} | 161 ++++---- Ghost.Graphics/Data/RenderTexture.cs | 2 +- Ghost.Graphics/Data/ResourceHandle.cs | 18 +- Ghost.Graphics/Data/Shader.cs | 72 ++-- Ghost.Graphics/Data/Vertex.cs | 67 +--- Ghost.Graphics/Ghost.Graphics.csproj | 3 + Ghost.Graphics/RHI/ICommandBuffer.cs | 85 ++-- Ghost.Graphics/RHI/IGraphicsEngine.cs | 15 + .../RHI/IPipelineStateController.cs | 7 +- Ghost.Graphics/RHI/IRenderDevice.cs | 2 +- Ghost.Graphics/RHI/IRenderTypes.cs | 19 +- Ghost.Graphics/RHI/IResourceAllocator.cs | 26 +- Ghost.Graphics/RHI/IResourceDatabase.cs | 23 +- Ghost.Graphics/RenderPasses/MeshRenderPass.cs | 6 +- Ghost.Graphics/RenderSystem.cs | 12 +- Ghost.Graphics/Utilities/MeshBuilder.cs | 32 +- Ghost.UnitTest/UnitTestApp.xaml | 2 +- .../Windows/GraphicsTestWindow.xaml.cs | 2 +- 39 files changed, 1669 insertions(+), 826 deletions(-) create mode 100644 Ghost.Core/Handle.cs delete mode 100644 Ghost.Graphics/Data/GraphicsResourceManager.cs delete mode 100644 Ghost.Graphics/Data/Material.cs create mode 100644 Ghost.Graphics/Data/MaterialClass.cs rename Ghost.Graphics/Data/{Mesh.cs => MeshClass.cs} (71%) diff --git a/Ghost.Core/Handle.cs b/Ghost.Core/Handle.cs new file mode 100644 index 0000000..86abf85 --- /dev/null +++ b/Ghost.Core/Handle.cs @@ -0,0 +1,88 @@ +namespace Ghost.Core; + +public readonly struct Handle +{ + public readonly int id; + + public readonly int generation; + + public Handle(int id, int generation) + { + this.id = id; + this.generation = generation; + } + + public static Handle Invalid => new(-1, -1); + + public readonly override int GetHashCode() + { + return id.GetHashCode(); + } + + public readonly override bool Equals(object? obj) + { + return obj is Handle id && Equals(id); + } + + public readonly bool Equals(Handle other) + { + return id == other.id; + } + + public readonly int CompareTo(Handle other) + { + return id.CompareTo(other.id); + } + + public static bool operator ==(Handle a, Handle b) + { + return a.Equals(b); + } + + public static bool operator !=(Handle a, Handle b) + { + return !a.Equals(b); + } +} + +public readonly struct Identifier +{ + public readonly int value; + + public Identifier(int value) + { + this.value = value; + } + + public static Identifier Invalid => new(-1); + + public readonly override int GetHashCode() + { + return value.GetHashCode(); + } + + public readonly override bool Equals(object? obj) + { + return obj is Identifier id && Equals(id); + } + + public readonly bool Equals(Identifier other) + { + return value == other.value; + } + + public readonly int CompareTo(Identifier other) + { + return value.CompareTo(other.value); + } + + public static bool operator ==(Identifier a, Identifier b) + { + return a.Equals(b); + } + + public static bool operator !=(Identifier a, Identifier b) + { + return !a.Equals(b); + } +} \ No newline at end of file diff --git a/Ghost.Entities/Components/ComponentStorage.cs b/Ghost.Entities/Components/ComponentStorage.cs index 13e1e27..7ad8a62 100644 --- a/Ghost.Entities/Components/ComponentStorage.cs +++ b/Ghost.Entities/Components/ComponentStorage.cs @@ -472,7 +472,7 @@ internal struct ComponentStorage : IDisposable private int _currentCapacity = 16; private IComponentPool?[] _componentPools = new IComponentPool[16]; - private BitSet?[] _componentEntityMasks = new BitSet[16]; + private UnsafeBitSet?[] _componentEntityMasks = new UnsafeBitSet?[16]; private readonly Dictionary _typeIDMap = new(16); private readonly Dictionary _typeHandleMap = new(16); @@ -487,7 +487,7 @@ internal struct ComponentStorage : IDisposable } internal readonly IReadOnlyList ComponentPools => _componentPools; - internal readonly IReadOnlyList ComponentEntityMasks => _componentEntityMasks; + internal readonly IReadOnlyList ComponentEntityMasks => _componentEntityMasks; internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -621,7 +621,7 @@ internal struct ComponentStorage : IDisposable } [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) || id >= _currentCapacity) @@ -631,17 +631,17 @@ internal struct ComponentStorage : IDisposable } bitSet = _componentEntityMasks[id]; - return bitSet != null; + return bitSet.HasValue; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool TryGetMask([NotNullWhen(true)] out BitSet? bitSet) + public readonly bool TryGetMask([NotNullWhen(true)] out UnsafeBitSet? bitSet) where T : unmanaged, IComponentData { return TryGetMask(TypeHandle.Get(), out bitSet); } - public BitSet GetOrCreateMask() + public UnsafeBitSet GetOrCreateMask() where T : unmanaged, IComponentData { var typeHandle = TypeHandle.Get(); @@ -658,12 +658,12 @@ internal struct ComponentStorage : IDisposable } 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); if (!_typeIDMap.TryGetValue(typeHandle, out var id)) @@ -679,9 +679,9 @@ internal struct ComponentStorage : IDisposable } ref var set = ref _componentEntityMasks[id]; - set ??= new BitSet(); + set ??= new UnsafeBitSet(); - return set; + return set.Value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -704,7 +704,11 @@ internal struct ComponentStorage : IDisposable pool?.Dispose(); } - Array.Clear(_componentPools); + foreach (var bitSet in _componentEntityMasks) + { + bitSet?.Dispose(); + } + _scriptComponentPool.Dispose(); } } \ No newline at end of file diff --git a/Ghost.Entities/EntityManager.cs b/Ghost.Entities/EntityManager.cs index 4abe0c6..2c74559 100644 --- a/Ghost.Entities/EntityManager.cs +++ b/Ghost.Entities/EntityManager.cs @@ -186,7 +186,7 @@ public readonly struct EntityManager : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] 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); } /// diff --git a/Ghost.Entities/Query/QueryFilter.cs b/Ghost.Entities/Query/QueryFilter.cs index 71032e2..9cd6a65 100644 --- a/Ghost.Entities/Query/QueryFilter.cs +++ b/Ghost.Entities/Query/QueryFilter.cs @@ -25,25 +25,20 @@ internal struct QueryFilter() internal List _absent = new(6); internal List _disabled = new(6); - public readonly BitSet ComputeFilterBitMask(World world) + public readonly UnsafeBitSet ComputeFilterBitMask(World world) { - BitSet? allMask = null; - BitSet? anyMask = null; - BitSet? absentMask = null; - - var hasAll = false; - var hasAny = false; - var hasAbsent = false; + UnsafeBitSet? allMask = null; + UnsafeBitSet? anyMask = null; + UnsafeBitSet? absentMask = null; foreach (var typeHandle in _all) { var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); - if (!hasAll) + if (!allMask.HasValue) { - allMask = new BitSet(mask.Length); - allMask.SetAll(); - hasAll = true; + allMask = new UnsafeBitSet(mask.Length); + allMask.Value.SetAll(); } allMask &= mask; @@ -53,10 +48,9 @@ internal struct QueryFilter() { var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); - if (!hasAny) + if (!anyMask.HasValue) { - anyMask = new BitSet(mask.Length); - hasAny = true; + anyMask = new UnsafeBitSet(mask.Length); } anyMask |= mask; @@ -66,31 +60,30 @@ internal struct QueryFilter() { var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); - if (!hasAbsent) + if (!absentMask.HasValue) { - absentMask = new BitSet(mask.Length); - hasAbsent = true; + absentMask = new UnsafeBitSet(mask.Length); } absentMask |= mask; } - var result = new BitSet(world.EntityManager.EntityCount); + var result = new UnsafeBitSet(world.EntityManager.EntityCount); 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; diff --git a/Ghost.Entities/Template/QueryEnumerable.cs b/Ghost.Entities/Template/QueryEnumerable.cs index 062fbc9..a164138 100644 --- a/Ghost.Entities/Template/QueryEnumerable.cs +++ b/Ghost.Entities/Template/QueryEnumerable.cs @@ -42,7 +42,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -213,7 +213,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -389,7 +389,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -570,7 +570,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -756,7 +756,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -947,7 +947,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -1143,7 +1143,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { @@ -1344,7 +1344,7 @@ public struct QueryEnumerable private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem Current { diff --git a/Ghost.Entities/Template/QueryEnumerable.tt b/Ghost.Entities/Template/QueryEnumerable.tt index 876b0fe..923a4fa 100644 --- a/Ghost.Entities/Template/QueryEnumerable.tt +++ b/Ghost.Entities/Template/QueryEnumerable.tt @@ -66,7 +66,7 @@ public struct QueryEnumerable<<#= generics #>> private int _index; private readonly int _count; - private BitSet _filterMask; + private UnsafeBitSet _filterMask; public QueryItem<<#= generics #>> Current { diff --git a/Ghost.Graphics/D3D12/CommandList.cs b/Ghost.Graphics/D3D12/CommandList.cs index d77be30..62c978f 100644 --- a/Ghost.Graphics/D3D12/CommandList.cs +++ b/Ghost.Graphics/D3D12/CommandList.cs @@ -33,7 +33,7 @@ public unsafe class CommandList /// /// The mesh to draw /// The bindless material to use - 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) material.Bind(this); @@ -62,7 +62,7 @@ public unsafe class CommandList _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); } diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index 0508e64..e64cb8f 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -1,5 +1,6 @@ using Ghost.Graphics.Data; using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel.Utilities; using Win32; using Win32.Graphics.Direct3D; using Win32.Graphics.Direct3D12; @@ -16,23 +17,35 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer private ComPtr _commandList; private readonly D3D12PipelineStateController _stateController; + private readonly D3D12ResourceDatabase _resourceDatabase; + private readonly D3D12ResourceAllocator _resourceAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly CommandBufferType _type; private bool _isRecording; private bool _disposed; + public CommandBufferType Type => _type; + 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; var commandListType = ConvertCommandBufferType(type); device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof(), _allocator.GetVoidAddressOf()); - device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof(), _commandList.GetVoidAddressOf()); + device.NativeDevice->CreateCommandList1(0u, commandListType, CommandListFlags.None, __uuidof(), _commandList.GetVoidAddressOf()); _stateController = stateController; + _resourceDatabase = resourceDatabase; + _resourceAllocator = resourceAllocator; _descriptorAllocator = descriptorAllocator; // Command lists are created in recording state, so close it @@ -40,8 +53,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _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() { + ThrowIfDisposed(); + if (_isRecording) { throw new InvalidOperationException("Command buffer is already recording"); @@ -54,16 +87,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void End() { - if (!_isRecording) - { - throw new InvalidOperationException("Command buffer is not recording"); - } + ThrowIfDisposed(); + ThrowIfNotRecording(); _commandList.Get()->Close(); _isRecording = false; } - public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor) + public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor) { // TODO: Implement render pass begin throw new NotImplementedException(); @@ -77,18 +108,27 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer 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); } public void SetScissorRect(RectDesc rect) { + ThrowIfDisposed(); + ThrowIfNotRecording(); + var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom); _commandList.Get()->RSSetScissorRects(1, &d3d12Rect); } public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after) { + ThrowIfDisposed(); + ThrowIfNotRecording(); + if (resource is D3D12Texture d3d12Texture) { _commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource, @@ -118,8 +158,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } // 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) var shaderPipeline = _stateController.GetShaderPipeline(material.Shader); if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline) @@ -128,8 +171,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } // Set root signature and pipeline state - _commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.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 var heaps = stackalloc ID3D12DescriptorHeap*[2]; @@ -142,7 +185,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer foreach (var cbufferInfo in material.Shader.ConstantBuffers) { 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) @@ -163,9 +207,67 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1) { + ThrowIfDisposed(); + ThrowIfNotRecording(); + _commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); } + public void Upload(BufferHandle buffer, ReadOnlySpan 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) { return type switch @@ -197,11 +299,20 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void Dispose() { + if (_isRecording) + { + throw new InvalidOperationException("Command buffer is still recording"); + } + if (_disposed) + { return; + } _commandList.Dispose(); _allocator.Dispose(); _disposed = true; + + GC.SuppressFinalize(this); } } diff --git a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs index 342c270..6f7e004 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs @@ -42,6 +42,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue pDevice->CreateFence(0, FenceFlags.None, __uuidof(), _fence.GetVoidAddressOf()); } + ~D3D12CommandQueue() + { + Dispose(); + } + public void Submit(ICommandBuffer commandBuffer) { if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer) @@ -113,12 +118,16 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Dispose() { if (_disposed) + { return; + } _fenceEvent?.Dispose(); _fence.Dispose(); _queue.Dispose(); _disposed = true; + + GC.SuppressFinalize(this); } } diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs index 73c9ca4..da39666 100644 --- a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs +++ b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs @@ -9,12 +9,17 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine #endif private readonly D3D12RenderDevice _device; + private readonly D3D12PipelineStateController _stateController; private readonly D3D12DescriptorAllocator _descriptorAllocator; + + private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceAllocator _resourceAllocator; - private readonly D3D12PipelineStateController _stateController; + private readonly D3D12CommandBuffer _copyCommandBuffer; + public IRenderDevice Device => _device; + public IResourceDatabase ResourceDatabase => _resourceDatabase; public IResourceAllocator ResourceAllocator => _resourceAllocator; public IPipelineStateController PipelineStateController => _stateController; @@ -24,12 +29,25 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine #if DEBUG _debugLayer = new(); #endif - _device = new(); _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() @@ -39,7 +57,13 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine 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) @@ -47,15 +71,30 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine 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() { + _copyCommandBuffer.Dispose(); _stateController.Dispose(); - _descriptorAllocator.Dispose(); - _resourceAllocator.Dispose(); - _device.Dispose(); + _resourceAllocator.Dispose(); + _resourceDatabase.Dispose(); + + _descriptorAllocator.Dispose(); + _device.Dispose(); #if DEBUG _debugLayer.Dispose(); #endif + + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs b/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs index 29aca4b..07044d7 100644 --- a/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs +++ b/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs @@ -1,4 +1,5 @@ -using Ghost.Graphics.D3D12.Utilities; +using Ghost.Core; +using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Win32; @@ -8,7 +9,7 @@ using Win32.Graphics.Dxgi.Common; namespace Ghost.Graphics.D3D12; -internal class D3D12ShaderPipeline : IShaderPipeline +internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable { public ComPtr rootSignature; public ComPtr pipelineState; @@ -21,42 +22,51 @@ internal class D3D12ShaderPipeline : IShaderPipeline { get; init; } + + public void Dispose() + { + rootSignature.Dispose(); + pipelineState.Dispose(); + samplerHeap.Dispose(); + vsResult.Dispose(); + psResult.Dispose(); + csResult.Dispose(); + } } internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable { private readonly ID3D12Device14* _device; + private readonly D3D12ResourceDatabase _resourceDatabase; - private readonly Dictionary _shaderPipelines; + private readonly Dictionary, D3D12ShaderPipeline> _shaderPipelines; - public D3D12PipelineStateController(D3D12RenderDevice device) + public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase) { _device = device.NativeDevice; + _resourceDatabase = resourceDatabase; + _shaderPipelines = new(); } - // TODO: Support compute shaders - public void ColectionShader(ReadOnlySpan shaders) + public void CompileShader(Identifier id, string shaderPath) { - foreach (var shader in shaders) - { - _shaderPipelines.TryAdd(shader, new() - { - Type = PipelineType.Graphics - }); - } - } + 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); - public void CompileCollected() - { - 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); + var shader = _resourceDatabase.GetShader(id); - kvp.Value.vsResult = vsResult; - kvp.Value.psResult = psResult; - } + D3D12ShaderCompiler.PerformDXCReflection(shader, vsResult.reflection.Get()); + 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) @@ -219,31 +229,29 @@ internal unsafe class D3D12PipelineStateController : IPipelineStateController, I { foreach (var kvp in _shaderPipelines) { - CreateRootSignature(kvp.Key, kvp.Value); + var shader = _resourceDatabase.GetShader(kvp.Key); + + CreateRootSignature(shader, kvp.Value); CreatePipelineStateObject(kvp.Value); CreateSamplerHeap(kvp.Value); } } - public IShaderPipeline GetShaderPipeline(Shader shader) + public IShaderPipeline GetShaderPipeline(Identifier id) { - if (_shaderPipelines.TryGetValue(shader, out var pipeline)) + if (_shaderPipelines.TryGetValue(id, out var 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() { foreach (var kvp in _shaderPipelines) { - kvp.Value.rootSignature.Dispose(); - kvp.Value.pipelineState.Dispose(); - kvp.Value.samplerHeap.Dispose(); - kvp.Value.vsResult.Dispose(); - kvp.Value.psResult.Dispose(); + kvp.Value.Dispose(); } } -} +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index b4a88dc..ef9264c 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -169,7 +169,7 @@ public unsafe class D3D12Renderer : IRenderer 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); @@ -207,7 +207,7 @@ public unsafe class D3D12Renderer : IRenderer // 3. Apply post-processing effects (tone mapping, gamma correction, etc.) // 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.EndRenderPass(); diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index 2e374fb..2045678 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -1,4 +1,5 @@ -using Ghost.Graphics.Data; +using Ghost.Core; +using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; @@ -10,32 +11,8 @@ using static Win32.Graphics.D3D12MemoryAllocator.Apis; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12ResourceAllocator : IResourceAllocator, 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_TEXTURE2D_DIMENSION = 16384u; private const uint _MAX_TEXTURE3D_DIMENSION = 2048u; @@ -45,8 +22,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); private UnsafeQueue _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); private Guid* IID_NULL @@ -60,7 +37,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator ResourceDescription.Tex2D( + 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 { @@ -131,10 +136,58 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocatorCreateShaderResourceView(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); @@ -150,12 +203,11 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator CreateMesh(ReadOnlySpan vertices, ReadOnlySpan indices) { - return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc); + var vertexBufferDesc = new BufferDesc + { + Size = (ulong)(vertices.Length * Unsafe.SizeOf()), + Stride = (uint)Unsafe.SizeOf(), + 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 IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false) + public Identifier CreateMaterial(Identifier shader) { - 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((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 @@ -319,6 +423,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator 0) { var handle = _temResources.Peek(); - - if (_allocations.TryGetElementAt(handle.id, handle.generation, out var info) - && info.Allocated) + ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist); + if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue) { break; } @@ -338,22 +477,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator 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 - foreach (var info in _allocations) + + foreach (var handle in _temResources) { - info.Dispose(); + ReleaseResource(handle); } - _allocations.Dispose(); _temResources.Dispose(); _allocator.Release(); + + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index 2a0c433..d6b6356 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -1,11 +1,338 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Ghost.Core; +using Ghost.Graphics.Data; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.Collections; +using Misaki.HighPerformance.LowLevel.Collections; +using Win32.Graphics.D3D12MemoryAllocator; +using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; -internal class D3D12ResourceDatabase +internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable { -} + private struct Slot + { + 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 _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> _meshDatas; + private readonly DynamicArray> _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(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 AddMesh(ref readonly Mesh mesh) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var id = new Identifier(_meshDatas.Count); + _meshDatas.Add(new() + { + value = mesh, + isValid = true + }); + + return id; + } + + public bool HasMesh(Identifier id) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid; + } + + public Mesh GetMesh(Identifier 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 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 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 AddShader(ref readonly Shader shader) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var id = new Identifier(_shaders.Count); + _shaders.Add(new() + { + value = shader, + isValid = true + }); + + return id; + } + + public bool HasShader(Identifier id) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].isValid; + } + + public Shader GetShader(Identifier 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 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 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); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs index b0c0a9e..3ce255c 100644 --- a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs +++ b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs @@ -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 compiler = default; using ComPtr utils = default; + using ComPtr includeHandler = default; // Create DXC compiler and utils - DxcCreateInstance(CLSID_DxcCompiler, __uuidof(), compiler.GetVoidAddressOf()); - DxcCreateInstance(CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf()); + DxcCreateInstance(in CLSID_DxcCompiler, __uuidof(), compiler.GetVoidAddressOf()); + DxcCreateInstance(in CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf()); + utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()); // Create source blob using ComPtr sourceBlob = default; - var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shader.Source); - fixed (byte* sourceBytesPtr = sourceBytes) + //var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath); + + 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 var argsArray = new string[] { - "-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6) - "-E", GetEntryPoint(stage), // Entry point - "-HV", "2021", // HLSL version 2021 (required for SM 6.6) - "-enable-16bit-types", // Enable 16-bit types - "-O3", // Optimization level - "-Qstrip_debug" // Strip debug info but KEEP reflection + "-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6) + "-E", GetEntryPoint(stage), // Entry point + "-HV", "2021", // HLSL version 2021 (required for SM 6.6) + "-enable-16bit-types", // Enable 16-bit types + "-O3", // Optimization level + "-Qstrip_debug" // Strip debug info but KEEP reflection }; // Convert to wide strings (DXC expects LPCWSTR) @@ -116,7 +120,7 @@ internal unsafe static class D3D12ShaderCompiler Encoding = DXC_CP_UTF8 }; - compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof(), result.GetVoidAddressOf()); + compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof(), result.GetVoidAddressOf()); } // Check compilation result @@ -203,85 +207,92 @@ internal unsafe static class D3D12ShaderCompiler ShaderDescription shaderDesc; reflection.Get()->GetDesc(&shaderDesc); - var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name); - var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name); + var cbufferRegistry = new Dictionary(); + var textureRegistry = new Dictionary(); for (uint i = 0; i < shaderDesc.BoundResources; i++) { ShaderInputBindDescription bindDesc; reflection.Get()->GetResourceBindingDesc(i, &bindDesc); - if (bindDesc.Type == ShaderInputType.ConstantBuffer) + switch (bindDesc.Type) { - var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); - if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) + case ShaderInputType.ConstantBuffer: { - continue; - } - - var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); - ShaderBufferDescription cbufferDesc; - cbuffer->GetDesc(&cbufferDesc); - - var cbufferInfo = new CBufferInfo - { - Name = cbufferName, - Size = cbufferDesc.Size, - RegisterSlot = bindDesc.BindPoint - }; - cbufferRegistry.Add(cbufferName, cbufferInfo); - - for (uint j = 0; j < cbufferDesc.Variables; j++) - { - var variable = cbuffer->GetVariableByIndex(j); - ShaderVariableDescription varDesc; - variable->GetDesc(&varDesc); - - var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); - if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName)) + var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); + if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) { continue; } - var propInfo = new PropertyInfo + var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); + ShaderBufferDescription cbufferDesc; + cbuffer->GetDesc(&cbufferDesc); + + var cbufferInfo = new CBufferInfo { - Name = variableName, - CBufferIndex = cbufferInfo.RegisterSlot, - ByteOffset = varDesc.StartOffset, - Size = varDesc.Size + Size = cbufferDesc.Size, + RegisterSlot = bindDesc.BindPoint + }; + cbufferRegistry.Add(cbufferName, cbufferInfo); + + for (uint j = 0; j < cbufferDesc.Variables; j++) + { + var variable = cbuffer->GetVariableByIndex(j); + ShaderVariableDescription varDesc; + variable->GetDesc(&varDesc); + + var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); + if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName)) + { + continue; + } + + var propInfo = new PropertyInfo + { + 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 - var newId = shader.Properties.Count; - shader.Properties.Add(propInfo); - shader.PropertyNameToIdMap.Add(variableName, newId); + textureRegistry.Add(textureName, textureInfo); + break; } } - else if (bindDesc.Type == ShaderInputType.Texture) - { - var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); - if (textureName == null || textureRegistry.ContainsKey(textureName)) - { - continue; - } - - // ALL texture input slots are regular textures! - // Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index] - var textureInfo = new TextureInfo - { - Name = textureName, - RegisterSlot = bindDesc.BindPoint, - RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs - }; - - textureRegistry.Add(textureName, textureInfo); - } } shader.ConstantBuffers.Clear(); - shader.ConstantBuffers.AddRange(cbufferRegistry.Values); + foreach (var cbuf in cbufferRegistry.Values) + { + shader.ConstantBuffers.Add(cbuf); + } shader.RegularTextures.Clear(); - shader.RegularTextures.AddRange(textureRegistry.Values); + foreach (var tex in textureRegistry.Values) + { + shader.RegularTextures.Add(tex); + } } } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs index 7420953..48cbb51 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs @@ -10,11 +10,11 @@ internal unsafe static class D3D12PipelineResource public const int BACK_BUFFER_COUNT = 2; 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.pNormalName, 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.pColorName, 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.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, 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.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, 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.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; diff --git a/Ghost.Graphics/Data/CBufferCache.cs b/Ghost.Graphics/Data/CBufferCache.cs index e94acd5..572d88b 100644 --- a/Ghost.Graphics/Data/CBufferCache.cs +++ b/Ghost.Graphics/Data/CBufferCache.cs @@ -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.Helpers; -using System.Runtime.CompilerServices; namespace Ghost.Graphics.Data; -internal struct CBufferCache : IDisposable +internal struct CBufferCache { public UnsafeArray CpuData { @@ -14,33 +11,18 @@ internal struct CBufferCache : IDisposable set; } - public GraphicsBuffer GpuResource + public BufferHandle GpuResource { get; } private readonly uint _alignedSize; - internal unsafe CBufferCache(uint bufferSize) + internal unsafe CBufferCache(BufferHandle buffer, uint bufferSize) { _alignedSize = (bufferSize + 255u) & ~255u; CpuData = new((int)_alignedSize, Allocator.Persistent); - GpuResource = GraphicsBuffer.Create(_alignedSize, GraphicsBuffer.Usage.Constant); - 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(); + GpuResource = buffer; } } \ No newline at end of file diff --git a/Ghost.Graphics/Data/Color.cs b/Ghost.Graphics/Data/Color.cs index 6632a19..da38f72 100644 --- a/Ghost.Graphics/Data/Color.cs +++ b/Ghost.Graphics/Data/Color.cs @@ -7,14 +7,14 @@ namespace Ghost.Graphics.Data; /// Represents a color with 4 bytes components. /// [StructLayout(LayoutKind.Sequential, Size = 4)] -public struct Color4 : IEquatable +public struct Color32 : IEquatable { public byte r; public byte g; public byte b; 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.g = g; @@ -22,24 +22,24 @@ public struct Color4 : IEquatable this.a = a; } - public Color4(Color color) + public Color32(Color color) : 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)) { } - 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; } 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() @@ -47,12 +47,12 @@ public struct Color4 : IEquatable 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); } - public static bool operator !=(Color4 left, Color4 right) + public static bool operator !=(Color32 left, Color32 right) { return !(left == right); } @@ -62,14 +62,14 @@ public struct Color4 : IEquatable /// Represents a color with 16 bytes components. /// [StructLayout(LayoutKind.Sequential, Size = 16)] -public struct Color16 : IEquatable +public struct Color128 : IEquatable { public float r; public float g; public float b; 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.g = g; @@ -77,24 +77,24 @@ public struct Color16 : IEquatable 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) { } - 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) { } - 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); } 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() @@ -102,12 +102,12 @@ public struct Color16 : IEquatable 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); } - public static bool operator !=(Color16 left, Color16 right) + public static bool operator !=(Color128 left, Color128 right) { return !(left == right); } diff --git a/Ghost.Graphics/Data/GraphicsResourceManager.cs b/Ghost.Graphics/Data/GraphicsResourceManager.cs deleted file mode 100644 index 6c98e28..0000000 --- a/Ghost.Graphics/Data/GraphicsResourceManager.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Ghost.Graphics.Data; - -public struct BatchMaterialID : IEquatable -{ - 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 _materials = new(); - private readonly Dictionary _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; - } -} \ No newline at end of file diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs deleted file mode 100644 index d678c25..0000000 --- a/Ghost.Graphics/Data/Material.cs +++ /dev/null @@ -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; - -/// -/// Material implementation for bindless rendering with SM 6.6 support -/// -public unsafe class Material : IDisposable -{ - private readonly CBufferCache[] _cbufferCaches; - - private bool _disposed; - - internal ReadOnlySpan 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(); - } - } - - /// - /// Sets a float property in the material's constant buffer. - /// - /// The ID of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetFloat(int propertyId, in float value) - { - WriteToCache(propertyId, in value); - } - - /// - /// Sets a float property in the material's constant buffer. - /// - /// The name of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetFloat(string propertyName, in float value) - { - SetFloat(Shader.GetPropertyId(propertyName), in value); - } - - /// - /// Sets a uint property in the material's constant buffer (useful for texture indices). - /// - /// The ID of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetUInt(int propertyId, in uint value) - { - WriteToCache(propertyId, in value); - } - - /// - /// Sets a uint property in the material's constant buffer (useful for texture indices). - /// - /// The name of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetUInt(string propertyName, in uint value) - { - SetUInt(Shader.GetPropertyId(propertyName), in value); - } - - /// - /// Sets a Vector property in the material's constant buffer. - /// - /// The ID of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetVector(int propertyId, in Vector4 value) - { - WriteToCache(propertyId, in value); - } - - /// - /// Sets a Vector property in the material's constant buffer. - /// - /// The name of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetVector(string propertyName, in Vector4 value) - { - SetVector(Shader.GetPropertyId(propertyName), in value); - } - - /// - /// Sets a Matrix property in the material's constant buffer. - /// - /// The ID of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetMatrix(int propertyId, in Matrix4x4 value) - { - WriteToCache(propertyId, in value); - } - - /// - /// Sets a Matrix property in the material's constant buffer. - /// - /// The name of the property to set. - /// The value to set for the property. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetMatrix(string propertyName, in Matrix4x4 value) - { - SetMatrix(Shader.GetPropertyId(propertyName), in value); - } - - /// - /// Sets a texture index for a shader property (for bindless texture access) - /// - /// The name of the shader property (e.g., "_TextureIndex1") - /// The bindless texture to reference - public void SetTexture(string propertyName, Texture2D texture) - { - SetUInt(propertyName, texture.DescriptorIndex); - } - - /// - /// Sets the mesh buffer indices for bindless vertex and index buffer access - /// - /// The mesh whose buffer indices to set - /// The name of the vertex buffer index property (e.g., "_VertexBufferIndex") - /// The name of the index buffer index property (e.g., "_IndexBufferIndex") - public void SetMeshBuffer(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") - { - SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex); - SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex); - } - - private unsafe void WriteToCache(int propertyId, in T value) - where T : unmanaged - { - if (propertyId == -1) - { - throw new ArgumentException("Property ID is invalid."); - } - - var propInfo = Shader.Properties[propertyId]; - if (propInfo.Size != sizeof(T)) - { - throw new ArgumentException($"Property '{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); - } - - /// - /// Uploads the material data to the GPU. - /// - 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; - } -} \ No newline at end of file diff --git a/Ghost.Graphics/Data/MaterialClass.cs b/Ghost.Graphics/Data/MaterialClass.cs new file mode 100644 index 0000000..fc80799 --- /dev/null +++ b/Ghost.Graphics/Data/MaterialClass.cs @@ -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 _cBufferCaches; + + public Identifier 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(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); + } + + /// + /// Sets a float property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetFloat(int propertyId, in float value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a float property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFloat(string propertyName, in float value) + { + SetFloat(_shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a uint property in the material's constant buffer (useful for texture indices). + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetUInt(int propertyId, in uint value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a uint property in the material's constant buffer (useful for texture indices). + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt(string propertyName, in uint value) + { + SetUInt(_shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetVector(int propertyId, in Vector4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetVector(string propertyName, in Vector4 value) + { + SetVector(_shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetMatrix(int propertyId, in Matrix4x4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatrix(string propertyName, in Matrix4x4 value) + { + SetMatrix(_shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a texture index for a shader property (for bindless texture access) + /// + /// The name of the shader property (e.g., "_TextureIndex1") + /// The bindless texture to reference + public void SetTexture(string propertyName, Texture2D texture) + { + SetUInt(propertyName, texture.DescriptorIndex); + } + + /// + /// Sets the mesh buffer indices for bindless vertex and index buffer access + /// + /// The mesh whose buffer indices to set + /// The name of the vertex buffer index property (e.g., "_VertexBufferIndex") + /// The name of the index buffer index property (e.g., "_IndexBufferIndex") + public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") + { + SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex); + SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex); + } + + /// + /// Uploads all cached material data to the GPU using the specified command buffer. + /// + /// The command buffer used to perform the upload operations to the GPU. Cannot be null. + public readonly void UploadMaterialData(ICommandBuffer cmb) + { + foreach (var cache in _materialData._cBufferCaches) + { + cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan()); + } + } +} + +/// +/// Material implementation for bindless rendering with SM 6.6 support +/// +public unsafe class MaterialClass : IDisposable +{ + private readonly UnsafeArray _cbufferCaches; + + private bool _disposed; + + internal ReadOnlySpan CBufferCaches => _cbufferCaches.AsSpan(); + + public Identifier Shader + { + get; set; + } + + public MaterialClass(Identifier 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((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); + } + } + } + + /// + /// Sets a float property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFloat(int propertyId, in float value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a float property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFloat(string propertyName, in float value) + { + SetFloat(Shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a uint property in the material's constant buffer (useful for texture indices). + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt(int propertyId, in uint value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a uint property in the material's constant buffer (useful for texture indices). + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt(string propertyName, in uint value) + { + SetUInt(Shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetVector(int propertyId, in Vector4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetVector(string propertyName, in Vector4 value) + { + SetVector(Shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatrix(int propertyId, in Matrix4x4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatrix(string propertyName, in Matrix4x4 value) + { + SetMatrix(Shader.GetPropertyId(propertyName), in value); + } + + /// + /// Sets a texture index for a shader property (for bindless texture access) + /// + /// The name of the shader property (e.g., "_TextureIndex1") + /// The bindless texture to reference + public void SetTexture(string propertyName, Texture2D texture) + { + SetUInt(propertyName, texture.DescriptorIndex); + } + + /// + /// Sets the mesh buffer indices for bindless vertex and index buffer access + /// + /// The mesh whose buffer indices to set + /// The name of the vertex buffer index property (e.g., "_VertexBufferIndex") + /// The name of the index buffer index property (e.g., "_IndexBufferIndex") + public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") + { + SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex); + SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex); + } + + private unsafe void WriteToCache(int propertyId, in T value) + where T : unmanaged + { + if (propertyId == -1) + { + throw new ArgumentException("Property ID is invalid."); + } + + var propInfo = Shader.Properties[propertyId]; + if (propInfo.Size != sizeof(T)) + { + throw new ArgumentException($"Property '{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); + } + + /// + /// Uploads the material data to the GPU. + /// + 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; + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Data/Mesh.cs b/Ghost.Graphics/Data/MeshClass.cs similarity index 71% rename from Ghost.Graphics/Data/Mesh.cs rename to Ghost.Graphics/Data/MeshClass.cs index 154289d..91de316 100644 --- a/Ghost.Graphics/Data/Mesh.cs +++ b/Ghost.Graphics/Data/MeshClass.cs @@ -2,82 +2,68 @@ using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; 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 System.Numerics; using System.Runtime.CompilerServices; using Win32.Graphics.Direct3D12; using Win32.Graphics.Dxgi.Common; namespace Ghost.Graphics.Data; -/// -/// The CPU-side mesh data structure. -/// -public struct MeshData +public struct Mesh { public UnsafeList vertices; - public UnsafeList indices; + public UnsafeList indices; public AABB boundingBox; - public MeshHandle handle; - - public MeshData() - { - handle = MeshHandle.Invalid; - } -} - -/// -/// The GPU-side mesh handle containing buffer references. -/// -public struct MeshHandle -{ public BufferHandle vertexBuffer; public BufferHandle indexBuffer; - public static MeshHandle Invalid => new() { vertexBuffer = BufferHandle.Invalid, indexBuffer = BufferHandle.Invalid }; - - public readonly bool IsValid => vertexBuffer.IsValid && indexBuffer.IsValid; -} - -public struct BatchMeshID : IEquatable -{ - public int value; - - public static BatchMeshID Null => new() { value = -1 }; - - public readonly override int GetHashCode() + public Mesh() { - return value.GetHashCode(); + vertexBuffer = BufferHandle.Invalid; + indexBuffer = BufferHandle.Invalid; } - public readonly override bool Equals(object? obj) + internal Mesh(ReadOnlySpan vertices, ReadOnlySpan 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); - } - - public static bool operator ==(BatchMeshID a, BatchMeshID b) - { - return a.Equals(b); - } - - public static bool operator !=(BatchMeshID a, BatchMeshID b) - { - return !a.Equals(b); + vertices.Dispose(); + indices.Dispose(); } } -public unsafe sealed class Mesh : IDisposable +public unsafe sealed class MeshClass : IDisposable { private UnsafeList _vertices; private UnsafeList _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); _indices = new(initialIndexCapacity, Allocator.Persistent); } - public Mesh(ReadOnlySpan vertices, ReadOnlySpan indices) + public MeshClass(ReadOnlySpan vertices, ReadOnlySpan indices) : this(vertices.Length, indices.Length) { _vertices = new(vertices.Length, Allocator.Persistent); @@ -148,7 +134,7 @@ public unsafe sealed class Mesh : IDisposable _indices.CopyFrom(indices); } - ~Mesh() + ~MeshClass() { Dispose(); } @@ -233,18 +219,18 @@ public unsafe sealed class Mesh : IDisposable var v1 = _vertices[i1]; var v2 = _vertices[i2]; - var edge1 = v1.Position - v0.Position; - var edge2 = v2.Position - v0.Position; - var faceNormal = Vector3.Cross(edge1.AsVector3(), edge2.AsVector3()); + var edge1 = v1.position - v0.position; + var edge2 = v2.position - v0.position; + var faceNormal = math.cross(edge1.xyz, edge2.xyz); - _vertices[i0].Normal += faceNormal.AsVector4(); - _vertices[i1].Normal += faceNormal.AsVector4(); - _vertices[i2].Normal += faceNormal.AsVector4(); + _vertices[i0].normal.xyz += faceNormal; + _vertices[i1].normal.xyz += faceNormal; + _vertices[i2].normal.xyz += faceNormal; } 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 /// public void ComputeTangents() { - var bitangents = new Vector4[_vertices.Count]; + var bitangents = new float4[_vertices.Count]; for (var i = 0; i < _indices.Count; i += 3) { @@ -268,29 +254,24 @@ public unsafe sealed class Mesh : IDisposable var v1 = _vertices[i1]; var v2 = _vertices[i2]; - var uv0 = _vertices[i0].UV; - var uv1 = _vertices[i1].UV; - var uv2 = _vertices[i2].UV; + var uv0 = _vertices[i0].uv; + var uv1 = _vertices[i1].uv; + var uv2 = _vertices[i2].uv; - var deltaPos1 = v1.Position - v0.Position; - var deltaPos2 = v2.Position - v0.Position; + var deltaPos1 = v1.position - v0.position; + var deltaPos2 = v2.position - v0.position; var deltaUV1 = uv1 - uv0; var deltaUV2 = uv2 - uv0; - var r = 1.0f / (deltaUV1.X * deltaUV2.Y - deltaUV1.Y * deltaUV2.X); - var tangent = (deltaPos1 * deltaUV2.Y - deltaPos2 * deltaUV1.Y) * r; - var bitangent = (deltaPos2 * deltaUV1.X - deltaPos1 * deltaUV2.X) * r; + var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; + var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; for (var j = 0; j < 3; j++) { var idx = _indices[i + j]; - var t = _vertices[idx].Tangent; - _vertices[idx].Tangent = new Vector4( - t.X + tangent.X, - t.Y + tangent.Y, - t.Z + tangent.Z, - 0.0f // we’ll fill w later - ); + var t = _vertices[idx].tangent; + _vertices[idx].tangent.xyz = t.xyz + tangent.xyz; bitangents[idx] += bitangent; } @@ -298,18 +279,16 @@ public unsafe sealed class Mesh : IDisposable for (var i = 0; i < _vertices.Count; i++) { - var n = _vertices[i].Normal; - var t = _vertices[i].Tangent; - var n3 = n.AsVector3(); - var t3 = t.AsVector3(); + var n = _vertices[i].normal; + var t = _vertices[i].tangent; - var proj = n3 * Vector3.Dot(n3, t3); - t3 = Vector3.Normalize(t3 - proj); + var proj = n * math.dot(n, t); + t = math.normalize(t - proj); 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) { - _boundingBox = Bounds.Zero; + _boundingBox = AABB.Zero; return; } - var min = new Vector3(float.MaxValue); - var max = new Vector3(float.MinValue); + var min = new float3(float.MaxValue); + var max = new float3(float.MinValue); foreach (var vertex in _vertices) { - var pos = vertex.Position.AsVector3(); - min = Vector3.Min(min, pos); - max = Vector3.Max(max, pos); + var pos = vertex.position.xyz; + min = math.min(min, pos); + max = math.max(max, pos); } - _boundingBox = new Bounds(min, max); + _boundingBox = new AABB(min, max); } /// diff --git a/Ghost.Graphics/Data/RenderTexture.cs b/Ghost.Graphics/Data/RenderTexture.cs index 23ec932..bd449e6 100644 --- a/Ghost.Graphics/Data/RenderTexture.cs +++ b/Ghost.Graphics/Data/RenderTexture.cs @@ -206,7 +206,7 @@ public unsafe class RenderTexture : Texture /// /// Command list to record clear commands /// Color to clear to - public void ClearColor(CommandList commandList, Color16 clearColor) + public void ClearColor(CommandList commandList, Color128 clearColor) { ThrowIfDisposed(); diff --git a/Ghost.Graphics/Data/ResourceHandle.cs b/Ghost.Graphics/Data/ResourceHandle.cs index c512fbd..3a77cb6 100644 --- a/Ghost.Graphics/Data/ResourceHandle.cs +++ b/Ghost.Graphics/Data/ResourceHandle.cs @@ -29,7 +29,7 @@ public readonly struct ResourceHandle : IEquatable { unchecked { - return (id * 397) ^ (int)generation; + return (id * 397) ^ generation; } } @@ -52,6 +52,7 @@ public readonly struct ResourceHandle : IEquatable public readonly struct TextureHandle : IEquatable { private readonly ResourceHandle _resourceHandle; + private readonly BindlessDescriptor _bindlessDescriptor; public ResourceHandle ResourceHandle => _resourceHandle; public static TextureHandle Invalid => new(ResourceHandle.Invalid); @@ -59,13 +60,20 @@ public readonly struct TextureHandle : IEquatable internal TextureHandle(ResourceHandle resourceHandle) { _resourceHandle = resourceHandle; + _bindlessDescriptor = BindlessDescriptor.Invalid; + } + + internal TextureHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor) + { + _resourceHandle = resourceHandle; + _bindlessDescriptor = descriptor; } public bool IsValid => _resourceHandle.IsValid; 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) @@ -75,7 +83,7 @@ public readonly struct TextureHandle : IEquatable public override int GetHashCode() { - return _resourceHandle.GetHashCode(); + return HashCode.Combine(_resourceHandle, _bindlessDescriptor); } public static bool operator ==(TextureHandle left, TextureHandle right) @@ -115,7 +123,7 @@ public readonly struct BufferHandle : IEquatable 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) @@ -125,7 +133,7 @@ public readonly struct BufferHandle : IEquatable public override int GetHashCode() { - return _resourceHandle.GetHashCode(); + return HashCode.Combine(_resourceHandle, _bindlessDescriptor); } public static bool operator ==(BufferHandle left, BufferHandle right) diff --git a/Ghost.Graphics/Data/Shader.cs b/Ghost.Graphics/Data/Shader.cs index b7eabb2..da1a8fa 100644 --- a/Ghost.Graphics/Data/Shader.cs +++ b/Ghost.Graphics/Data/Shader.cs @@ -1,12 +1,10 @@ +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; + namespace Ghost.Graphics.Data; internal readonly struct TextureInfo { - public required string Name - { - get; init; - } - public uint RegisterSlot { get; init; @@ -20,11 +18,6 @@ internal readonly struct TextureInfo internal readonly struct PropertyInfo { - public required string Name - { - get; init; - } - public uint CBufferIndex { get; init; @@ -43,11 +36,6 @@ internal readonly struct PropertyInfo internal readonly struct CBufferInfo { - public required string Name - { - get; init; - } - public uint Size { get; init; @@ -64,36 +52,44 @@ internal readonly struct CBufferInfo /// // TODO: Multi pass and keyword support -public unsafe class Shader : IDisposable +public struct Shader : IDisposable { - private readonly string _source; - - private readonly List _constantBuffers = new(); - private readonly List _properties = new(); - private readonly List _regularTextures = new(); // Add regular texture support - private readonly Dictionary _propertyNameToIdMap = new(); + private UnsafeList _constantBuffers; + private UnsafeList _properties; + private UnsafeList _regularTextures; // Add regular texture support + // TODO: Possible to move this to unmanaged heap? + private Dictionary _propertyNameToIdMap; private bool _disposed; - internal string Source => _source; + internal readonly UnsafeList ConstantBuffers => _constantBuffers; + internal readonly UnsafeList Properties => _properties; + internal readonly UnsafeList RegularTextures => _regularTextures; + internal readonly Dictionary PropertyNameToIdMap => _propertyNameToIdMap; - internal List ConstantBuffers => _constantBuffers; - internal List Properties => _properties; - internal List RegularTextures => _regularTextures; - internal Dictionary PropertyNameToIdMap => _propertyNameToIdMap; - - // TODO: In real production, we should not load the shader source code directly. - internal Shader(string shaderCode) + public Shader() { - _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; } /// /// Gets a unique, stable ID for a shader property. /// - /// The name of the property (e.g., "_Color"). + /// The name of the shader property. /// The integer ID of the property, or -1 if not found. - public int GetPropertyId(string propertyName) + public readonly int GetPropertyId(string propertyName) { return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1; } @@ -105,12 +101,12 @@ public unsafe class Shader : IDisposable return; } - _constantBuffers.Clear(); - _properties.Clear(); - _propertyNameToIdMap.Clear(); - _regularTextures.Clear(); + _constantBuffers.Dispose(); + _properties.Dispose(); + _regularTextures.Dispose(); - GC.SuppressFinalize(this); + _propertyNameToIdMap.Clear(); + _propertyNameToIdMap = null!; _disposed = true; } diff --git a/Ghost.Graphics/Data/Vertex.cs b/Ghost.Graphics/Data/Vertex.cs index 16eee45..e907224 100644 --- a/Ghost.Graphics/Data/Vertex.cs +++ b/Ghost.Graphics/Data/Vertex.cs @@ -1,7 +1,6 @@ -using Misaki.HighPerformance.Mathematics; -using System.Runtime.CompilerServices; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.Mathematics; using System.Runtime.InteropServices; -using System.Text; using Win32.Graphics.Dxgi.Common; namespace Ghost.Graphics.Data; @@ -9,59 +8,21 @@ namespace Ghost.Graphics.Data; [StructLayout(LayoutKind.Sequential)] public struct Vertex { - public unsafe struct Semantic + public unsafe static class Semantic { public const Format ALIGNED_FORMAT = Format.R32G32B32A32Float; + public const int COUNT = 5; - private static readonly byte[] s_positionBytes = Encoding.UTF8.GetBytes("POSITION"); - private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL"); - private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT"); - private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR"); - private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD"); - - 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 static readonly FixedString32 position = new("POSITION"); + public static readonly FixedString32 normal = new("NORMAL"); + public static readonly FixedString32 tangent = new("TANGENT"); + public static readonly FixedString32 uv = new("TEXCOORD"); + public static readonly FixedString32 color = new("COLOR"); } - public float4 Position - { - get; - set; - } - - 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; - } + public float4 position; + public float4 normal; + public float4 tangent; + public float4 uv; + public Color128 color; } \ No newline at end of file diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj index 1030df1..76250bf 100644 --- a/Ghost.Graphics/Ghost.Graphics.csproj +++ b/Ghost.Graphics/Ghost.Graphics.csproj @@ -30,6 +30,9 @@ + + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance\bin\Release\net9.0\Misaki.HighPerformance.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index 8b4a7bc..51a232d 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -1,4 +1,7 @@ +using Ghost.Graphics.D3D12; using Ghost.Graphics.Data; +using Misaki.HighPerformance.LowLevel.Utilities; +using System.Drawing; namespace Ghost.Graphics.RHI; @@ -7,6 +10,11 @@ namespace Ghost.Graphics.RHI; /// public interface ICommandBuffer : IDisposable { + public CommandBufferType Type + { + get; + } + /// /// Begins recording commands into this command buffer /// @@ -22,7 +30,7 @@ public interface ICommandBuffer : IDisposable /// /// Render target to render into /// Color to clear the render target with - public void BeginRenderPass(IRenderTarget renderTarget, Color16 clearColor); + public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor); /// /// Ends the current render pass @@ -66,7 +74,7 @@ public interface ICommandBuffer : IDisposable /// /// The mesh to be drawn. Must not be null. /// The material to use for rendering the mesh. Must not be null. - public void DrawMesh(Mesh mesh, Material material); + public void DrawMesh(MeshClass mesh, MaterialClass material); /// /// Dispatches compute threads @@ -75,6 +83,26 @@ public interface ICommandBuffer : IDisposable /// Thread groups in Y dimension /// Thread groups in Z dimension public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1); + + /// + /// Uploads the specified data to the buffer represented by the given handle. + /// + /// The unmanaged value type of the elements to upload to the buffer. + /// A handle to the buffer that will receive the uploaded data. + /// A read-only span containing the data to upload to the buffer. The span must contain elements of type + /// . + public void Upload(BufferHandle buffer, ReadOnlySpan data) + where T : unmanaged; + + /// + /// Uploads texture data to the specified texture resource starting at the given subresource index. + /// + /// The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle. + /// The index of the first subresource in the texture to receive data. Must be less than the total number of subresources in the texture. + /// A reference to the structure containing the subresource data to upload. The data must match the format and layout expected by the texture. + /// The number of subresources to upload, starting from . + /// Must be greater than zero and not exceed the remaining subresources in the texture. + public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources); } /// @@ -82,21 +110,21 @@ public interface ICommandBuffer : IDisposable /// public struct ViewportDesc { - public float X; - public float Y; - public float Width; - public float Height; - public float MinDepth; - public float MaxDepth; + public float x; + public float y; + public float width; + public float height; + public float minDepth; + public float maxDepth; public ViewportDesc(float width, float height) { - X = 0; - Y = 0; - Width = width; - Height = height; - MinDepth = 0.0f; - MaxDepth = 1.0f; + x = 0; + y = 0; + this.width = width; + this.height = height; + minDepth = 0.0f; + maxDepth = 1.0f; } } @@ -122,17 +150,28 @@ public struct RectDesc /// /// Resource states /// +[Flags] public enum ResourceState { Common = 0, - VertexAndConstantBuffer = 0x1, - IndexBuffer = 0x2, - RenderTarget = 0x4, - UnorderedAccess = 0x8, - DepthWrite = 0x10, - DepthRead = 0x20, - PixelShaderResource = 0x80, - CopyDest = 0x400, - CopySource = 0x800, + VertexAndConstantBuffer = 1 << 0, + IndexBuffer = 1 << 1, + RenderTarget = 1 << 2, + UnorderedAccess = 1 << 3, + DepthWrite = 1 << 4, + DepthRead = 1 << 5, + PixelShaderResource = 1 << 6, + CopyDest = 1 << 7, + CopySource = 1 << 8, + GenericRead = 1 << 9, + IndirectArgument = 1 << 10, Present = 0, } + + +public struct SubResourceData +{ + public unsafe void* pData; + public nint rowPitch; + public nint slicePitch; +} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IGraphicsEngine.cs b/Ghost.Graphics/RHI/IGraphicsEngine.cs index 3f81678..03dcef1 100644 --- a/Ghost.Graphics/RHI/IGraphicsEngine.cs +++ b/Ghost.Graphics/RHI/IGraphicsEngine.cs @@ -7,6 +7,11 @@ public interface IGraphicsEngine : IDisposable get; } + public IResourceDatabase ResourceDatabase + { + get; + } + public IResourceAllocator ResourceAllocator { get; @@ -27,4 +32,14 @@ public interface IGraphicsEngine : IDisposable /// Swap chain description /// A new swap chain instance public ISwapChain CreateSwapChain(SwapChainDesc desc); + + /// + /// Begins a new rendering frame, preparing the graphics context for drawing operations. + /// + public void BeginFrame(); + + /// + /// Completes the current rendering frame and performs any necessary finalization steps. + /// + public void EndFrame(); } \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IPipelineStateController.cs b/Ghost.Graphics/RHI/IPipelineStateController.cs index 84bb6b9..8740ee7 100644 --- a/Ghost.Graphics/RHI/IPipelineStateController.cs +++ b/Ghost.Graphics/RHI/IPipelineStateController.cs @@ -1,3 +1,4 @@ +using Ghost.Core; using Ghost.Graphics.Data; namespace Ghost.Graphics.RHI; @@ -15,11 +16,9 @@ public interface IShaderPipeline public interface IPipelineStateController { - public void ColectionShader(ReadOnlySpan shaders); - - public void CompileCollected(); + public void CompileShader(Identifier id, string shaderPath); public void PreCookPipelineState(); - public IShaderPipeline GetShaderPipeline(Shader shader); + public IShaderPipeline GetShaderPipeline(Identifier id); } \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IRenderDevice.cs b/Ghost.Graphics/RHI/IRenderDevice.cs index 83207d4..593fb63 100644 --- a/Ghost.Graphics/RHI/IRenderDevice.cs +++ b/Ghost.Graphics/RHI/IRenderDevice.cs @@ -31,7 +31,7 @@ public interface IRenderDevice : IDisposable } /// -/// Command buffer types matching D3D12 command list types +/// Command buffer types /// public enum CommandBufferType { diff --git a/Ghost.Graphics/RHI/IRenderTypes.cs b/Ghost.Graphics/RHI/IRenderTypes.cs index 79beb1e..8a525a2 100644 --- a/Ghost.Graphics/RHI/IRenderTypes.cs +++ b/Ghost.Graphics/RHI/IRenderTypes.cs @@ -173,7 +173,8 @@ public struct RenderTargetDesc Format = desc.Format, Dimension = desc.Dimension, MipLevels = desc.MipLevels, - Usage = usage + Usage = usage, + CreationFlags = TextureCreationFlags.Bindless }; } } @@ -245,6 +246,15 @@ public struct TextureDesc get; set; } + + /// + /// Texture creation flags + /// + public TextureCreationFlags CreationFlags + { + get; + set; + } } /// @@ -341,3 +351,10 @@ public enum MemoryType Upload, // CPU-to-GPU memory Readback // GPU-to-CPU memory } + +[Flags] +public enum TextureCreationFlags +{ + None = 0, + Bindless = 1 << 0 +} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IResourceAllocator.cs b/Ghost.Graphics/RHI/IResourceAllocator.cs index 5331a22..d936098 100644 --- a/Ghost.Graphics/RHI/IResourceAllocator.cs +++ b/Ghost.Graphics/RHI/IResourceAllocator.cs @@ -4,40 +4,26 @@ namespace Ghost.Graphics.RHI; public interface IResourceAllocator { - /// - /// Creates a render target for off-screen rendering - /// - /// Render target description - /// A new render target instance - public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false); - /// /// Creates a texture resource /// /// Texture description /// A new texture handle point to the resource - public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false); + public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false); /// - /// Creates a texture resource + /// Creates a render target for off-screen rendering /// - /// Texture description - /// A new texture instance - public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false); + /// Render target description + /// A new render target instance + public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false); /// /// Creates a buffer resource /// /// Buffer description /// A new buffer handle point to the resource - public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false); - - /// - /// Creates a buffer resource - /// - /// Buffer description - /// A new buffer instance - public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false); + public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false); /// /// Release a resource given its handle diff --git a/Ghost.Graphics/RHI/IResourceDatabase.cs b/Ghost.Graphics/RHI/IResourceDatabase.cs index 50fbbd4..1613f5f 100644 --- a/Ghost.Graphics/RHI/IResourceDatabase.cs +++ b/Ghost.Graphics/RHI/IResourceDatabase.cs @@ -1,4 +1,5 @@ -using Ghost.Graphics.Data; +using Ghost.Core; +using Ghost.Graphics.Data; namespace Ghost.Graphics.RHI; @@ -32,4 +33,24 @@ public interface IResourceDatabase /// /// The handle of the resource to be removed. public void RemoveResource(ResourceHandle handle); + + public Identifier AddMesh(ref readonly Mesh mesh); + + public bool HasMesh(Identifier id); + + public Mesh GetMesh(Identifier id); + + public ref Mesh GetMeshReference(Identifier id); + + public void RemoveMesh(Identifier id); + + public Identifier AddShader(ref readonly Shader shader); + + public bool HasShader(Identifier id); + + public Shader GetShader(Identifier id); + + public ref Shader GetShaderReference(Identifier id); + + public void RemoveShader(Identifier id); } \ No newline at end of file diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index 8e0ab4c..f5b9de8 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -100,9 +100,9 @@ float4 PSMain(PixelInput input) : SV_TARGET "; // High-level bindless objects - private Mesh? _mesh; + private MeshClass? _mesh; private Shader? _shader; - private Material? _material; + private MaterialClass? _material; private Texture2D[]? _textures; // Texture file paths for this demo @@ -118,7 +118,7 @@ float4 PSMain(PixelInput input) : SV_TARGET _mesh = MeshBuilder.CreateCube(0.75f); _mesh.UploadMeshData(); - _shader = new Shader(_HLSL_SOURCE); + _shader = new ShaderData(_HLSL_SOURCE); _material = new Material(_shader); _textures = new Texture2D[_textureFiles.Length]; diff --git a/Ghost.Graphics/RenderSystem.cs b/Ghost.Graphics/RenderSystem.cs index ecd12ac..fecfb79 100644 --- a/Ghost.Graphics/RenderSystem.cs +++ b/Ghost.Graphics/RenderSystem.cs @@ -34,7 +34,7 @@ internal class RenderSystem private const uint _FRAME_COUNT = 2; - private readonly IGraphicsEngine _engine = null!; + private readonly IGraphicsEngine _graphicsEngine = null!; private readonly FrameResource[] _frameResources = null!; private readonly Thread _renderThread = null!; private readonly AutoResetEvent _shutdownEvent = null!; @@ -47,14 +47,14 @@ internal class RenderSystem private bool _isRunning; private bool _disposed; - public IGraphicsEngine GraphicsEngine => _engine; + public IGraphicsEngine GraphicsEngine => _graphicsEngine; public uint CPUFenceValue => _cpuFenceValue; public uint GPUFenceValue => _gpuFenceValue; public bool IsRunning => _isRunning; public RenderSystem(GraphicsAPI api) { - _engine = api switch + _graphicsEngine = api switch { GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this), _ => throw new NotSupportedException($"Graphics API {api} is not supported.") @@ -84,7 +84,7 @@ internal class RenderSystem { ObjectDisposedException.ThrowIf(_disposed, this); - var renderer = _engine.CreateRenderer(); + var renderer = _graphicsEngine.CreateRenderer(); ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer)); return renderer; } @@ -166,12 +166,16 @@ internal class RenderSystem // Only proceed if CPU ready event was signaled if (waitResult == 0) { + _graphicsEngine.BeginFrame(); + foreach (var renderer in _renderers) { renderer.ExecutePendingResize(); renderer.Render(); } + _graphicsEngine.EndFrame(); + _gpuFenceValue++; frameResource.gpuReadyEvent.Set(); } diff --git a/Ghost.Graphics/Utilities/MeshBuilder.cs b/Ghost.Graphics/Utilities/MeshBuilder.cs index 721832b..45ac543 100644 --- a/Ghost.Graphics/Utilities/MeshBuilder.cs +++ b/Ghost.Graphics/Utilities/MeshBuilder.cs @@ -8,10 +8,10 @@ public static class MeshBuilder /// /// Creates a unit cube centered at the origin with size 1. /// - 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 mesh = new Mesh(24, 36); + var mesh = new MeshClass(24, 36); var corners = new Vector4[] { @@ -40,11 +40,11 @@ public static class MeshBuilder { var vertex = new Vertex { - Position = corners[face[i]], - Normal = Vector4.Zero, - Tangent = Vector4.Zero, - Color = color, - UV = uvs[i].AsVector4() + position = corners[face[i]], + normal = Vector4.Zero, + tangent = Vector4.Zero, + color = color, + uv = uvs[i].AsVector4() }; mesh.AddVertex(vertex); } @@ -61,11 +61,11 @@ public static class MeshBuilder /// /// Creates a plane on the XZ axis centered at the origin. /// - 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 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(1.0f, 0.0f, 0.0f, 0.0f))); @@ -83,9 +83,9 @@ public static class MeshBuilder /// /// Creates a UV sphere centered at the origin. /// - 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 for (var lat = 0; lat <= latitudeSegments; lat++) @@ -107,11 +107,11 @@ public static class MeshBuilder mesh.AddVertex( new() { - Position = new Vector4(x, y, z, 0.0f) * radius, - Normal = Vector4.Zero, - Tangent = Vector4.Zero, - Color = color, - UV = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f) + position = new Vector4(x, y, z, 0.0f) * radius, + normal = Vector4.Zero, + tangent = Vector4.Zero, + color = color, + uv = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f) }); } } diff --git a/Ghost.UnitTest/UnitTestApp.xaml b/Ghost.UnitTest/UnitTestApp.xaml index 638fd34..0490a2f 100644 --- a/Ghost.UnitTest/UnitTestApp.xaml +++ b/Ghost.UnitTest/UnitTestApp.xaml @@ -8,7 +8,7 @@ - + diff --git a/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs b/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs index 9551161..af157bc 100644 --- a/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs +++ b/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs @@ -34,7 +34,7 @@ public sealed partial class GraphicsTestWindow : Window _renderSystem = new (GraphicsAPI.Direct3D12); _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); CompositionTarget.Rendering += OnRendering;