From 28c386b0bbe161264d78deabd6d515f92f309046 Mon Sep 17 00:00:00 2001 From: Misaki Date: Thu, 23 Oct 2025 14:42:53 +0900 Subject: [PATCH] Refactor D3D12 Resource Management Refactored and renamed components related to D3D12 graphics programming, replacing "descriptor" with "viewGroup" to improve resource grouping and management. Updated `D3D12CommandBuffer`, `D3D12DescriptorAllocator`, and `D3D12PipelineLibrary` to reflect these changes. Simplified material and shader creation in `D3D12ResourceAllocator`. Enhanced `D3D12ResourceDatabase` with resource naming for debugging and improved management. Refactored `Shader` and `ShaderPass` to use modern C# features and `IResourceReleasable` interface. Introduced `D3D12Utility` for centralized utility methods. Updated `Material` class for efficient buffer creation. Renamed `ShaderCompiler` to `SDLCompiler` with improved error handling. Updated `MeshRenderPass` to use new shader compilation process. Various improvements in error handling, code readability, and utility methods. --- Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 6 +- .../D3D12/D3D12DescriptorAllocator.cs | 2 +- Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs | 6 +- .../D3D12/D3D12ResourceAllocator.cs | 26 +--- Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs | 143 +++++++++++++----- Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs | 1 - Ghost.Graphics/D3D12/D3D12SwapChain.cs | 2 +- .../D3D12/Utilities/D3D12Utility.cs | 61 ++++++-- Ghost.Graphics/Data/Material.cs | 32 ++-- Ghost.Graphics/Data/Shader.cs | 33 ++-- Ghost.Graphics/Ghost.Graphics.csproj | 1 + Ghost.Graphics/RHI/Common.cs | 89 +++++++++-- Ghost.Graphics/RHI/IResourceAllocator.cs | 4 +- Ghost.Graphics/RHI/IResourceDatabase.cs | 18 ++- Ghost.Graphics/RHI/ISwapChain.cs | 84 ---------- .../RHI/{IResource.cs => RHIUtility.cs} | 32 +--- Ghost.Graphics/RenderPasses/MeshRenderPass.cs | 12 +- Ghost.Shader.Test/Program.cs | 8 +- Ghost.Shader/AssemblyInfo.cs | 3 +- Ghost.Shader/Compiler/Parser/DefinesBlock.cs | 2 +- Ghost.Shader/Compiler/Parser/IBlockParser.cs | 2 +- Ghost.Shader/Compiler/Parser/IncludesBlock.cs | 4 +- Ghost.Shader/Compiler/Parser/KeywordsBlock.cs | 6 +- Ghost.Shader/Compiler/Parser/PassBlock.cs | 12 +- Ghost.Shader/Compiler/Parser/PipelineBlock.cs | 12 +- .../Compiler/Parser/PropertiesBlock.cs | 52 +++---- Ghost.Shader/Compiler/Parser/ShaderBlock.cs | 6 +- .../{ShaderCompiler.cs => SDLCompiler.cs} | 40 ++++- 28 files changed, 393 insertions(+), 306 deletions(-) rename Ghost.Graphics/RHI/{IResource.cs => RHIUtility.cs} (70%) rename Ghost.Shader/Compiler/{ShaderCompiler.cs => SDLCompiler.cs} (90%) diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index dc10619..cf42bb8 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -126,14 +126,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer throw new ArgumentException($"Render target at index {i} is not a valid texture handle"); } - var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).descriptor; + var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).viewGroup; rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv); } var dsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0]; if (dsvHandle != null) { - *dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).descriptor.dsv); + *dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).viewGroup.dsv); } _commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, FALSE, dsvHandle); @@ -270,7 +270,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get()); _commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature); - // Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 + // Set viewGroup heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 var heaps = stackalloc ID3D12DescriptorHeap*[2]; heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap diff --git a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs index 4b34b9d..35fa603 100644 --- a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs @@ -8,7 +8,7 @@ using static TerraFX.Aliases.D3D12_Alias; namespace Ghost.Graphics.D3D12; /// -/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps. +/// D3D12 implementation of viewGroup allocator that manages different types of viewGroup heaps. /// internal unsafe class D3D12DescriptorAllocator : IDisposable { diff --git a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs index 99ad60d..ad2e014 100644 --- a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs +++ b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs @@ -91,7 +91,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable { _defaultRootSignature = default; - // NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables. + // NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up viewGroup tables. var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[_ROOT_PARAM_COUNT]; rootParameters[0] = new D3D12_ROOT_PARAMETER1 { @@ -325,7 +325,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable SampleMask = UINT32_MAX, SampleDesc = new DXGI_SAMPLE_DESC(1, 0), NumRenderTargets = rtvCount, - DSVFormat = dsv.ToD3D12Format(), + DSVFormat = dsv.ToDXGIFormat(), DepthStencilState = BuildDepthStencil(in pipelineDescriptor), NodeMask = 0, Flags = D3D12_PIPELINE_STATE_FLAG_NONE, @@ -362,7 +362,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable for (var i = 0; i < rtvCount && i < 6; i++) { - desc.RTVFormats[i] = rtvs[i].ToD3D12Format(); + desc.RTVFormats[i] = rtvs[i].ToDXGIFormat(); desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(pipelineDescriptor.colorMask & 0x0F); hash.rtvFormats[i] = rtvs[i]; } diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index f2df292..cce2840 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -555,32 +555,16 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable public Handle CreateMaterial(Identifier shader) { - var materialData = new Material - { - Shader = shader, - }; + var material = new Material(); + material.SetShader(shader, this, _resourceDatabase); - ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader); - - // TODO: Get per-material constant buffer size from database - - var desc = new BufferDesc - { - Size = shaderRef.PerMaterialBufferInfo.Size, - Usage = BufferUsage.Constant, - MemoryType = ResourceMemoryType.Default, - }; - - var buffer = CreateBuffer(ref desc); - materialData._materialPropertiesCache = new CBufferCache(buffer, shaderRef.PerMaterialBufferInfo.Size); - - return _resourceDatabase.AddMaterial(ref materialData); + return _resourceDatabase.AddMaterial(ref material); } public Identifier CreateShader(ShaderDescriptor descriptor) { - var shaderData = new Shader(); - return _resourceDatabase.AddShader(ref shaderData); + var shader = new Shader(descriptor); + return _resourceDatabase.AddShader(shader); } #region Conversion Methods diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index 1a0a475..7d75e83 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -4,6 +4,7 @@ using Ghost.Graphics.RHI; using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; +using System.Diagnostics; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -34,7 +35,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable } public ResourceDesc desc; - public ResourceViewGroup descriptor; + public ResourceViewGroup viewGroup; public ResourceUnion resourceUnion; public ResourceState state; public uint cpuFenceValue; @@ -48,7 +49,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable this.resourceUnion = new ResourceUnion(allocation); this.isExternal = false; - this.descriptor = resourceDescriptor; + this.viewGroup = resourceDescriptor; this.cpuFenceValue = cpuFenceValue; this.state = state; this.desc = desc; @@ -59,7 +60,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable this.resourceUnion = new ResourceUnion(resource); this.isExternal = true; - this.descriptor = default; + this.viewGroup = default; this.cpuFenceValue = ~0u; this.state = state; this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc()); @@ -80,10 +81,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable } resourceUnion = default; - descriptor = default; + viewGroup = default; } - descriptorAllocator.Release(descriptor); + descriptorAllocator.Release(viewGroup); return refCount; } @@ -99,14 +100,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable private UnsafeSlotMap _resources; #if DEBUG || GHOST_EDITOR - private readonly Dictionary _resourceName; + private readonly Dictionary, string> _resourceName; #endif private readonly UnsafeSlotMap _meshes; private readonly UnsafeSlotMap _materials; - - // NOTE: We use a simple list since shaderSlot is not frequently added/removed. This can save 4 bytes for each ecs component. - private readonly DynamicArray> _shaders; + private readonly DynamicArray _shaders; // NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component. private readonly Dictionary _shaderPasses; private bool _disposed; @@ -135,28 +134,41 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable where T : IResourceReleasable { resource.ReleaseResource(this); + resource = default!; } - public Handle ImportExternalResource(T resource, ResourceState initialState) - where T : unmanaged + public Handle ImportExternalResource(ComPtr resource, ResourceState initialState, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); - if (resource is not ComPtr d3d12Resource) - { - throw new InvalidOperationException($"Expect ComPtr in D3D12ResourceDatabase, but got {typeof(T)}."); - } + var id = _resources.Add(new ResourceRecord(resource, initialState), out var generation); + var handle = new Handle(id, generation); - var id = _resources.Add(new ResourceRecord(d3d12Resource, initialState), out var generation); - return new Handle(id, generation); +#if DEBUG || GHOST_EDITOR + if (name != null) + { + _resourceName[handle] = name; + } +#endif + + return handle; } - public Handle AddResource(ComPtr allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc) + public Handle AddResource(ComPtr allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation); - return new Handle(id, generation); + var handle = new Handle(id, generation); + +#if DEBUG || GHOST_EDITOR + if (name != null) + { + _resourceName[handle] = name; + } +#endif + + return handle; } public ref ResourceRecord GetResourceInfo(Handle handle) @@ -218,13 +230,28 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable public int GetBindlessIndex(Handle handle) { + ObjectDisposedException.ThrowIf(_disposed, this); + ref var info = ref GetResourceInfo(handle, out var exist); if (!exist || !info.Allocated) { return -1; } - return info.descriptor.srv.value; + return info.viewGroup.srv.value; + } + + public string? GetResourceName(Handle handle) + { + ObjectDisposedException.ThrowIf(_disposed, this); + +#if DEBUG || GHOST_EDITOR + if (_resourceName.TryGetValue(handle, out var name)) + { + return name; + } +#endif + return null; } public unsafe void ReleaseResource(Handle handle) @@ -244,9 +271,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable var refCount = info.Release(_descriptorAllocator); #if DEBUG || GHOST_EDITOR + _resourceName.Remove(handle, out var name); if (refCount > 0) { - throw new GPUResourceLeakException(refCount, info.ResourcePtr, _resourceName[info]); + throw new GPUResourceLeakException(refCount, info.ResourcePtr, name ?? "Unknown Resource"); } #endif @@ -269,6 +297,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable public ref Mesh GetMeshReference(Handle handle) { + ObjectDisposedException.ThrowIf(_disposed, this); + ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist); if (!exist) { @@ -280,6 +310,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable public void ReleaseMesh(Handle handle) { + ObjectDisposedException.ThrowIf(_disposed, this); + ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist); if (!exist) { @@ -306,6 +338,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable public ref Material GetMaterialReference(Handle handle) { + ObjectDisposedException.ThrowIf(_disposed, this); + ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist); if (!exist) { @@ -317,6 +351,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable public void ReleaseMaterial(Handle handle) { + ObjectDisposedException.ThrowIf(_disposed, this); + ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist); if (!exist) { @@ -327,43 +363,45 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable _materials.Remove(handle.id, handle.generation); } - public Identifier AddShader(ref readonly Shader shader) + public Identifier AddShader(Shader shader) { ObjectDisposedException.ThrowIf(_disposed, this); var id = _shaders.Count; - _shaders.Add(new Slot { value = shader, occupied = true }); + _shaders.Add(shader); return new Identifier(id); } public bool HasShader(Identifier id) { ObjectDisposedException.ThrowIf(_disposed, this); - return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].occupied; + return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value] != null; } - public ref Shader GetShaderReference(Identifier id) + public Shader GetShaderReference(Identifier id) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (!HasShader(id)) { throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid."); } - ref var shader = ref _shaders[id.value].value; - return ref shader; + var shader = _shaders[id.value]!; + return shader; } public void ReleaseShader(Identifier id) { + ObjectDisposedException.ThrowIf(_disposed, this); + if (!HasShader(id)) { return; } - ref var shaderSlot = ref _shaders[id.value]; - - ReleaseResource(ref shaderSlot.value); - shaderSlot.occupied = false; + ref var shader = ref _shaders[id.value]!; + ReleaseResource(ref shader); } public void AddShaderPass(ShaderPassKey passKey, ShaderPass pass) @@ -384,36 +422,63 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable return pass; } - // Should we need to release the shaderSlot pass? + public void RemoveShaderPass(ShaderPassKey passKey) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (_shaderPasses.Remove(passKey, out var pass)) + { + ReleaseResource(ref pass); + } + } public void Dispose() { + [Conditional("DEBUG"), Conditional("GHOST_EDITOR")] + static void ThrowMemoryLeakException(string resourceType, int count) + { + throw new InvalidOperationException($"ResourceAllocator is being disposed with {count} {resourceType} still registered. Ensure all resources are released before disposing."); + } + if (_disposed) { return; } -#if DEBUG || GHOST_EDITOR if (_resources.Count > 0) { - throw new InvalidOperationException($"ResourceAllocator is being disposed with {_resources.Count} allocations still registered. Ensure all resources are released before disposing."); + ThrowMemoryLeakException("GPU resources", _resources.Count); } if (_meshes.Count > 0) { - throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshes.Count} meshes still registered. Ensure all meshes are released before disposing."); + ThrowMemoryLeakException("meshes", _meshes.Count); } if (_materials.Count > 0) { - throw new InvalidOperationException($"ResourceAllocator is being disposed with {_materials.Count} materials still registered. Ensure all materials are released before disposing."); + ThrowMemoryLeakException("materials", _materials.Count); } - if (_shaders.Count > 0) + // Shader are reference type, it will be managed by GC, so we don't throw exception here. + for (var i = 0; i < _shaders.Count; i++) { - throw new InvalidOperationException($"ResourceAllocator is being disposed with {_shaders.Count} shaders still registered. Ensure all shaders are released before disposing."); + ref var shader = ref _shaders[i]; + if (shader == null) + { + continue; + } + + ReleaseResource(ref shader); } -#endif + + // Same for shader pass. + foreach (var kv in _shaderPasses) + { + var pass = kv.Value; + ReleaseResource(ref pass); + } + _resources.Dispose(); _meshes.Dispose(); _materials.Dispose(); diff --git a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs index 3d9e422..fa017ec 100644 --- a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs +++ b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs @@ -123,7 +123,6 @@ internal readonly struct ShaderReflectionData internal static unsafe class D3D12ShaderCompiler { - private static string GetProfileString(ShaderStage stage, CompilerTier version) { return (stage, version) switch diff --git a/Ghost.Graphics/D3D12/D3D12SwapChain.cs b/Ghost.Graphics/D3D12/D3D12SwapChain.cs index 8df2428..e16cc68 100644 --- a/Ghost.Graphics/D3D12/D3D12SwapChain.cs +++ b/Ghost.Graphics/D3D12/D3D12SwapChain.cs @@ -60,7 +60,7 @@ internal unsafe class D3D12SwapChain : ISwapChain { Width = desc.width, Height = desc.height, - Format = desc.format.ToD3D12Format(), + Format = desc.format.ToDXGIFormat(), SampleDesc = new DXGI_SAMPLE_DESC(1, 0), BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT, diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs index e5027f5..616860f 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs @@ -1,21 +1,11 @@ -using Misaki.HighPerformance.LowLevel.Utilities; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel.Utilities; using TerraFX.Interop.DirectX; using static TerraFX.Aliases.D3D12_Alias; namespace Ghost.Graphics.D3D12.Utilities; -internal unsafe static class ID3D12Resource_Extensions -{ - extension(ID3D12Resource resource) - { - public void SetName(ReadOnlySpan name) - { - resource.SetName(name.GetUnsafePtr()); - } - } -} - internal static class D3D12_RASTERIZER_DESC_Extensions { extension(D3D12_RASTERIZER_DESC) @@ -150,4 +140,51 @@ internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions }; } } +} + +internal unsafe static class D3D12Utility +{ + public static void SetName(ref this ID3D12Resource resource, ReadOnlySpan name) + { + resource.SetName(name.GetUnsafePtr()); + } + + public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension) + { + return dimension switch + { + D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D, + D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D, + D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D, + _ => TextureDimension.Unknown, + }; + } + + public static DXGI_FORMAT ToDXGIFormat(this TextureFormat format) + { + return format switch + { + TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, + TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, + TextureFormat.R16G16B16A16_Float => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT, + TextureFormat.R32G32B32A32_Float => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT, + TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT, + TextureFormat.D32_Float => DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT, + _ => throw new NotSupportedException($"Texture format {format} is not supported."), + }; + } + + public static TextureFormat ToTextureFormat(this DXGI_FORMAT format) + { + return format switch + { + DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm, + DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float, + DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float, + DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt, + DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float, + _ => TextureFormat.Unknown, + }; + } } \ No newline at end of file diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs index 1525bad..6fa9407 100644 --- a/Ghost.Graphics/Data/Material.cs +++ b/Ghost.Graphics/Data/Material.cs @@ -18,20 +18,12 @@ internal struct CBufferCache : IResourceReleasable public readonly Handle GpuResource => _gpuResource; public readonly uint AlignedSize => _alignedSize; - public unsafe CBufferCache(IResourceAllocator allocator, uint bufferSize) + public unsafe CBufferCache(Handle buffer, uint bufferSize) { _alignedSize = (bufferSize + 255u) & ~255u; _cpuData = new((int)AlignedSize, Allocator.Persistent); - - var desc = new BufferDesc - { - Size = bufferSize, - Usage = BufferUsage.Constant, - MemoryType = ResourceMemoryType.Default, - }; - - _gpuResource = allocator.CreateBuffer(ref desc); + _gpuResource = buffer; } public void ReleaseResource(IResourceDatabase database) @@ -52,11 +44,6 @@ public struct Material : IResourceReleasable, IHandleType public readonly Identifier Shader => _shader; - public Material(Identifier shader, IResourceAllocator allocator, IResourceDatabase database) - { - SetShader(shader, allocator, database); - } - internal ref CBufferCache GetPassCache(int passIndex) { return ref _materialPropertiesCache[passIndex]; @@ -77,7 +64,16 @@ public struct Material : IResourceReleasable, IHandleType { var pass = database.GetShaderPass(shader.GetPassKey(i)); var cbufferInfo = pass.PassPropertyInfo; - _materialPropertiesCache[i] = new CBufferCache(allocator, cbufferInfo.Size); + + var desc = new BufferDesc + { + Size = cbufferInfo.Size, + Usage = BufferUsage.Constant, + MemoryType = ResourceMemoryType.Default, + }; + + var buffer = allocator.CreateBuffer(ref desc); + _materialPropertiesCache[i] = new CBufferCache(buffer, cbufferInfo.Size); } } @@ -95,7 +91,7 @@ public struct Material : IResourceReleasable, IHandleType public ref struct MaterialAccessor { private ref Material _materialData; - private readonly ref Shader _shader; + private Shader _shader; private readonly IResourceDatabase _resourceDatabase; @@ -104,7 +100,7 @@ public ref struct MaterialAccessor _resourceDatabase = resourceDatabase; _materialData = ref resourceDatabase.GetMaterialReference(material); - _shader = ref resourceDatabase.GetShaderReference(_materialData.Shader); + _shader = resourceDatabase.GetShaderReference(_materialData.Shader); } private readonly unsafe void WriteToCache(string propertyName, in T value) diff --git a/Ghost.Graphics/Data/Shader.cs b/Ghost.Graphics/Data/Shader.cs index 438e649..a0ae305 100644 --- a/Ghost.Graphics/Data/Shader.cs +++ b/Ghost.Graphics/Data/Shader.cs @@ -1,7 +1,7 @@ using Ghost.Core; -using Ghost.Core.Contracts; using Ghost.Core.Graphics; using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.InteropServices; @@ -51,13 +51,13 @@ public readonly struct CBufferInfo } } -public readonly unsafe struct ShaderPass +public unsafe class ShaderPass : IResourceReleasable { // NOTE: This is for per pass cbuffer only. Global, per view, and per mesh cbuffers are fixed. private readonly Dictionary _propertyLookup; private readonly UnsafeList _properties; - internal readonly CBufferInfo PassPropertyInfo + internal CBufferInfo PassPropertyInfo { get; } @@ -69,36 +69,41 @@ public readonly unsafe struct ShaderPass _propertyLookup = propertyNameToIdMap; } - public readonly int GetPropertyId(string propertyName) + public int GetPropertyId(string propertyName) { return _propertyLookup.TryGetValue(propertyName, out var id) ? id : -1; } - public readonly PropertyInfo GetPropertyInfo(int id) + public PropertyInfo GetPropertyInfo(int id) { return _properties[id]; } - public readonly PropertyInfo GetPropertyInfo(string propertyName) + public PropertyInfo GetPropertyInfo(string propertyName) { return _properties[GetPropertyId(propertyName)]; } + + void IResourceReleasable.ReleaseResource(IResourceDatabase database) + { + _properties.Dispose(); + } } /// /// A representation of a GPU shader, including all the passes it contains. /// -public readonly struct Shader : IResourceReleasable, IIdentifierType +public class Shader : IResourceReleasable, IIdentifierType { - private readonly ShaderPassKey[] _passIDs; + private UnsafeArray _passIDs; private readonly Dictionary _passLookup; // pass name to index private readonly Dictionary> _propertyLookup; // property name to pass index (property name to list of pass indices that contain the property) - public int PassCount => _passIDs.Length; + public int PassCount => _passIDs.Count; internal Shader(ShaderDescriptor descriptor) { - _passIDs = new ShaderPassKey[descriptor.passes.Count]; + _passIDs = new UnsafeArray(descriptor.passes.Count, Allocator.Persistent); _passLookup = new(descriptor.passes.Count); _propertyLookup = new(descriptor.passes.Count); @@ -132,12 +137,12 @@ public readonly struct Shader : IResourceReleasable, IIdentifierType } } - public readonly ShaderPassKey GetPassKey(int index) + public ShaderPassKey GetPassKey(int index) { return _passIDs[index]; } - public readonly bool TryGetPassKey(string passName, out ShaderPassKey? passID) + public bool TryGetPassKey(string passName, out ShaderPassKey? passID) { var index = _passLookup.GetValueOrDefault(passName, -1); if (index == -1) @@ -150,7 +155,7 @@ public readonly struct Shader : IResourceReleasable, IIdentifierType return true; } - public readonly IReadOnlyCollection GetPropertyPassIndices(string propertyName) + public IReadOnlyCollection GetPropertyPassIndices(string propertyName) { if (_propertyLookup.TryGetValue(propertyName, out var passIndices)) { @@ -162,6 +167,6 @@ public readonly struct Shader : IResourceReleasable, IIdentifierType void IResourceReleasable.ReleaseResource(IResourceDatabase database) { - // Should we do something here? + _passIDs.Dispose(); } } \ No newline at end of file diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj index d7603b4..80ad45a 100644 --- a/Ghost.Graphics/Ghost.Graphics.csproj +++ b/Ghost.Graphics/Ghost.Graphics.csproj @@ -23,6 +23,7 @@ + diff --git a/Ghost.Graphics/RHI/Common.cs b/Ghost.Graphics/RHI/Common.cs index d457020..1e18e8f 100644 --- a/Ghost.Graphics/RHI/Common.cs +++ b/Ghost.Graphics/RHI/Common.cs @@ -1,4 +1,5 @@ -using Misaki.HighPerformance.Utilities; +using Ghost.Graphics.D3D12.Utilities; +using Misaki.HighPerformance.Utilities; using System.IO.Hashing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -74,7 +75,7 @@ internal struct GraphicsPipelineHash data[1] = rtvCount; data[2] = (ulong)dsvFormat; - for (int i = 0; i < 8; i++) + for (var i = 0; i < 8; i++) { data[3 + i] = (ulong)rtvFormats[i]; } @@ -425,18 +426,88 @@ public struct BufferDesc } } -public static class TextureDimensionExtension +/// +/// Swap chain description +/// +public struct SwapChainDesc { - public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension) + /// + /// Width of the swap chain + /// + public uint width; + + /// + /// Height of the swap chain + /// + public uint height; + + /// + /// Back buffer format + /// + public TextureFormat format; + + /// + /// Target for presentation (window handle or composition target) + /// + public SwapChainTarget target; + + public SwapChainDesc(uint width, uint height, SwapChainTarget target, TextureFormat format = TextureFormat.B8G8R8A8_UNorm, uint bufferCount = 2) { - return dimension switch + this.width = width; + this.height = height; + this.format = format; + this.target = target; + } +} + +/// +/// Swap chain target (window handle or composition surface) +/// +public struct SwapChainTarget +{ + /// + /// Target type + /// + public SwapChainTargetType type; + + /// + /// Window handle for HWND targets + /// + public nint windowHandle; + + /// + /// Composition surface for UWP/WinUI targets + /// + public object? compositionSurface; + + public static SwapChainTarget FromWindowHandle(nint hwnd) + { + return new SwapChainTarget { - D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D, - D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D, - D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D, - _ => TextureDimension.Unknown, + type = SwapChainTargetType.WindowHandle, + windowHandle = hwnd, + compositionSurface = null }; } + + public static SwapChainTarget FromCompositionSurface(object surface) + { + return new SwapChainTarget + { + type = SwapChainTargetType.Composition, + windowHandle = nint.Zero, + compositionSurface = surface + }; + } +} + +/// +/// Swap chain target types +/// +public enum SwapChainTargetType +{ + WindowHandle, + Composition } diff --git a/Ghost.Graphics/RHI/IResourceAllocator.cs b/Ghost.Graphics/RHI/IResourceAllocator.cs index 2dcca9b..e0b1060 100644 --- a/Ghost.Graphics/RHI/IResourceAllocator.cs +++ b/Ghost.Graphics/RHI/IResourceAllocator.cs @@ -1,4 +1,5 @@ using Ghost.Core; +using Ghost.Core.Graphics; using Ghost.Graphics.Data; using Misaki.HighPerformance.LowLevel.Collections; @@ -46,7 +47,8 @@ public interface IResourceAllocator /// Creates a new shader and returns its unique identifier. /// /// An representing the newly created shader. - public Identifier CreateShader(); + /// The viewGroup containing the shader's properties and passes. + public Identifier CreateShader(ShaderDescriptor descriptor); /// /// Release a resource given its handle diff --git a/Ghost.Graphics/RHI/IResourceDatabase.cs b/Ghost.Graphics/RHI/IResourceDatabase.cs index 26d00a6..f80f5b9 100644 --- a/Ghost.Graphics/RHI/IResourceDatabase.cs +++ b/Ghost.Graphics/RHI/IResourceDatabase.cs @@ -13,6 +13,7 @@ public interface IResourceReleasable public interface IResourceDatabase { + /* /// /// Imports an external unmanaged resource and returns a handle for use within the resource management system. /// @@ -20,8 +21,9 @@ public interface IResourceDatabase /// A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage. /// The initial state to assign to the imported resource. /// A handle representing the imported resource, which can be used for subsequent operations. - unsafe Handle ImportExternalResource(T resourcePtr, ResourceState initialState) + unsafe Handle ImportExternalResource(T resourcePtr, ResourceState initialState, string? name = null) where T : unmanaged; + */ /// /// Retrieves the current state of the specified resource. @@ -51,6 +53,16 @@ public interface IResourceDatabase /// The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found. int GetBindlessIndex(Handle handle); + /// + /// Retrieves the name of the GPU resource associated with the specified handle. + /// + /// + /// You should only use this method in debug builds or inside engine editor. + /// + /// A handle to the GPU resource for which to obtain the name. Must reference a valid resource. + /// The name of the GPU resource associated with the specified handle, or null if the resource does not have a name. + string? GetResourceName(Handle handle); + /// /// Removes a resource from the database using its handle. /// @@ -116,7 +128,7 @@ public interface IResourceDatabase /// /// The shader to add. The shader is passed by read-only reference and will not be modified. /// The representing the newly added shader. - Identifier AddShader(ref readonly Shader shader); + Identifier AddShader(Shader shader); /// /// Determines whether a shader with the specified identifier exists in the collection. @@ -130,7 +142,7 @@ public interface IResourceDatabase /// /// The identifier of the shader to retrieve. Must refer to a valid shader. /// A reference to the shader corresponding to the specified identifier. - ref Shader GetShaderReference(Identifier id); + Shader GetShaderReference(Identifier id); /// /// Releases the shader associated with the specified identifier, freeing any resources allocated to it. diff --git a/Ghost.Graphics/RHI/ISwapChain.cs b/Ghost.Graphics/RHI/ISwapChain.cs index bccffd7..7868d72 100644 --- a/Ghost.Graphics/RHI/ISwapChain.cs +++ b/Ghost.Graphics/RHI/ISwapChain.cs @@ -50,88 +50,4 @@ public interface ISwapChain : IDisposable /// New width /// New height public void Resize(uint width, uint height); -} - -/// -/// Swap chain description -/// -public struct SwapChainDesc -{ - /// - /// Width of the swap chain - /// - public uint width; - - /// - /// Height of the swap chain - /// - public uint height; - - /// - /// Back buffer format - /// - public TextureFormat format; - - /// - /// Target for presentation (window handle or composition target) - /// - public SwapChainTarget target; - - public SwapChainDesc(uint width, uint height, SwapChainTarget target, TextureFormat format = TextureFormat.B8G8R8A8_UNorm, uint bufferCount = 2) - { - this.width = width; - this.height = height; - this.format = format; - this.target = target; - } -} - -/// -/// Swap chain target (window handle or composition surface) -/// -public struct SwapChainTarget -{ - /// - /// Target type - /// - public SwapChainTargetType type; - - /// - /// Window handle for HWND targets - /// - public nint windowHandle; - - /// - /// Composition surface for UWP/WinUI targets - /// - public object? compositionSurface; - - public static SwapChainTarget FromWindowHandle(nint hwnd) - { - return new SwapChainTarget - { - type = SwapChainTargetType.WindowHandle, - windowHandle = hwnd, - compositionSurface = null - }; - } - - public static SwapChainTarget FromCompositionSurface(object surface) - { - return new SwapChainTarget - { - type = SwapChainTargetType.Composition, - windowHandle = nint.Zero, - compositionSurface = surface - }; - } -} - -/// -/// Swap chain target types -/// -public enum SwapChainTargetType -{ - WindowHandle, - Composition } \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IResource.cs b/Ghost.Graphics/RHI/RHIUtility.cs similarity index 70% rename from Ghost.Graphics/RHI/IResource.cs rename to Ghost.Graphics/RHI/RHIUtility.cs index 9171020..997f9e0 100644 --- a/Ghost.Graphics/RHI/IResource.cs +++ b/Ghost.Graphics/RHI/RHIUtility.cs @@ -1,37 +1,9 @@ -using TerraFX.Interop.DirectX; +using TerraFX.Interop.DirectX; namespace Ghost.Graphics.RHI; -internal static class TextureFormatExtensions +internal static class RHIUtility { - public static DXGI_FORMAT ToD3D12Format(this TextureFormat format) - { - return format switch - { - TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, - TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, - TextureFormat.R16G16B16A16_Float => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT, - TextureFormat.R32G32B32A32_Float => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT, - TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT, - TextureFormat.D32_Float => DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT, - _ => throw new NotSupportedException($"Texture format {format} is not supported."), - }; - } - - public static TextureFormat ToTextureFormat(this DXGI_FORMAT format) - { - return format switch - { - DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm, - DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm, - DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float, - DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float, - DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt, - DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float, - _ => TextureFormat.Unknown, - }; - } - public static int GetBytesPerPixel(this TextureFormat format) { return format switch diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index 895cfb9..c4cad32 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -3,6 +3,7 @@ using Ghost.Graphics.Contracts; using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Ghost.Graphics.Utilities; +using Ghost.Shader.Compiler; using Misaki.HighPerformance.Image; namespace Ghost.Graphics.RenderPasses; @@ -27,13 +28,17 @@ internal unsafe class MeshRenderPass : IRenderPass public void Initialize(ref readonly RenderingContext ctx, IResourceAllocator resourceAllocator, IPipelineLibrary stateController) { + var shaderDescriptor = SDLCompiler.CompileShader("F:\\csharp\\GhostEngine\\Ghost.Graphics\\RenderPasses\\ShaderCode.hlsl").GetValueOrThrow(); + + stateController.CompileShader(shaderDescriptor); + stateController.PreCookPipelineState(); + MeshBuilder.CreateCube(0.75f, default, out var vertices, out var indices); _mesh = ctx.CreateMesh(vertices, indices); ctx.UploadMesh(_mesh, true); - _shader = resourceAllocator.CreateShader(); - + _shader = resourceAllocator.CreateShader(shaderDescriptor); _material = resourceAllocator.CreateMaterial(_shader); var imageResults = new ImageResult[_textureFiles.Length]; @@ -59,9 +64,6 @@ internal unsafe class MeshRenderPass : IRenderPass _textures[i] = ctx.CreateTexture(ref desc); ctx.UploadTexture(_textures[i], new Span(imageData.Data, (int)imageData.Size)); } - - stateController.CompileShader(_shader, "F:\\csharp\\GhostEngine\\Ghost.Graphics\\RenderPasses\\ShaderCode.hlsl"); - stateController.PreCookPipelineState(); } public void Execute(ref readonly RenderingContext ctx) diff --git a/Ghost.Shader.Test/Program.cs b/Ghost.Shader.Test/Program.cs index 69f199e..1b4c7bd 100644 --- a/Ghost.Shader.Test/Program.cs +++ b/Ghost.Shader.Test/Program.cs @@ -10,8 +10,8 @@ var source = File.ReadAllText("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader var lexer = new Lexer(source); var stream = new TokenStream(lexer.Tokenize()); -var shaderInfo = ShaderCompiler.ParseShaders(stream); -var model = ShaderCompiler.SemanticAnalysis(shaderInfo[0], out var errors); +var shaderInfo = SDLCompiler.ParseShaders(stream); +var model = SDLCompiler.SemanticAnalysis(shaderInfo[0], out var errors); foreach (var error in errors) { @@ -29,8 +29,8 @@ if (model == null) return; } -var descriptor = ShaderCompiler.ResolveShader(model); -ShaderCompiler.CompileShader(descriptor, "C:/Users/Misaki/Downloads/Archive"); +var descriptor = SDLCompiler.ResolveShader(model); +SDLCompiler.GenerateShader(descriptor, "C:/Users/Misaki/Downloads/Archive"); Console.WriteLine("Shader compiled successfully:"); diff --git a/Ghost.Shader/AssemblyInfo.cs b/Ghost.Shader/AssemblyInfo.cs index c320e1e..fe76bd6 100644 --- a/Ghost.Shader/AssemblyInfo.cs +++ b/Ghost.Shader/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Ghost.Shader.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Ghost.Shader.Test")] +[assembly: InternalsVisibleTo("Ghost.Graphics")] \ No newline at end of file diff --git a/Ghost.Shader/Compiler/Parser/DefinesBlock.cs b/Ghost.Shader/Compiler/Parser/DefinesBlock.cs index fafecd5..a46dba8 100644 --- a/Ghost.Shader/Compiler/Parser/DefinesBlock.cs +++ b/Ghost.Shader/Compiler/Parser/DefinesBlock.cs @@ -27,7 +27,7 @@ internal class DefinesBlock : IBlockParser, List> return defines; } - public static List? SemanticAnalysis(List? syntax, List errors) + public static List? SemanticAnalysis(List? syntax, List errors) { if (syntax == null) { diff --git a/Ghost.Shader/Compiler/Parser/IBlockParser.cs b/Ghost.Shader/Compiler/Parser/IBlockParser.cs index 76eb468..ed3c2a0 100644 --- a/Ghost.Shader/Compiler/Parser/IBlockParser.cs +++ b/Ghost.Shader/Compiler/Parser/IBlockParser.cs @@ -4,5 +4,5 @@ internal interface IBlockParser { public static abstract bool ShouldEnter(Token token); public static abstract T? Parse(TokenStreamSlice ts); - public static abstract U? SemanticAnalysis(T? syntax, List errors); + public static abstract U? SemanticAnalysis(T? syntax, List errors); } diff --git a/Ghost.Shader/Compiler/Parser/IncludesBlock.cs b/Ghost.Shader/Compiler/Parser/IncludesBlock.cs index 19f31ce..7fc86b5 100644 --- a/Ghost.Shader/Compiler/Parser/IncludesBlock.cs +++ b/Ghost.Shader/Compiler/Parser/IncludesBlock.cs @@ -27,7 +27,7 @@ internal class IncludesBlock : IBlockParser, List> return includes; } - public static List? SemanticAnalysis(List? syntax, List errors) + public static List? SemanticAnalysis(List? syntax, List errors) { if (syntax == null || syntax.Count == 0) { @@ -44,7 +44,7 @@ internal class IncludesBlock : IBlockParser, List> } else { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Included file '{path}' not found.", line = includeToken.line, diff --git a/Ghost.Shader/Compiler/Parser/KeywordsBlock.cs b/Ghost.Shader/Compiler/Parser/KeywordsBlock.cs index 0b39499..761d268 100644 --- a/Ghost.Shader/Compiler/Parser/KeywordsBlock.cs +++ b/Ghost.Shader/Compiler/Parser/KeywordsBlock.cs @@ -30,7 +30,7 @@ internal class KeywordsBlock : IBlockParser, List< return keywords; } - public static List? SemanticAnalysis(List? syntax, List errors) + public static List? SemanticAnalysis(List? syntax, List errors) { if (syntax == null) { @@ -42,7 +42,7 @@ internal class KeywordsBlock : IBlockParser, List< { if (keyword.arguments == null || keyword.arguments.Count == 0) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Function '{keyword.name.lexeme}' must have at least one argument.", line = keyword.name.line, @@ -61,7 +61,7 @@ internal class KeywordsBlock : IBlockParser, List< group.type = KeywordType.Static; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Unknown function name '{keyword.name.lexeme}'.", line = keyword.name.line, diff --git a/Ghost.Shader/Compiler/Parser/PassBlock.cs b/Ghost.Shader/Compiler/Parser/PassBlock.cs index a2962eb..bae5904 100644 --- a/Ghost.Shader/Compiler/Parser/PassBlock.cs +++ b/Ghost.Shader/Compiler/Parser/PassBlock.cs @@ -61,7 +61,7 @@ internal class PassBlock : IBlockParser return pass; } - public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List errors) + public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List errors) { if (syntax == null) { @@ -81,7 +81,7 @@ internal class PassBlock : IBlockParser if (semantic.localProperties != null && semantic.localProperties.Any(p => p.scope == PropertyScope.Global)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Global properties cannot be declared inside a pass. Move them to the shader properties block.", line = syntax.name.line, @@ -108,7 +108,7 @@ internal class PassBlock : IBlockParser break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Unknown function '{func.name.lexeme}' in pass {syntax.name.lexeme}.", line = func.name.line, @@ -123,7 +123,7 @@ internal class PassBlock : IBlockParser { // TODO: Inheritance from base pass. // TODO: Add mesh shader support. - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Pass {syntax.name.lexeme} must contain a mesh shader (ms) and a pixel shader (ps) declaration.", line = syntax.name.line, @@ -134,11 +134,11 @@ internal class PassBlock : IBlockParser return semantic; } - private static void AnalysisShaderEntry(List errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint) + private static void AnalysisShaderEntry(List errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint) { if (func.arguments?.Count != 2) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Shader declaration requires exactly two arguments: (shaderPath, entryPoint).", line = func.name.line, diff --git a/Ghost.Shader/Compiler/Parser/PipelineBlock.cs b/Ghost.Shader/Compiler/Parser/PipelineBlock.cs index f7aa62e..d0e08e6 100644 --- a/Ghost.Shader/Compiler/Parser/PipelineBlock.cs +++ b/Ghost.Shader/Compiler/Parser/PipelineBlock.cs @@ -38,7 +38,7 @@ internal class PipelineBlock : IBlockParser return pipeline; } - public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List errors) + public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List errors) { if (syntax == null) { @@ -80,7 +80,7 @@ internal class PipelineBlock : IBlockParser semantic.zTest = ZTestOptions.Always; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Invalid ZTest option: {valueDecl.value.lexeme}", line = valueDecl.value.line, @@ -100,7 +100,7 @@ internal class PipelineBlock : IBlockParser semantic.zWrite = ZWriteOptions.Off; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Invalid ZWrite option: {valueDecl.value.lexeme}", line = valueDecl.value.line, @@ -123,7 +123,7 @@ internal class PipelineBlock : IBlockParser semantic.cull = CullOptions.Back; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Invalid Cull option: {valueDecl.value.lexeme}", line = valueDecl.value.line, @@ -152,7 +152,7 @@ internal class PipelineBlock : IBlockParser semantic.blend = BlendOptions.PremultipliedAlpha; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Invalid Blend option: {valueDecl.value.lexeme}", line = valueDecl.value.line, @@ -169,7 +169,7 @@ internal class PipelineBlock : IBlockParser } else { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Invalid Color Mask value: {valueDecl.value.lexeme}", line = valueDecl.value.line, diff --git a/Ghost.Shader/Compiler/Parser/PropertiesBlock.cs b/Ghost.Shader/Compiler/Parser/PropertiesBlock.cs index b4975f7..b83b37c 100644 --- a/Ghost.Shader/Compiler/Parser/PropertiesBlock.cs +++ b/Ghost.Shader/Compiler/Parser/PropertiesBlock.cs @@ -6,7 +6,7 @@ namespace Ghost.Shader.Compiler.Parser; internal class PropertiesBlock : IBlockParser> { - private delegate object? PropertyValueBuilder(List tokens, List errors); + private delegate object? PropertyValueBuilder(List tokens, List errors); private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, PropertyValueBuilder? Builder); @@ -78,11 +78,11 @@ internal class PropertiesBlock : IBlockParser ParseTextureDefault(syntax[0], errors)), }; - private static float ParseFloatValue(Token token, List errors) + private static float ParseFloatValue(Token token, List errors) { if (!float.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Failed to parse float value '{token.lexeme}'.", line = token.line, @@ -93,11 +93,11 @@ internal class PropertiesBlock : IBlockParser errors) + private static int ParseIntValue(Token token, List errors) { if (!int.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Failed to parse int value '{token.lexeme}'.", line = token.line, @@ -108,11 +108,11 @@ internal class PropertiesBlock : IBlockParser errors) + private static uint ParseUIntValue(Token token, List errors) { if (!uint.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Failed to parse uint value '{token.lexeme}'.", line = token.line, @@ -123,11 +123,11 @@ internal class PropertiesBlock : IBlockParser errors) + private static bool ParseBoolValue(Token token, List errors) { if (!bool.TryParse(token.lexeme, out var result)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Failed to parse bool value '{token.lexeme}'.", line = token.line, @@ -138,11 +138,11 @@ internal class PropertiesBlock : IBlockParser errors) + private static string ParseTextureDefault(Token token, List errors) { if (!TokenLexicon.IsTextureDefaultValue(token.lexeme)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Texture default value '{token.lexeme}' is not valid.", line = token.line, @@ -242,7 +242,7 @@ internal class PropertiesBlock : IBlockParser? SemanticAnalysis(PropertiesSyntax? syntax, List errors) + public static List? SemanticAnalysis(PropertiesSyntax? syntax, List errors) { if (syntax == null) { @@ -295,11 +295,11 @@ internal class PropertiesBlock : IBlockParser errors, PropertyDeclaration property, PropertySemantic model) + private static bool ValidatePropertyType(List errors, PropertyDeclaration property, PropertySemantic model) { if (!TokenLexicon.IsType(property.type.lexeme)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Shader property type '{property.type.lexeme}' is not a valid type.", line = property.type.line, @@ -313,11 +313,11 @@ internal class PropertiesBlock : IBlockParser errors, HashSet usedPropertyNames, PropertyDeclaration property, PropertySemantic model) + private static bool ValidatePropertyName(List errors, HashSet usedPropertyNames, PropertyDeclaration property, PropertySemantic model) { if (string.IsNullOrWhiteSpace(property.name.lexeme)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Shader property has an empty name.", line = property.name.line, @@ -328,7 +328,7 @@ internal class PropertiesBlock : IBlockParser errors, PropertyDeclaration property, PropertySemantic model) + private static bool ValidatePropertyConstructor(List errors, PropertyDeclaration property, PropertySemantic model) { var constructor = property.propertyConstructor; if (!constructor.HasValue) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Shader property constructor is null.", line = property.name.line, @@ -360,7 +360,7 @@ internal class PropertiesBlock : IBlockParser return shader; } - public static ShaderSemantics? SemanticAnalysis(ShaderSyntax? syntax, List errors) + public static ShaderSemantics? SemanticAnalysis(ShaderSyntax? syntax, List errors) { if (syntax == null) { @@ -85,7 +85,7 @@ internal class ShaderBlock : IBlockParser case TokenLexicon.KnownFunctions.FALLBACK: if (func.arguments == null || func.arguments.Count != 1) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Fallback declaration requires exactly one arguments: (fallback shader name).", line = func.name.line, @@ -98,7 +98,7 @@ internal class ShaderBlock : IBlockParser shaderModel.fallback = func.arguments[0].lexeme; break; default: - errors.Add(new ShaderError + errors.Add(new SDLError { message = $"Unknown function '{func.name.lexeme}' in shader.", line = func.name.line, diff --git a/Ghost.Shader/Compiler/ShaderCompiler.cs b/Ghost.Shader/Compiler/SDLCompiler.cs similarity index 90% rename from Ghost.Shader/Compiler/ShaderCompiler.cs rename to Ghost.Shader/Compiler/SDLCompiler.cs index 53a76f3..7c7fa26 100644 --- a/Ghost.Shader/Compiler/ShaderCompiler.cs +++ b/Ghost.Shader/Compiler/SDLCompiler.cs @@ -1,10 +1,11 @@ -using Ghost.Core.Graphics; +using Ghost.Core; +using Ghost.Core.Graphics; using Ghost.Shader.Compiler.Parser; using System.Text; namespace Ghost.Shader.Compiler; -public struct ShaderError +public struct SDLError { public string message; public int line; @@ -16,7 +17,7 @@ public struct ShaderError } } -internal static class ShaderCompiler +internal static class SDLCompiler { private const string _GLOBAL_PROPERTY_FILE_NAME = "GlobalData.g.hlsl"; private const string _GENERATED_FILE_HEADER = "// Auto-generated shader file. Please do not edit this file directly."; @@ -51,13 +52,13 @@ internal static class ShaderCompiler return shaders; } - public static ShaderSemantics? SemanticAnalysis(ShaderSyntax syntax, out List errors) + public static ShaderSemantics? SemanticAnalysis(ShaderSyntax syntax, out List errors) { errors = new(); if (string.IsNullOrWhiteSpace(syntax.name.lexeme)) { - errors.Add(new ShaderError + errors.Add(new SDLError { message = "Shader name cannot be empty.", line = syntax.name.line, @@ -244,6 +245,29 @@ internal static class ShaderCompiler return descriptor; } + public static Result CompileShader(string shaderPath) + { + var source = File.ReadAllText(shaderPath); + + var lexer = new Lexer(source); + var stream = new TokenStream(lexer.Tokenize()); + var shaderInfo = ParseShaders(stream); + var model = SemanticAnalysis(shaderInfo[0], out var errors); + + if (errors.Count != 0 || model == null) + { + var errorMessages = new StringBuilder(); + foreach (var error in errors) + { + errorMessages.AppendLine(error.ToString()); + } + + return Result.Fail("Failed to compile shader due to errors:\n" + errorMessages.ToString()); + } + + return ResolveShader(model); + } + private static string ShaderPropertyTypeToHLSLType(ShaderPropertyType type) { return type switch @@ -274,7 +298,7 @@ internal static class ShaderCompiler }; } - public static string CompilePass(IPassDescriptor descriptor, string targetDirectory) + public static string GeneratePass(IPassDescriptor descriptor, string targetDirectory) { if (descriptor is not FullPassDescriptor fullPass) { @@ -330,7 +354,7 @@ struct PerMaterialData return outputFilePath; } - public static void CompileShader(ShaderDescriptor descriptor, string targetDirectory) + public static void GenerateShader(ShaderDescriptor descriptor, string targetDirectory) { if (!Directory.Exists(targetDirectory)) { @@ -369,7 +393,7 @@ struct GlobalData // Compile each pass. foreach (var pass in descriptor.passes) { - CompilePass(pass, targetDirectory); + GeneratePass(pass, targetDirectory); } } } \ No newline at end of file