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.
This commit is contained in:
2025-10-23 14:42:53 +09:00
parent d2d9f5feb7
commit 28c386b0bb
28 changed files with 393 additions and 306 deletions

View File

@@ -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

View File

@@ -8,7 +8,7 @@ using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
/// D3D12 implementation of viewGroup allocator that manages different types of viewGroup heaps.
/// </summary>
internal unsafe class D3D12DescriptorAllocator : IDisposable
{

View File

@@ -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];
}

View File

@@ -555,32 +555,16 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
public Handle<Material> CreateMaterial(Identifier<Shader> 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<Shader> CreateShader(ShaderDescriptor descriptor)
{
var shaderData = new Shader();
return _resourceDatabase.AddShader(ref shaderData);
var shader = new Shader(descriptor);
return _resourceDatabase.AddShader(shader);
}
#region Conversion Methods

View File

@@ -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<ResourceRecord> _resources;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<ResourceRecord, string> _resourceName;
private readonly Dictionary<Handle<GPUResource>, string> _resourceName;
#endif
private readonly UnsafeSlotMap<Mesh> _meshes;
private readonly UnsafeSlotMap<Material> _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<Slot<Shader>> _shaders;
private readonly DynamicArray<Shader?> _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<ShaderPassKey, ShaderPass> _shaderPasses;
private bool _disposed;
@@ -135,28 +134,41 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
where T : IResourceReleasable
{
resource.ReleaseResource(this);
resource = default!;
}
public Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged
public Handle<GPUResource> ImportExternalResource(ComPtr<ID3D12Resource> resource, ResourceState initialState, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (resource is not ComPtr<ID3D12Resource> d3d12Resource)
var id = _resources.Add(new ResourceRecord(resource, initialState), out var generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
if (name != null)
{
throw new InvalidOperationException($"Expect ComPtr<ID3D12Resource> in D3D12ResourceDatabase, but got {typeof(T)}.");
_resourceName[handle] = name;
}
#endif
return handle;
}
var id = _resources.Add(new ResourceRecord(d3d12Resource, initialState), out var generation);
return new Handle<GPUResource>(id, generation);
}
public Handle<GPUResource> AddResource(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
public Handle<GPUResource> AddResource(ComPtr<D3D12MA_Allocation> 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<GPUResource>(id, generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
if (name != null)
{
_resourceName[handle] = name;
}
#endif
return handle;
}
public ref ResourceRecord GetResourceInfo(Handle<GPUResource> handle)
@@ -218,13 +230,28 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public int GetBindlessIndex(Handle<GPUResource> 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<GPUResource> 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<GPUResource> 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<Mesh> 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<Mesh> 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<Material> 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<Material> 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<Shader> AddShader(ref readonly Shader shader)
public Identifier<Shader> AddShader(Shader shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _shaders.Count;
_shaders.Add(new Slot<Shader> { value = shader, occupied = true });
_shaders.Add(shader);
return new Identifier<Shader>(id);
}
public bool HasShader(Identifier<Shader> 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<Shader> id)
public Shader GetShaderReference(Identifier<Shader> 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<Shader> 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;
}
#endif
ReleaseResource(ref shader);
}
// Same for shader pass.
foreach (var kv in _shaderPasses)
{
var pass = kv.Value;
ReleaseResource(ref pass);
}
_resources.Dispose();
_meshes.Dispose();
_materials.Dispose();

View File

@@ -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

View File

@@ -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,

View File

@@ -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<char> name)
{
resource.SetName(name.GetUnsafePtr());
}
}
}
internal static class D3D12_RASTERIZER_DESC_Extensions
{
extension(D3D12_RASTERIZER_DESC)
@@ -151,3 +141,50 @@ internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions
}
}
}
internal unsafe static class D3D12Utility
{
public static void SetName(ref this ID3D12Resource resource, ReadOnlySpan<char> 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,
};
}
}

View File

@@ -18,20 +18,12 @@ internal struct CBufferCache : IResourceReleasable
public readonly Handle<GraphicsBuffer> GpuResource => _gpuResource;
public readonly uint AlignedSize => _alignedSize;
public unsafe CBufferCache(IResourceAllocator allocator, uint bufferSize)
public unsafe CBufferCache(Handle<GraphicsBuffer> 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 => _shader;
public Material(Identifier<Shader> 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<T>(string propertyName, in T value)

View File

@@ -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<string, int> _propertyLookup;
private readonly UnsafeList<PropertyInfo> _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();
}
}
/// <summary>
/// A representation of a GPU shader, including all the passes it contains.
/// </summary>
public readonly struct Shader : IResourceReleasable, IIdentifierType
public class Shader : IResourceReleasable, IIdentifierType
{
private readonly ShaderPassKey[] _passIDs;
private UnsafeArray<ShaderPassKey> _passIDs;
private readonly Dictionary<string, int> _passLookup; // pass name to index
private readonly Dictionary<string, List<int>> _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<ShaderPassKey>(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<int> GetPropertyPassIndices(string propertyName)
public IReadOnlyCollection<int> 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();
}
}

View File

@@ -23,6 +23,7 @@
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
<ProjectReference Include="..\Ghost.Shader\Ghost.Shader.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -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
/// <summary>
/// Swap chain description
/// </summary>
public struct SwapChainDesc
{
public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
/// <summary>
/// Width of the swap chain
/// </summary>
public uint width;
/// <summary>
/// Height of the swap chain
/// </summary>
public uint height;
/// <summary>
/// Back buffer format
/// </summary>
public TextureFormat format;
/// <summary>
/// Target for presentation (window handle or composition target)
/// </summary>
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;
}
}
/// <summary>
/// Swap chain target (window handle or composition surface)
/// </summary>
public struct SwapChainTarget
{
/// <summary>
/// Target type
/// </summary>
public SwapChainTargetType type;
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint windowHandle;
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
public object? compositionSurface;
public static SwapChainTarget FromWindowHandle(nint hwnd)
{
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,
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
};
}
}
/// <summary>
/// Swap chain target types
/// </summary>
public enum SwapChainTargetType
{
WindowHandle,
Composition
}

View File

@@ -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.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
public Identifier<Shader> CreateShader();
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
public Identifier<Shader> CreateShader(ShaderDescriptor descriptor);
/// <summary>
/// Release a resource given its handle

View File

@@ -13,6 +13,7 @@ public interface IResourceReleasable
public interface IResourceDatabase
{
/*
/// <summary>
/// Imports an external unmanaged resource and returns a handle for use within the resource management system.
/// </summary>
@@ -20,8 +21,9 @@ public interface IResourceDatabase
/// <param name="resourcePtr">A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage.</param>
/// <param name="initialState">The initial state to assign to the imported resource.</param>
/// <returns>A handle representing the imported resource, which can be used for subsequent operations.</returns>
unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState)
unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState, string? name = null)
where T : unmanaged;
*/
/// <summary>
/// Retrieves the current state of the specified resource.
@@ -51,6 +53,16 @@ public interface IResourceDatabase
/// <returns>The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found.</returns>
int GetBindlessIndex(Handle<GPUResource> handle);
/// <summary>
/// Retrieves the name of the GPU resource associated with the specified handle.
/// </summary>
/// <remarks>
/// You should only use this method in debug builds or inside engine editor.
/// </remarks>
/// <param name="handle">A handle to the GPU resource for which to obtain the name. Must reference a valid resource.</param>
/// <returns>The name of the GPU resource associated with the specified handle, or null if the resource does not have a name.</returns>
string? GetResourceName(Handle<GPUResource> handle);
/// <summary>
/// Removes a resource from the database using its handle.
/// </summary>
@@ -116,7 +128,7 @@ public interface IResourceDatabase
/// </summary>
/// <param name="shader">The shader to add. The shader is passed by read-only reference and will not be modified.</param>
/// <returns>The <see cref="Identifier{Shader}"/> representing the newly added shader.</returns>
Identifier<Shader> AddShader(ref readonly Shader shader);
Identifier<Shader> AddShader(Shader shader);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
@@ -130,7 +142,7 @@ public interface IResourceDatabase
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A reference to the shader corresponding to the specified identifier.</returns>
ref Shader GetShaderReference(Identifier<Shader> id);
Shader GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.

View File

@@ -51,87 +51,3 @@ public interface ISwapChain : IDisposable
/// <param name="height">New height</param>
public void Resize(uint width, uint height);
}
/// <summary>
/// Swap chain description
/// </summary>
public struct SwapChainDesc
{
/// <summary>
/// Width of the swap chain
/// </summary>
public uint width;
/// <summary>
/// Height of the swap chain
/// </summary>
public uint height;
/// <summary>
/// Back buffer format
/// </summary>
public TextureFormat format;
/// <summary>
/// Target for presentation (window handle or composition target)
/// </summary>
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;
}
}
/// <summary>
/// Swap chain target (window handle or composition surface)
/// </summary>
public struct SwapChainTarget
{
/// <summary>
/// Target type
/// </summary>
public SwapChainTargetType type;
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint windowHandle;
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
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
};
}
}
/// <summary>
/// Swap chain target types
/// </summary>
public enum SwapChainTargetType
{
WindowHandle,
Composition
}

View File

@@ -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

View File

@@ -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<byte>(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)

View File

@@ -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:");

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Shader.Test")]
[assembly: InternalsVisibleTo("Ghost.Graphics")]

View File

@@ -27,7 +27,7 @@ internal class DefinesBlock : IBlockParser<List<Token>, List<string>>
return defines;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<ShaderError> errors)
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<SDLError> errors)
{
if (syntax == null)
{

View File

@@ -4,5 +4,5 @@ internal interface IBlockParser<T, U>
{
public static abstract bool ShouldEnter(Token token);
public static abstract T? Parse(TokenStreamSlice ts);
public static abstract U? SemanticAnalysis(T? syntax, List<ShaderError> errors);
public static abstract U? SemanticAnalysis(T? syntax, List<SDLError> errors);
}

View File

@@ -27,7 +27,7 @@ internal class IncludesBlock : IBlockParser<List<Token>, List<string>>
return includes;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<ShaderError> errors)
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<SDLError> errors)
{
if (syntax == null || syntax.Count == 0)
{
@@ -44,7 +44,7 @@ internal class IncludesBlock : IBlockParser<List<Token>, List<string>>
}
else
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Included file '{path}' not found.",
line = includeToken.line,

View File

@@ -30,7 +30,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
return keywords;
}
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<ShaderError> errors)
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<SDLError> errors)
{
if (syntax == null)
{
@@ -42,7 +42,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, 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<FunctionCallDeclaration>, 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,

View File

@@ -61,7 +61,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
return pass;
}
public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List<ShaderError> errors)
public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List<SDLError> errors)
{
if (syntax == null)
{
@@ -81,7 +81,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
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<PassSyntax, PassSemantic>
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<PassSyntax, PassSemantic>
{
// 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<PassSyntax, PassSemantic>
return semantic;
}
private static void AnalysisShaderEntry(List<ShaderError> errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint)
private static void AnalysisShaderEntry(List<SDLError> 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,

View File

@@ -38,7 +38,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
return pipeline;
}
public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List<ShaderError> errors)
public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List<SDLError> errors)
{
if (syntax == null)
{
@@ -80,7 +80,7 @@ internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
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<PipelineSyntax, PipelineSemantic>
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<PipelineSyntax, PipelineSemantic>
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<PipelineSyntax, PipelineSemantic>
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<PipelineSyntax, PipelineSemantic>
}
else
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Invalid Color Mask value: {valueDecl.value.lexeme}",
line = valueDecl.value.line,

View File

@@ -6,7 +6,7 @@ namespace Ghost.Shader.Compiler.Parser;
internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySemantic>>
{
private delegate object? PropertyValueBuilder(List<Token> tokens, List<ShaderError> errors);
private delegate object? PropertyValueBuilder(List<Token> tokens, List<SDLError> errors);
private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, PropertyValueBuilder? Builder);
@@ -78,11 +78,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
[ShaderPropertyType.TextureCube] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
};
private static float ParseFloatValue(Token token, List<ShaderError> errors)
private static float ParseFloatValue(Token token, List<SDLError> 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<PropertiesSyntax, List<PropertySem
return result;
}
private static int ParseIntValue(Token token, List<ShaderError> errors)
private static int ParseIntValue(Token token, List<SDLError> 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<PropertiesSyntax, List<PropertySem
return result;
}
private static uint ParseUIntValue(Token token, List<ShaderError> errors)
private static uint ParseUIntValue(Token token, List<SDLError> 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<PropertiesSyntax, List<PropertySem
return result;
}
private static bool ParseBoolValue(Token token, List<ShaderError> errors)
private static bool ParseBoolValue(Token token, List<SDLError> 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<PropertiesSyntax, List<PropertySem
return result;
}
private static string ParseTextureDefault(Token token, List<ShaderError> errors)
private static string ParseTextureDefault(Token token, List<SDLError> 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<PropertiesSyntax, List<PropertySem
return syntax;
}
public static List<PropertySemantic>? SemanticAnalysis(PropertiesSyntax? syntax, List<ShaderError> errors)
public static List<PropertySemantic>? SemanticAnalysis(PropertiesSyntax? syntax, List<SDLError> errors)
{
if (syntax == null)
{
@@ -295,11 +295,11 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return models;
}
private static bool ValidatePropertyType(List<ShaderError> errors, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyType(List<SDLError> 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<PropertiesSyntax, List<PropertySem
return true;
}
private static bool ValidatePropertyName(List<ShaderError> errors, HashSet<string> usedPropertyNames, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyName(List<SDLError> errors, HashSet<string> 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<PropertiesSyntax, List<PropertySem
}
else if (usedPropertyNames.Contains(property.name.lexeme))
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Shader property name '{property.name.lexeme}' is duplicated.",
line = property.name.line,
@@ -342,12 +342,12 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return true;
}
private static bool ValidatePropertyConstructor(List<ShaderError> errors, PropertyDeclaration property, PropertySemantic model)
private static bool ValidatePropertyConstructor(List<SDLError> 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<PropertiesSyntax, List<PropertySem
var constructorValue = constructor.Value;
if (string.IsNullOrWhiteSpace(constructorValue.name.lexeme))
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = "Shader property constructor has an empty name.",
line = constructorValue.name.line,
@@ -372,7 +372,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (constructorValue.name.lexeme != property.type.lexeme)
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Shader property constructor name '{constructorValue.name.lexeme}' does not match property type '{property.type.lexeme}'.",
line = constructorValue.name.line,
@@ -384,7 +384,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"No constructor metadata registered for property type '{model.type}'.",
line = constructorValue.name.line,
@@ -397,7 +397,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
// Count check
if (constructorValue.arguments == null)
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = "Shader property constructor arguments are null.",
line = constructorValue.name.line,
@@ -409,7 +409,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
if (constructorValue.arguments.Count != info.ArgCount)
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {constructorValue.arguments.Count}.",
line = constructorValue.name.line,
@@ -426,7 +426,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
var arg = constructorValue.arguments[i];
if (!arg.Match(info.ArgTokenType))
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Shader property constructor argument {i} expects token kind '{info.ArgTokenType}', but got '{arg.type}'.",
line = arg.line,
@@ -451,7 +451,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
}
catch (Exception ex)
{
errors.Add(new ShaderError
errors.Add(new SDLError
{
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
line = constructorValue.name.line,

View File

@@ -49,7 +49,7 @@ internal class ShaderBlock : IBlockParser<ShaderSyntax, ShaderSemantics>
return shader;
}
public static ShaderSemantics? SemanticAnalysis(ShaderSyntax? syntax, List<ShaderError> errors)
public static ShaderSemantics? SemanticAnalysis(ShaderSyntax? syntax, List<SDLError> errors)
{
if (syntax == null)
{
@@ -85,7 +85,7 @@ internal class ShaderBlock : IBlockParser<ShaderSyntax, ShaderSemantics>
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<ShaderSyntax, ShaderSemantics>
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,

View File

@@ -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<ShaderError> errors)
public static ShaderSemantics? SemanticAnalysis(ShaderSyntax syntax, out List<SDLError> 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<ShaderDescriptor> 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<ShaderDescriptor>.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);
}
}
}