diff --git a/Ghost.Engine/EngineCore.cs b/Ghost.Engine/EngineCore.cs index 58a69c0..9bbaf75 100644 --- a/Ghost.Engine/EngineCore.cs +++ b/Ghost.Engine/EngineCore.cs @@ -2,7 +2,6 @@ using Ghost.Engine.Services; using Ghost.Graphics; using Ghost.Graphics.Data; -using Misaki.HighPerformance.Unsafe.Buffer; namespace Ghost.Engine; @@ -12,8 +11,6 @@ internal class EngineCore { ActivationHandler.Handle(args); - AllocationManager.Initialize(); - GraphicsPipeline.Initialize(GraphicsAPI.D3D12); GraphicsPipeline.Start(); @@ -29,6 +26,5 @@ internal class EngineCore { GraphicsPipeline.SignalCPUReady(); GraphicsPipeline.Shutdown(); - AllocationManager.Dispose(); } } \ No newline at end of file diff --git a/Ghost.Engine/Ghost.Engine.csproj b/Ghost.Engine/Ghost.Engine.csproj index 96716b3..0f98a92 100644 --- a/Ghost.Engine/Ghost.Engine.csproj +++ b/Ghost.Engine/Ghost.Engine.csproj @@ -23,7 +23,7 @@ - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.Entities/Components/ComponentStorage.cs b/Ghost.Entities/Components/ComponentStorage.cs index 9742979..d5969e0 100644 --- a/Ghost.Entities/Components/ComponentStorage.cs +++ b/Ghost.Entities/Components/ComponentStorage.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Misaki.HighPerformance.Unsafe.Collections; +using Misaki.HighPerformance.LowLevel.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/Ghost.Entities/Ghost.Entities.csproj b/Ghost.Entities/Ghost.Entities.csproj index 6999cd3..5bb2131 100644 --- a/Ghost.Entities/Ghost.Entities.csproj +++ b/Ghost.Entities/Ghost.Entities.csproj @@ -27,7 +27,7 @@ - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.Entities/Query/QueryFilter.cs b/Ghost.Entities/Query/QueryFilter.cs index c2aaae7..71032e2 100644 --- a/Ghost.Entities/Query/QueryFilter.cs +++ b/Ghost.Entities/Query/QueryFilter.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Misaki.HighPerformance.Unsafe.Collections; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Entities.Query; diff --git a/Ghost.Entities/Template/QueryEnumerable.cs b/Ghost.Entities/Template/QueryEnumerable.cs index 56b947b..062fbc9 100644 --- a/Ghost.Entities/Template/QueryEnumerable.cs +++ b/Ghost.Entities/Template/QueryEnumerable.cs @@ -3,7 +3,7 @@ using Ghost.Core; using Ghost.Entities.Components; using Ghost.Entities.Query; -using Misaki.HighPerformance.Unsafe.Collections; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Entities; diff --git a/Ghost.Entities/Template/QueryEnumerable.tt b/Ghost.Entities/Template/QueryEnumerable.tt index 6c45780..876b0fe 100644 --- a/Ghost.Entities/Template/QueryEnumerable.tt +++ b/Ghost.Entities/Template/QueryEnumerable.tt @@ -8,7 +8,7 @@ using Ghost.Core; using Ghost.Entities.Components; using Ghost.Entities.Query; -using Misaki.HighPerformance.Unsafe.Collections; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Entities; diff --git a/Ghost.Graphics/Contracts/ICommandBuffer.cs b/Ghost.Graphics/Contracts/ICommandBuffer.cs index dca49bf..6757fed 100644 --- a/Ghost.Graphics/Contracts/ICommandBuffer.cs +++ b/Ghost.Graphics/Contracts/ICommandBuffer.cs @@ -5,7 +5,10 @@ namespace Ghost.Graphics.Contracts; public interface ICommandBuffer { - public void DrawMesh(Mesh mesh); - public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size); + // TODO: They should be internal, maybe an interface ICommandBufferInternal? public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState); + public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress); + + public void DrawMesh(Mesh mesh, Material material); + public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size); } \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IResource.cs b/Ghost.Graphics/Contracts/IResource.cs index ce8aea5..c062e34 100644 --- a/Ghost.Graphics/Contracts/IResource.cs +++ b/Ghost.Graphics/Contracts/IResource.cs @@ -1,6 +1,8 @@ -namespace Ghost.Graphics.Contracts; +using Misaki.HighPerformance.LowLevel.Collections; -public interface IResource : IDisposable +namespace Ghost.Graphics.Contracts; + +public unsafe interface IResource : IDisposable { public ulong GPUAddress { @@ -20,4 +22,17 @@ public interface IResource : IDisposable public void SetData(Span data) where T : unmanaged; + + public void SetData(T* data, uint length) + where T : unmanaged; + + public void SetData(void* data, uint size); + + public UnsafeArray ReadData(Allocator allocator) + where T : unmanaged; + + public void ReadData(T* ppData, uint* size) + where T : unmanaged; + + public void ReadData(void* ppData, uint* size); } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index 5003517..4ee6a9c 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -17,8 +17,24 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList = commandList; } - public void DrawMesh(Mesh mesh) + public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState) { + var dxResource = (D3D12Resource)resource; + _commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState); + } + + public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress) + { + _commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress); + } + + public void DrawMesh(Mesh mesh, Material material) + { + _commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature); + _commandList.Ptr->SetPipelineState(material.Shader.PipelineState); + + material.UploadAndBind(this); + _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView); _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView); @@ -33,10 +49,4 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size); } - - public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState) - { - var dxResource = (D3D12Resource)resource; - _commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState); - } } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index 33dbb53..c0c404b 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -224,7 +224,7 @@ internal unsafe class D3D12Renderer : IRenderer ref var frameResource = ref _frameResources[i]; if (frameResource.backBuffer.Get() is not null) { - frameResource.backBuffer.Dispose(); + var c = frameResource.backBuffer.Reset(); _rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes); } diff --git a/Ghost.Graphics/D3D12/D3D12Resource.cs b/Ghost.Graphics/D3D12/D3D12Resource.cs index fa3f292..17e7f46 100644 --- a/Ghost.Graphics/D3D12/D3D12Resource.cs +++ b/Ghost.Graphics/D3D12/D3D12Resource.cs @@ -1,5 +1,6 @@ using Ghost.Core; using Ghost.Graphics.Contracts; +using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; using Win32; using Win32.Graphics.Direct3D12; @@ -8,13 +9,11 @@ namespace Ghost.Graphics.D3D12; public unsafe class D3D12Resource : IResource { - private ComPtr _nativeResource - { - get; - set; - } + private ComPtr _nativeResource; private string _name = string.Empty; + private bool _disposed; + internal ConstPtr NativeResource => new(_nativeResource.Get()); public ulong GPUAddress => _nativeResource.Get()->GetGPUVirtualAddress(); @@ -40,30 +39,101 @@ public unsafe class D3D12Resource : IResource TempResource = temp; } + ~D3D12Resource() + { + DisposeInternal(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetData(Span data) where T : unmanaged { - var size = (uint)(data.Length * sizeof(T)); - var range = new Win32.Graphics.Direct3D12.Range(0, size); - fixed (T* ptr = data) { - void* mappedPtr; - var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr); - if (hr.Failure) - { - var message = hr.ToString(); - throw new InvalidOperationException($"Failed to map resource: {message}"); - } - Unsafe.CopyBlock(mappedPtr, ptr, size); - _nativeResource.Get()->Unmap(0, &range); + SetData(ptr, (uint)data.Length); + } + } + + public unsafe void SetData(T* data, uint length) + where T : unmanaged + { + var size = (uint)(length * sizeof(T)); + SetData((void*)data, size); + } + + public unsafe void SetData(void* data, uint size) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var range = new Win32.Graphics.Direct3D12.Range(0, size); + + void* mappedPtr; + var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr); + if (hr.Failure) + { + var message = hr.ToString(); + throw new InvalidOperationException($"Failed to map resource: {message}"); + } + + Unsafe.CopyBlock(mappedPtr, data, size); + _nativeResource.Get()->Unmap(0, &range); + } + + public UnsafeArray ReadData(Allocator allocator) + where T : unmanaged + { + var size = (uint)_nativeResource.Get()->GetDesc().Width; + var data = new UnsafeArray((int)(size / (uint)sizeof(T)), allocator); + try + { + ReadData(data.GetUnsafePtr(), &size); + return data; + } + catch (Exception) + { + data.Dispose(); + throw; + } + } + + public void ReadData(T* pData, uint* size) + where T : unmanaged + { + ReadData(pData, size); + } + + public void ReadData(void* pData, uint* size) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var range = new Win32.Graphics.Direct3D12.Range(0, (uint)_nativeResource.Get()->GetDesc().Width); + + void* mappedPtr; + var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr); + if (hr.Failure) + { + var message = hr.ToString(); + throw new InvalidOperationException($"Failed to map resource: {message}"); + } + + Unsafe.CopyBlock(pData, mappedPtr, (uint)(range.End - range.Begin)); + _nativeResource.Get()->Unmap(0, &range); + if (size != null) + { + *size = (uint)(range.End - range.Begin); } } internal void DisposeInternal() { - var c = _nativeResource.Reset(); + if (_disposed) + { + return; + } + + _nativeResource.Dispose(); + + _disposed = true; } public void Dispose() @@ -71,6 +141,7 @@ public unsafe class D3D12Resource : IResource if (!TempResource) { DisposeInternal(); + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs index 8462f94..883f9f1 100644 --- a/Ghost.Graphics/Data/Material.cs +++ b/Ghost.Graphics/Data/Material.cs @@ -1,21 +1,103 @@ -using Win32; -using Win32.Graphics.Direct3D12; +using Ghost.Graphics.Contracts; +using Ghost.Graphics.Shading; +using System.Numerics; +using System.Runtime.CompilerServices; namespace Ghost.Graphics.Data; public class Material : IDisposable { - // TODO: Pipeline state should be abstracted that can support multiple graphics APIs. - private ComPtr _pipelineState; + private readonly Dictionary _cbufferCaches; + + private bool _disposed; public Shader Shader { get; set; - } = Shader.Empty; + } + + public Material(Shader shader) + { + Shader = shader; + + _cbufferCaches = new(); + foreach (var cbufferInfo in shader.ConstantBuffers.Values) + { + _cbufferCaches.Add(cbufferInfo.Name, new CBufferCache(cbufferInfo.Size)); + } + } + + ~Material() + { + Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFloat(string propertyName, in float value) + { + WriteToCache(propertyName, in value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetVector(string propertyName, in Vector4 value) + { + WriteToCache(propertyName, in value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatrix(string propertyName, in Matrix4x4 value) + { + WriteToCache(propertyName, in value); + } + + private unsafe void WriteToCache(string propertyName, in T value) where T : unmanaged + { + if (!Shader.Properties.TryGetValue(propertyName, out var propInfo)) + { + throw new ArgumentException($"Property '{propertyName}' does not exist in the shader.", nameof(propertyName)); + } + + if (propInfo.Size != sizeof(T)) + { + throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.", nameof(propertyName)); + } + + var cache = _cbufferCaches[propInfo.CBufferName]; + + Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value); + } + + public void UploadAndBind(ICommandBuffer cmb) + { + foreach (var cache in _cbufferCaches.Values) + { + cache.UploadToGpu(); + } + + foreach (var (name, cache) in _cbufferCaches) + { + var cbufferInfo = Shader.ConstantBuffers[name]; + cmb.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress); + } + } public void Dispose() { - _pipelineState.Dispose(); + if (_disposed) + { + return; + } + + foreach (var cache in _cbufferCaches.Values) + { + cache.Dispose(); + } + + _cbufferCaches.Clear(); + + GC.SuppressFinalize(this); + + _disposed = true; } } \ No newline at end of file diff --git a/Ghost.Graphics/Data/Mesh.cs b/Ghost.Graphics/Data/Mesh.cs index 93e565b..c08f4d1 100644 --- a/Ghost.Graphics/Data/Mesh.cs +++ b/Ghost.Graphics/Data/Mesh.cs @@ -1,7 +1,7 @@ using Ghost.Core; using Ghost.Graphics.Contracts; -using Misaki.HighPerformance.Unsafe.Collections; -using Misaki.HighPerformance.Unsafe.Helpers; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Helpers; using System.Numerics; using System.Runtime.CompilerServices; using Win32.Graphics.Direct3D12; diff --git a/Ghost.Graphics/Data/Shader.cs b/Ghost.Graphics/Data/Shader.cs deleted file mode 100644 index 74a1bfe..0000000 --- a/Ghost.Graphics/Data/Shader.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Ghost.Core; -using Ghost.Graphics.D3D12; -using System.Runtime.InteropServices; -using System.Text; -using Win32; -using Win32.Graphics.Direct3D; -using Win32.Graphics.Direct3D.Fxc; -using Win32.Graphics.Direct3D12; - -namespace Ghost.Graphics.Data; - -public unsafe class Shader -{ - private static readonly Shader s_empty = new("ErrorShader"); - public static Shader Empty => s_empty; - - private ComPtr _rootSignature; - - public ConstPtr RootSignature => new(_rootSignature.Get()); - - public Shader(string shaderPath) - { - } - - /// - /// Compiles HLSL source code from a string into shader bytecode. - /// - /// The string containing the HLSL code. - /// The name of the shader entry point function (e.g., "VSMain"). - /// The shader model to target (e.g., "vs_5_0", "ps_5_0"). - /// A byte array containing the compiled shader bytecode. - /// Thrown if shader compilation fails. - public static unsafe byte[] CompileShader(string sourceCode, string entryPoint, string shaderProfile) - { - ComPtr bytecodeBlob = default; - ComPtr errorBlob = default; - - // Convert strings to null-terminated ASCII for the native function - var sourceCodeBytes = Encoding.UTF8.GetBytes(sourceCode); - var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint); - var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile); - - // Call the D3DCompile function - var hr = D3DCompile( - sourceCodeBytes.AsSpan(), - entryPointBytes.AsSpan(), - shaderProfileBytes.AsSpan(), - CompileFlags.EnableStrictness | CompileFlags.Debug, - bytecodeBlob.GetAddressOf(), - errorBlob.GetAddressOf() - ); - - if (hr.Failure) - { - // If compilation fails, get the error message from the error blob - var errorMessage = "Shader compilation failed."; - if (errorBlob.Get() is not null) - { - errorMessage += "\n" + Encoding.ASCII.GetString( - (byte*)errorBlob.Get()->GetBufferPointer(), - (int)errorBlob.Get()->GetBufferSize() - ); - } - errorBlob.Dispose(); - throw new Exception(errorMessage); - } - - // Copy the compiled bytecode from the blob into a managed byte array - var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()]; - Marshal.Copy((IntPtr)bytecodeBlob.Get()->GetBufferPointer(), bytecode, 0, bytecode.Length); - - // Clean up the COM blobs - bytecodeBlob.Dispose(); - errorBlob.Dispose(); - - return bytecode; - } - - private void LoadShader(Span byteCode) - { - using ComPtr reflector = default; - fixed (void* codePtr = byteCode) - { - D3DReflect(codePtr, (nuint)byteCode.Length, __uuidof(), reflector.GetVoidAddressOf()); - } - - ShaderDescription shaderDesc; - reflector.Get()->GetDesc(&shaderDesc); - - var rootParameters = new List(); - var staticSamplers = new List(); - - for (uint i = 0; i < shaderDesc.BoundResources; i++) - { - ShaderInputBindDescription bindDesc; - reflector.Get()->GetResourceBindingDesc(i, &bindDesc); - - switch (bindDesc.Type) - { - case ShaderInputType.ConstantBuffer: - var cbufferParam = new RootParameter(); - cbufferParam.ParameterType = RootParameterType.Cbv; - cbufferParam.ShaderVisibility = ShaderVisibility.All; - cbufferParam.Descriptor.RegisterSpace = bindDesc.Space; - cbufferParam.Descriptor.ShaderRegister = bindDesc.BindPoint; - - rootParameters.Add(cbufferParam); - - var cbuffer = reflector.Get()->GetConstantBufferByName(bindDesc.Name); - ShaderBufferDescription cbufferDesc; - cbuffer->GetDesc(&cbufferDesc); - - for (var j = 0u; j < cbufferDesc.Variables; j++) - { - var variable = cbuffer->GetVariableByIndex(j); - ShaderVariableDescription varDesc; - variable->GetDesc(&varDesc); - } - - break; - case ShaderInputType.TextureBuffer: - break; - case ShaderInputType.Texture: - break; - case ShaderInputType.Sampler: - var samplerDesc = new StaticSamplerDescription - { - Filter = Filter.MinMagMipLinear, - AddressU = TextureAddressMode.Wrap, - AddressV = TextureAddressMode.Wrap, - AddressW = TextureAddressMode.Wrap, - ShaderVisibility = ShaderVisibility.All, - ShaderRegister = bindDesc.BindPoint, - RegisterSpace = bindDesc.Space, - }; - staticSamplers.Add(samplerDesc); - break; - - case ShaderInputType.UavRwTyped: - break; - case ShaderInputType.Structured: - break; - case ShaderInputType.UavRwStructured: - break; - case ShaderInputType.ByteAddress: - break; - case ShaderInputType.UavRwByteAddress: - break; - case ShaderInputType.UavAppendStructured: - break; - case ShaderInputType.UavConsumeStructured: - break; - case ShaderInputType.UavRwStructuredWithCounter: - break; - case ShaderInputType.RtAccelerationStructure: - break; - case ShaderInputType.UavFeedbackTexture: - break; - default: - break; - } - } - } - - private void CreateRootSignature() - { - var rootSignatureDesc = new RootSignatureDescription(); - - using ComPtr signature = default; - using ComPtr error = default; - - var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_2, signature.GetAddressOf(), error.GetAddressOf()); - if (hr.Failure) - { - var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize()); - throw new Exception($"Failed to serialize root signature: {errorMessage}"); - } - - GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); - } -} \ No newline at end of file diff --git a/Ghost.Graphics/Data/ShaderProperty.cs b/Ghost.Graphics/Data/ShaderProperty.cs deleted file mode 100644 index ce8252c..0000000 --- a/Ghost.Graphics/Data/ShaderProperty.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Win32.Graphics.Direct3D; - -namespace Ghost.Graphics.Data; - -public class ShaderProperty -{ - public string Name - { - get; - } - - public ShaderInputType Type - { - get; - } -} \ No newline at end of file diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj index f8eefe5..33f5a54 100644 --- a/Ghost.Graphics/Ghost.Graphics.csproj +++ b/Ghost.Graphics/Ghost.Graphics.csproj @@ -27,8 +27,11 @@ - - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll + + + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index 9517b7a..dd2cc73 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -1,24 +1,18 @@ using Ghost.Graphics.Contracts; -using Ghost.Graphics.D3D12; -using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.Data; +using Ghost.Graphics.Shading; using Ghost.Graphics.Utilities; using System.Drawing; -using System.Runtime.CompilerServices; -using Win32; -using Win32.Graphics.Direct3D; -using Win32.Graphics.Direct3D12; -using Win32.Graphics.Dxgi.Common; +using System.Numerics; namespace Ghost.Graphics.RenderPasses; internal unsafe class MeshRenderPass : IRenderPass { private const string _HLSL_SOURCE = @" - cbuffer ConstantBuffer : register(b0) { - float4x4 WVP_Matrix; + float4 _Color; }; struct VertexInput @@ -43,105 +37,35 @@ PixelInput VSMain(VertexInput input) float4 PSMain(PixelInput input) : SV_TARGET { - return float4(1.0, 1.0, 1.0, 1.0); + return float4(_Color.xyz, 1.0); } "; private Mesh? _mesh; - - private ComPtr _rootSignature; - private ComPtr _pipelineState; + private Shader? _shader; + private Material? _material; public void Initialize(ICommandBuffer cmb) { - _mesh = MeshBuilder.CreateCube(0.25f, new(Color.AliceBlue)); + _mesh = MeshBuilder.CreateCube(0.25f); _mesh.UploadMeshData(cmb); - CreateRootSignature(); - CreatePipelineStateObject(); - } + _shader = new(_HLSL_SOURCE); + _material = new(_shader); - private void CreateRootSignature() - { - var rootParameters = new RootParameter[] - { - new () - { - ParameterType = RootParameterType.Cbv, - ShaderVisibility = ShaderVisibility.Vertex, - Descriptor = new RootDescriptor(0, 0) - } - }; - - var rootSignatureDesc = new RootSignatureDescription(0u, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0])) - { - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout - }; - - using ComPtr signature = default; - using ComPtr error = default; - - var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf()); - if (hr.Failure) - { - var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize()); - throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}"); - } - - GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); - } - - private void CreatePipelineStateObject() - { - try - { - var vertexShaderBytecode = Shader.CompileShader(_HLSL_SOURCE, "VSMain", "vs_5_0"); - var pixelShaderBytecode = Shader.CompileShader(_HLSL_SOURCE, "PSMain", "ps_5_0"); - - fixed (byte* vsPtr = vertexShaderBytecode) - fixed (byte* psPtr = pixelShaderBytecode) - { - var psoDesc = new GraphicsPipelineStateDescription - { - pRootSignature = _rootSignature.Get(), - VS = new ShaderBytecode(vsPtr, (nuint)vertexShaderBytecode.Length), - PS = new ShaderBytecode(psPtr, (nuint)pixelShaderBytecode.Length), - InputLayout = D3D12PipelineResource.InputLayoutDescription, - RasterizerState = RasterizerDescription.CullNone, - BlendState = BlendDescription.Opaque, - DepthStencilState = DepthStencilDescription.Default, - SampleMask = uint.MaxValue, - PrimitiveTopologyType = PrimitiveTopologyType.Triangle, - NumRenderTargets = 1, - SampleDesc = new SampleDescription(1, 0), - DSVFormat = Format.Unknown, - }; - - psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; - - // Create the PSO - GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } + var color = new Vector4(Color.Brown.R / 255f, Color.Brown.G / 255f, Color.Brown.B / 255f, 1.0f); + _material.SetVector("_Color", ref color); } public void Execute(ICommandBuffer cmb) { - var dx12Cmb = (D3D12CommandBuffer)cmb; - dx12Cmb.CommandList.Ptr->SetGraphicsRootSignature(_rootSignature.Get()); - dx12Cmb.CommandList.Ptr->SetPipelineState(_pipelineState.Get()); - - cmb.DrawMesh(_mesh!); + cmb.DrawMesh(_mesh!, _material!); } public void Dispose() { _mesh?.Dispose(); - _rootSignature.Dispose(); - _pipelineState.Dispose(); + _shader?.Dispose(); + _material?.Dispose(); } } diff --git a/Ghost.Graphics/Shading/CBufferCache.cs b/Ghost.Graphics/Shading/CBufferCache.cs new file mode 100644 index 0000000..0d8c895 --- /dev/null +++ b/Ghost.Graphics/Shading/CBufferCache.cs @@ -0,0 +1,43 @@ +using Ghost.Graphics.Contracts; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Helpers; +using System.Runtime.CompilerServices; + +namespace Ghost.Graphics.Shading; + +internal struct CBufferCache : IDisposable +{ + public UnsafeArray CpuData + { + get; + } + + public IResource GpuResource + { + get; + } + + private readonly uint _alignedSize; + + public unsafe CBufferCache(uint bufferSize) + { + CpuData = new((int)bufferSize, Allocator.Persistent); + + _alignedSize = (bufferSize + 255u) & ~255u; + GpuResource = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(_alignedSize); + GpuResource.Name = "Material_CBufferCache"; + + UploadToGpu(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void UploadToGpu() + { + GpuResource.SetData(CpuData.AsSpan()); + } + + public readonly void Dispose() + { + GpuResource.Dispose(); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Shading/Shader.cs b/Ghost.Graphics/Shading/Shader.cs new file mode 100644 index 0000000..4b799bf --- /dev/null +++ b/Ghost.Graphics/Shading/Shader.cs @@ -0,0 +1,291 @@ +using Ghost.Core; +using Ghost.Graphics.D3D12; +using Ghost.Graphics.D3D12.Utilities; +using Misaki.HighPerformance.LowLevel.Helpers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Win32; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D.Fxc; +using Win32.Graphics.Direct3D12; +using Win32.Graphics.Dxgi.Common; + +namespace Ghost.Graphics.Shading; + +internal readonly struct PropertyInfo +{ + public required string Name + { + get; init; + } + + public required string CBufferName + { + get; init; + } + + public uint ByteOffset + { + get; init; + } + + public uint Size + { + get; init; + } +} + +internal readonly struct CBufferInfo +{ + public required string Name + { + get; init; + } + + public uint Size + { + get; init; + } + + public uint RegisterSlot + { + get; init; + } +} + +public unsafe class Shader : IDisposable +{ + private ComPtr _pipelineState; + private ComPtr _rootSignature; + + private readonly byte[] _vertexShaderBytecode; + private readonly byte[] _pixelShaderBytecode; + + private readonly Dictionary _constantBuffers = new(); + private readonly Dictionary _properties = new(); + + private bool _disposed; + + internal ConstPtr PipelineState => new(_pipelineState.Get()); + internal ConstPtr RootSignature => new(_rootSignature.Get()); + + internal IReadOnlyDictionary ConstantBuffers => _constantBuffers; + internal IReadOnlyDictionary Properties => _properties; + + //public Shader(string shaderPath) + //{ + + //} + + public Shader(string shaderCode) + { + _vertexShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "VSMain", "vs_5_0"); + _pixelShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "PSMain", "ps_5_0"); + + PerformReflection(_vertexShaderBytecode); + PerformReflection(_pixelShaderBytecode); + + CreateRootSignature(); + CreatePipelineStateObject(); + } + + ~Shader() + { + Dispose(); + } + + /// + /// Compiles HLSL source code from a string into shader bytecode. + /// + /// The string containing the HLSL code. + /// The name of the shader entry point function (e.g., "VSMain"). + /// The shader model to target (e.g., "vs_5_0", "ps_5_0"). + /// A byte array containing the compiled shader bytecode. + /// Thrown if shader compilation fails. + public static unsafe byte[] CompileShader(byte[] sourceCodeBytes, string entryPoint, string shaderProfile) + { + using ComPtr bytecodeBlob = default; + using ComPtr errorBlob = default; + + var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint); + var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile); + + // Call the D3DCompile function + var hr = D3DCompile( + sourceCodeBytes.AsSpan(), + entryPointBytes.AsSpan(), + shaderProfileBytes.AsSpan(), + CompileFlags.EnableStrictness | CompileFlags.Debug, + bytecodeBlob.GetAddressOf(), + errorBlob.GetAddressOf() + ); + + if (hr.Failure) + { + var errorMessage = "Shader compilation failed."; + if (errorBlob.Get() is not null) + { + errorMessage += "\n" + Encoding.ASCII.GetString( + (byte*)errorBlob.Get()->GetBufferPointer(), + (int)errorBlob.Get()->GetBufferSize() + ); + } + + throw new Exception(errorMessage); + } + + var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()]; + Unsafe.CopyBlock(bytecode.AsSpan().GetPointer(), bytecodeBlob.Get()->GetBufferPointer(), (uint)bytecode.Length); + + return bytecode; + } + + private void CreateRootSignature() + { + var rootParameters = new RootParameter1[_constantBuffers.Values.Count]; + + var i = 0; + foreach (var cbufferInfo in _constantBuffers.Values) + { + var rootParameter = new RootParameter1 + { + ParameterType = RootParameterType.Cbv, + ShaderVisibility = ShaderVisibility.All, + Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0), + }; + + rootParameters[i++] = rootParameter; + } + + var rootSignatureDesc = new RootSignatureDescription((uint)rootParameters.Length, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0])) + { + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout + }; + + using ComPtr signature = default; + using ComPtr error = default; + + var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf()); + if (hr.Failure) + { + var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize()); + throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}"); + } + + GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); + } + + private void CreatePipelineStateObject() + { + try + { + fixed (byte* vsPtr = _vertexShaderBytecode) + fixed (byte* psPtr = _pixelShaderBytecode) + { + var psoDesc = new GraphicsPipelineStateDescription + { + pRootSignature = _rootSignature.Get(), + VS = new ShaderBytecode(vsPtr, (nuint)_vertexShaderBytecode.Length), + PS = new ShaderBytecode(psPtr, (nuint)_pixelShaderBytecode.Length), + InputLayout = D3D12PipelineResource.InputLayoutDescription, + RasterizerState = RasterizerDescription.CullNone, + BlendState = BlendDescription.Opaque, + DepthStencilState = DepthStencilDescription.Default, + SampleMask = uint.MaxValue, + PrimitiveTopologyType = PrimitiveTopologyType.Triangle, + NumRenderTargets = 1, + SampleDesc = new SampleDescription(1, 0), + DSVFormat = Format.Unknown, + }; + + psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; + + // Create the PSO + GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + private unsafe void PerformReflection(Span byteCode) + { + using ComPtr reflection = default; + fixed (void* codePtr = byteCode) + { + D3DReflect(codePtr, (nuint)byteCode.Length, __uuidof(), reflection.GetVoidAddressOf()); + } + + ShaderDescription shaderDesc; + reflection.Get()->GetDesc(&shaderDesc); + + for (uint i = 0; i < shaderDesc.BoundResources; i++) + { + ShaderInputBindDescription bindDesc; + reflection.Get()->GetResourceBindingDesc(i, &bindDesc); + + if (bindDesc.Type == ShaderInputType.ConstantBuffer) + { + var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); + if (cbufferName == null || _constantBuffers.ContainsKey(cbufferName)) + { + continue; + } + + var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); + ShaderBufferDescription cbufferDesc; + cbuffer->GetDesc(&cbufferDesc); + + _constantBuffers.Add(cbufferName, new CBufferInfo + { + Name = cbufferName, + Size = cbufferDesc.Size, + RegisterSlot = bindDesc.BindPoint + }); + + 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 || _properties.ContainsKey(variableName)) + { + continue; + } + + _properties.Add(variableName, new PropertyInfo + { + Name = variableName, + CBufferName = cbufferName, + ByteOffset = varDesc.StartOffset, + Size = varDesc.Size + }); + } + } + } + reflection.Dispose(); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _pipelineState.Dispose(); + _rootSignature.Dispose(); + + _constantBuffers.Clear(); + _properties.Clear(); + + GC.SuppressFinalize(this); + + _disposed = true; + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Shading/ShaderProperty.cs b/Ghost.Graphics/Shading/ShaderProperty.cs new file mode 100644 index 0000000..c82b512 --- /dev/null +++ b/Ghost.Graphics/Shading/ShaderProperty.cs @@ -0,0 +1,48 @@ +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Helpers; + +namespace Ghost.Graphics.Shading; + +public enum ShaderPropertyType +{ + Float, + Float2, + Float3, + Float4, + Color, + Matrix, + Texture2D, + Texture3D +} + +public struct ShaderProperty : IDisposable +{ + private UnsafeArray _value; + private FixedString128 _name; + private readonly uint _valueOffset; + + internal readonly uint Offset => _valueOffset; + + public readonly string Name => _name.Value; + public readonly ReadOnlySpan Value => _value.AsSpan(); + + public ShaderPropertyType PropertyType + { + get; + } + + public ShaderProperty(Span value, uint offset, string name, ShaderPropertyType type) + { + _value = new(value.Length, Allocator.Persistent); + _valueOffset = offset; + _name = new(name); + PropertyType = type; + } + + public void Dispose() + { + _value.Dispose(); + _name.Dispose(); + } +} \ No newline at end of file diff --git a/Ghost.UnitTest/Ghost.UnitTest.csproj b/Ghost.UnitTest/Ghost.UnitTest.csproj index 4992dfe..20d0e22 100644 --- a/Ghost.UnitTest/Ghost.UnitTest.csproj +++ b/Ghost.UnitTest/Ghost.UnitTest.csproj @@ -56,7 +56,7 @@ - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.UnitTest/UnitTestApp.xaml b/Ghost.UnitTest/UnitTestApp.xaml index 2b53ec9..638fd34 100644 --- a/Ghost.UnitTest/UnitTestApp.xaml +++ b/Ghost.UnitTest/UnitTestApp.xaml @@ -1,4 +1,4 @@ - + - + - + diff --git a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs index e792496..cf3aa1f 100644 --- a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs +++ b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs @@ -2,7 +2,6 @@ using Ghost.Graphics; using Ghost.Graphics.Contracts; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; -using Misaki.HighPerformance.Unsafe.Buffer; using WinRT; namespace Ghost.UnitTest; @@ -32,7 +31,6 @@ public sealed partial class UnitTestAppWindow : Window private void UnitTestAppWindow_Activated(object sender, WindowActivatedEventArgs args) { - AllocationManager.Initialize(); GraphicsPipeline.Initialize(Graphics.Data.GraphicsAPI.D3D12); GraphicsPipeline.Start(); @@ -49,7 +47,6 @@ public sealed partial class UnitTestAppWindow : Window { GraphicsPipeline.SignalCPUReady(); GraphicsPipeline.Shutdown(); - AllocationManager.Dispose(); CompositionTarget.Rendering -= OnRendering; _swapChainPanelNative.Dispose(); _renderView?.Dispose();