feat(shader): refactor and enhance shader compilation

Refactored shader compilation and resource management systems:
- Introduced `DXCShaderCompiler` for HLSL compilation and reflection.
- Added `BuildFinalShaderCode` method for robust shader code generation.
- Replaced raw strings with `ShaderEntryPoint` struct for shader paths.
- Updated `RenderContext` and `RenderGraphContext` for new pipeline methods.
- Added thread-safe resource management methods in `ResourceManager`.
- Introduced `DXCShaderReflectionData` for shader reflection handling.
- Removed redundant code and simplified `ShaderPropertiesRegistry`.

BREAKING CHANGE: Updated shader and resource APIs to use new structures and methods.
This commit is contained in:
2026-04-11 00:45:46 +09:00
parent 4ed5572ce7
commit f9a6e9cbbe
131 changed files with 13135 additions and 1002 deletions

View File

@@ -0,0 +1,36 @@
namespace Ghost.Core.Graphics;
[AttributeUsage(AttributeTargets.Struct)]
public class GenerateShaderPropertyAttribute : Attribute
{
public GenerateShaderPropertyAttribute(string shaderName, string? name = null)
{
}
}
[AttributeUsage(AttributeTargets.Field)]
public class GenerateAsHLSLTypeAttribute : Attribute
{
public GenerateAsHLSLTypeAttribute(string hlslTypeName)
{
}
}
public enum PackingRules
{
Exact,
Aligned,
}
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum)]
public class GenerateHLSLAttribute : Attribute
{
private readonly PackingRules _packingRules;
private readonly string? _outputSource;
public GenerateHLSLAttribute(PackingRules packingRules, string? outputSource)
{
_packingRules = packingRules;
_outputSource = outputSource;
}
}

View File

@@ -14,12 +14,12 @@ public enum KeywordSpace
Global,
}
public struct ShaderEntryPoint
public struct ShaderCode
{
public string entry;
public string shader;
public string code;
public string entryPoint;
public readonly bool IsCreated => !string.IsNullOrEmpty(entry) && !string.IsNullOrEmpty(shader);
public readonly bool IsCreated => !string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(entryPoint);
}
public struct KeywordsGroup
@@ -35,35 +35,29 @@ public struct PassDescriptor
public ulong identifier;
public string name;
public string? hlsl;
public ShaderEntryPoint taskShader;
public ShaderEntryPoint meshShader;
public ShaderEntryPoint pixelShader;
public ShaderCode amplificationShaderCode;
public ShaderCode meshShaderCode;
public ShaderCode pixelShaderCode;
public string[] defines;
public string[] includes;
public KeywordsGroup[] keywords;
public PipelineState localPipeline;
}
public class GraphicsShaderDescriptor
{
public string name = string.Empty;
public string propertiesCode = string.Empty;
public uint propertyBufferSize;
public ShaderModel shaderModel;
public PassDescriptor[] passes = Array.Empty<PassDescriptor>();
public required string name = string.Empty;
public required uint propertyBufferSize;
public required ShaderModel shaderModel;
public required PassDescriptor[] passes = Array.Empty<PassDescriptor>();
}
public class ComputeShaderDescriptor
{
public ulong identifier;
public string name = string.Empty;
public string propertiesCode = string.Empty;
public uint propertyBufferSize;
public string? hlsl;
public ShaderModel shaderModel;
public string[] defines = Array.Empty<string>();
public string[] includes = Array.Empty<string>();
public KeywordsGroup[] keywords = Array.Empty<KeywordsGroup>();
public ShaderEntryPoint[] entryPoints = Array.Empty<ShaderEntryPoint>();
public required ulong identifier;
public required string name = string.Empty;
public required uint propertyBufferSize;
public required ShaderModel shaderModel;
public required ShaderCode[] shaderCodes;
public required string[] defines;
public required KeywordsGroup[] keywords;
}

View File

@@ -1,41 +0,0 @@
namespace Ghost.Core.Graphics;
[AttributeUsage(AttributeTargets.Struct)]
public class GenerateShaderPropertyAttribute : Attribute
{
public GenerateShaderPropertyAttribute(string shaderName, string? name = null)
{
}
}
[AttributeUsage(AttributeTargets.Field)]
public class GenerateAsHLSLTypeAttribute : Attribute
{
public GenerateAsHLSLTypeAttribute(string hlslTypeName)
{
}
}
#if DEBUG || GHOST_EDITOR
public struct ShaderPropertyInfo
{
public string shaderName;
public string code;
public uint size;
}
public static class ShaderPropertiesRegistry
{
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
public static void Register(string name, string code, uint size)
{
s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size };
}
public static bool TryGetInfo(string name, out ShaderPropertyInfo info)
{
return s_nameToCode.TryGetValue(name, out info);
}
}
#endif

View File

@@ -433,4 +433,41 @@ public static class ResultExtensions
return func(result.Value);
}
}
public static void Match(this Result result, Action onSuccess, Action<string?> onFailure)
{
if (result.IsSuccess)
{
onSuccess();
}
else
{
onFailure(result.Message);
}
}
public static U Match<T, U>(this Result<T> result, Func<T, U> onSuccess, Func<string?, U> onFailure)
{
if (result.IsSuccess)
{
return onSuccess(result.Value);
}
else
{
return onFailure(result.Message);
}
}
public static U Match<T, U, E>(this Result<T, E> result, Func<T, U> onSuccess, Func<E, U> onFailure)
where E : struct, Enum
{
if (result.IsSuccess)
{
return onSuccess(result.Value);
}
else
{
return onFailure(result.Error);
}
}
}

View File

@@ -1,6 +1,6 @@
namespace Ghost.Engine;
public enum ShadowCastingMode
public enum ShadowCastingMode : uint
{
Off,
On,

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Graphics;
using Ghost.Entities;
using Ghost.Graphics;
using Misaki.HighPerformance.Jobs;

View File

@@ -1,12 +1,35 @@
using Ghost.Core;
using Ghost.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using System.Diagnostics;
namespace Ghost.Engine.RenderPipeline;
internal class GhostRenderPipeline : IRenderPipeline
{
private struct AddInstanceData
{
public float4x4 localToWorld;
public uint instanceId;
public uint meshBuffer;
public uint materialPalette;
public uint renderingLayerMask;
public uint shadowCastingMode;
}
private struct RemoveInstanceData
{
public uint instanceId;
public uint swapWithInstanceId;
}
private readonly RenderSystem _renderSystem;
private readonly RenderGraph _renderGraph;
private readonly GPUScene _gpuScene;
public GPUScene GPUScene => _gpuScene;
@@ -14,9 +37,90 @@ internal class GhostRenderPipeline : IRenderPipeline
public GhostRenderPipeline(RenderSystem renderSystem)
{
_renderSystem = renderSystem;
_renderGraph = new RenderGraph(renderSystem.ResourceManager, renderSystem.GraphicsEngine);
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
}
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
{
if (!ghostPayload.AddRequest.IsEmpty)
{
var addDesc = new BufferDesc
{
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<AddInstanceData>(),
Stride = (uint)MemoryUtility.SizeOf<AddInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload
};
var addBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Add Instance Buffer");
var pAddData = (AddInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
var i = 0;
while (ghostPayload.AddRequest.TryDequeue(out var addRequest))
{
var (mesh, error) = resourceManager.GetMeshReference(addRequest.meshInstance.mesh);
if (error.IsFailure)
{
Debug.Fail($"Failed to get mesh reference for mesh instance with ID {addRequest.instanceId}");
continue;
}
pAddData[i] = new AddInstanceData
{
localToWorld = addRequest.localToWorld,
instanceId = addRequest.instanceId,
meshBuffer = resourceDatabase.GetBindlessIndex(mesh.Get().MeshDataBuffer.AsResource()),
materialPalette = (uint)addRequest.meshInstance.materialPalette.Value,
renderingLayerMask = addRequest.meshInstance.renderingLayerMask,
shadowCastingMode = (uint)addRequest.meshInstance.shadowCastingMode
};
i++;
}
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
return addBuffer;
}
return default;
}
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
{
if (!ghostPayload.RemoveRequest.IsEmpty)
{
var addDesc = new BufferDesc
{
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<RemoveInstanceData>(),
Stride = (uint)MemoryUtility.SizeOf<RemoveInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload
};
var removeBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Remove Instance Buffer");
var pRemoveData = (RemoveInstanceData*)resourceDatabase.MapResource(removeBuffer.AsResource(), 0, null);
var i = 0;
while (ghostPayload.RemoveRequest.TryDequeue(out var removeRequest))
{
pRemoveData[i] = new RemoveInstanceData
{
instanceId = removeRequest.instanceId,
swapWithInstanceId = removeRequest.swapWithInstanceId
};
i++;
}
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
return removeBuffer;
}
return default;
}
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
{
var ghostPayload = (GhostRenderPayload)payload;
@@ -26,15 +130,21 @@ internal class GhostRenderPipeline : IRenderPipeline
foreach (ref readonly var request in ghostPayload.RenderRequests)
{
if (!RenderPipelineUtility.GetViewAndProjectionMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
if (!RenderPipelineUtility.GetVPMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
{
continue;
}
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
}
}
public void Dispose()
{
throw new NotImplementedException();
_renderGraph.Dispose();
_gpuScene.Dispose();
}
}

View File

@@ -191,7 +191,7 @@ struct {info.Name}
codeBuilder.Clear();
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
registerBuilder.AppendLine($@" global::Ghost.DSL.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
}
var registerTypeName = "g_shaderproperty_registeration";

View File

@@ -3,44 +3,15 @@
#endif
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Utilities;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12;
public static class D3D12GraphicsEngineFactory
{
static D3D12GraphicsEngineFactory()
{
var currentDir = AppContext.BaseDirectory;
var platform = OperatingSystem.IsWindows() ? "win" :
OperatingSystem.IsLinux() ? "linux" :
OperatingSystem.IsMacOS() ? "osx" : "unknown";
var arch = Environment.Is64BitProcess ? "x64" : "x86";
var nativeDllDir = Path.Combine(currentDir, "runtimes", platform + "-" + arch, "native");
AssemblyLoadContext.Default.ResolvingUnmanagedDll += (assembly, libraryName) =>
{
if (libraryName == "dxcompiler")
{
NativeLibrary.TryLoad(Path.Combine(nativeDllDir, "dxil.dll"), out _);
if (NativeLibrary.TryLoad(Path.Combine(nativeDllDir, "dxcompiler.dll"), out var dxcHandle))
{
return dxcHandle;
}
}
return IntPtr.Zero;
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IGraphicsEngine Create(GraphicsEngineDesc desc)
{
@@ -68,7 +39,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
private readonly D3D12DebugLayer _debugLayer;
#endif
private readonly D3D12RenderDevice _device;
private readonly DXCShaderCompiler _shaderCompiler;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary;
@@ -81,7 +51,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
private bool _disposed;
public IRenderDevice Device => _device;
public IShaderCompiler ShaderCompiler => _shaderCompiler;
public IPipelineLibrary PipelineLibrary => _pipelineLibrary;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
@@ -94,7 +63,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_debugLayer = new D3D12DebugLayer();
#endif
_device = new D3D12RenderDevice();
_shaderCompiler = new DXCShaderCompiler();
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
_resourceDatabase = new D3D12ResourceDatabase(_descriptorAllocator);
@@ -133,7 +101,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
{
Debug.Assert(!_disposed);
for (int i = 0; i < _commandBufferPool.Count; i++)
for (var i = 0; i < _commandBufferPool.Count; i++)
{
if (_commandBufferPool[i].Type == type)
{
@@ -209,7 +177,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_resourceDatabase.Dispose();
_descriptorAllocator.Dispose();
_shaderCompiler.Dispose();
_device.Dispose();
#if ENABLE_DEBUG_LAYER
_debugLayer.Dispose();

View File

@@ -28,7 +28,6 @@ internal struct D3D12PipelineState : IDisposable
internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>, IPipelineLibrary
{
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UniquePtr<ID3D12RootSignature> _defaultRootSignature;
@@ -56,11 +55,10 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
return pLibrary;
}
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
public D3D12PipelineLibrary(D3D12RenderDevice device)
: base(CreateLibrary(device, null)) // TODO: we need to path to load the existing library from disk.
{
_device = device;
_resourceDatabase = resourceDatabase;
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, Allocator.Persistent);
@@ -82,7 +80,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
{
ShaderRegister = 0, // b0
RegisterSpace = 0, // space0
Num32BitValues = PushConstantsData.NUM_32BITS_VALUE
Num32BitValues = PushConstantsData.NUM_32BITS_VALUE // 3
}
};
@@ -139,32 +137,6 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
fs.Write(buffer.AsSpan());
}
private static Result ValidateReflectionData(ShaderReflectionData reflectionData)
{
if (reflectionData.ResourcesBindings.Count > RootSignatureLayout.ROOT_PARAMETER_COUNT)
{
return Result.Failure($"Shader uses more root parameters than supported ({RootSignatureLayout.ROOT_PARAMETER_COUNT}).");
}
if (reflectionData.ResourcesBindings.Count == 0)
{
return Result.Success();
}
var rootConstant = reflectionData.ResourcesBindings[0];
if (rootConstant.Type != ShaderInputType.ConstantBuffer)
{
return Result.Failure($"Root constant parameter must be a constant buffer.");
}
if (rootConstant.Size != sizeof(PushConstantsData))
{
return Result.Failure($"Root constant buffer size must be {sizeof(PushConstantsData)} bytes.");
}
return Result.Success();
}
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite)
{
var depthEnabled = ztest != ZTest.Disabled;
@@ -185,6 +157,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
}
var hr = pNativeObject->LoadPipeline(pKeyStr, pStreamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
if (hr == E.E_INVALIDARG)
{
// Pipeline not found in the library, create a new one.
@@ -204,34 +177,8 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
return Result.Success();
}
public Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
public Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode)
{
static Result ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
{
var msr = ValidateReflectionData(compiled.msResult.reflectionData);
if (msr.IsFailure)
{
return Result.Failure("Validation of mesh shader reflection data failed: " + msr.Message);
}
var psr = ValidateReflectionData(compiled.psResult.reflectionData);
if (psr.IsFailure)
{
return Result.Failure("Validation of pixel shader reflection data failed: " + psr.Message);
}
if (compiled.tsResult.IsCreated)
{
var tsr = ValidateReflectionData(compiled.tsResult.reflectionData);
if (tsr.IsFailure)
{
return Result.Failure("Validation of task shader reflection data failed: " + tsr.Message);
}
}
return Result.Success();
}
AssertNotDisposed();
if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
@@ -244,98 +191,92 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
if (!_pipelineCache.ContainsKey(pipelineKey))
{
var result = ValidatePassReflectionData(in compiled);
if (result.IsFailure)
fixed (byte* pASByteCode = asByteCode, pMSByteCode = msByteCode, pPSByteCode = psByteCode)
{
return result;
}
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{
pRootSignature = _defaultRootSignature.Get(),
MS = new D3D12_SHADER_BYTECODE(compiled.msResult.bytecode.GetUnsafePtr(), (nuint)compiled.msResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(compiled.psResult.bytecode.GetUnsafePtr(), (nuint)compiled.psResult.bytecode.Count),
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = descriptor.PipelineOption.Blend switch
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
Blend.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE,
Blend.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY,
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
},
RasterizerState = descriptor.PipelineOption.Cull switch
pRootSignature = _defaultRootSignature.Get(),
MS = new D3D12_SHADER_BYTECODE(pMSByteCode, (nuint)msByteCode.Length),
PS = new D3D12_SHADER_BYTECODE(pPSByteCode, (nuint)psByteCode.Length),
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = descriptor.PipelineOption.Blend switch
{
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
Blend.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE,
Blend.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY,
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
},
RasterizerState = descriptor.PipelineOption.Cull switch
{
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
Cull.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE,
_ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE
},
};
if (asByteCode.Length != 0)
{
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
Cull.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE,
_ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE
},
};
desc.AS = new D3D12_SHADER_BYTECODE(pASByteCode, (nuint)asByteCode.Length);
}
if (compiled.tsResult.IsCreated)
{
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
}
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
{
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
}
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
{
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
}
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &meshStream,
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
};
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &meshStream,
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
};
result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
if (result.IsFailure)
{
return result;
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
if (result.IsFailure)
{
return result;
}
}
}
return pipelineKey;
}
public Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ref readonly ShaderCompileResult compiled)
public Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csBytecode)
{
AssertNotDisposed();
var pipelineKey = RHIUtility.CreateComputePipelineKey(descriptor.VariantKey, compiled.hashCode);
var pipelineKey = RHIUtility.CreateComputePipelineKey(descriptor.VariantKey);
if (!_pipelineCache.ContainsKey(pipelineKey))
{
var result = ValidateReflectionData(compiled.reflectionData);
if (result.IsFailure)
fixed (byte* pCSByteCode = csBytecode)
{
return result;
}
var byteCode = new D3D12_SHADER_BYTECODE(pCSByteCode, (nuint)csBytecode.Length);
var desc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
var byteCode = new D3D12_SHADER_BYTECODE(compiled.bytecode.GetUnsafePtr(), (nuint)compiled.bytecode.Length);
var desc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &desc,
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
};
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &desc,
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
};
result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
if (result.IsFailure)
{
return result;
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
if (result.IsFailure)
{
return result;
}
}
}

View File

@@ -1,548 +0,0 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.InteropServices;
using System.Text;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.DirectX.DXC;
namespace Ghost.Graphics.Core;
internal sealed partial class DXCShaderCompiler
{
private static string GetProfileString(ShaderStage stage, ShaderModel version)
{
return (stage, version) switch
{
(ShaderStage.TaskShader, ShaderModel.SM_6_6) => "as_6_6",
(ShaderStage.PixelShader, ShaderModel.SM_6_6) => "ps_6_6",
(ShaderStage.MeshShader, ShaderModel.SM_6_6) => "ms_6_6",
(ShaderStage.ComputeShader, ShaderModel.SM_6_6) => "cs_6_6",
(ShaderStage.TaskShader, ShaderModel.SM_6_7) => "as_6_7",
(ShaderStage.PixelShader, ShaderModel.SM_6_7) => "ps_6_7",
(ShaderStage.MeshShader, ShaderModel.SM_6_7) => "ms_6_7",
(ShaderStage.ComputeShader, ShaderModel.SM_6_7) => "cs_6_7",
(ShaderStage.TaskShader, ShaderModel.SM_6_8) => "as_6_8",
(ShaderStage.PixelShader, ShaderModel.SM_6_8) => "ps_6_8",
(ShaderStage.MeshShader, ShaderModel.SM_6_8) => "ms_6_8",
(ShaderStage.ComputeShader, ShaderModel.SM_6_8) => "cs_6_8",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
};
}
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
{
return level switch
{
CompilerOptimizeLevel.O0 => DXC_ARG_OPTIMIZATION_LEVEL0,
CompilerOptimizeLevel.O1 => DXC_ARG_OPTIMIZATION_LEVEL1,
CompilerOptimizeLevel.O2 => DXC_ARG_OPTIMIZATION_LEVEL2,
CompilerOptimizeLevel.O3 => DXC_ARG_OPTIMIZATION_LEVEL3,
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
};
}
private static List<string> GetCompilerArguments(ref readonly ShaderCompilationConfig config)
{
var argsArray = new List<string>
{
"-T", GetProfileString(config.stage, config.model), // Target profile (ms_6_6, ps_6_6)
"-E", config.entryPoint, // Entry point
"-HV", "2021", // HLSL version 2021
"-enable-16bit-types", // Enable 16-bit types
GetOptimizeLevelString(config.optimizeLevel), // Optimization level
};
foreach (var define in config.defines)
{
argsArray.Add("-D");
argsArray.Add(define);
}
if (config.stage == ShaderStage.TaskShader
|| config.stage == ShaderStage.MeshShader
|| config.stage == ShaderStage.PixelShader)
{
argsArray.Add("-D");
argsArray.Add("__GRAPHICS__");
}
else if (config.stage == ShaderStage.ComputeShader)
{
argsArray.Add("-D");
argsArray.Add("__COMPUTE__");
}
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
{
argsArray.Add("-Qstrip_debug");
}
if (!config.options.HasFlag(CompilerOption.KeepReflections))
{
argsArray.Add("-Qstrip_reflect");
}
if (config.options.HasFlag(CompilerOption.WarnAsError))
{
argsArray.Add(DXC_ARG_WARNINGS_ARE_ERRORS);
}
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
{
argsArray.Add("-spirv");
}
return argsArray;
}
private static Result<string, Error> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
{
string shaderCode;
if (shaderPath == "hlsl_block")
{
if (string.IsNullOrEmpty(injectedCode))
{
return Error.InvalidArgument;
}
shaderCode = string.Empty;
}
else
{
if (!File.Exists(shaderPath))
{
return Error.NotFound;
}
shaderCode = File.ReadAllText(shaderPath);
}
var sb = new StringBuilder();
foreach (var includePath in includes)
{
sb.AppendLine($"#include \"{includePath}\"");
}
if (!string.IsNullOrEmpty(injectedCode))
{
sb.AppendLine($"#line 0 \"injected_code\"");
sb.AppendLine(injectedCode);
}
if (!string.IsNullOrEmpty(shaderCode))
{
sb.AppendLine($"#line 0 \"{shaderPath}\"");
sb.AppendLine(shaderCode);
}
return sb.ToString();
}
private static ShaderInputType ToInputType(D3D_SHADER_INPUT_TYPE type)
{
return type switch
{
D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER => ShaderInputType.ConstantBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_TBUFFER => ShaderInputType.Texture,
D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE => ShaderInputType.Texture,
D3D_SHADER_INPUT_TYPE.D3D_SIT_SAMPLER => ShaderInputType.Sampler,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWTYPED => ShaderInputType.UAV,
D3D_SHADER_INPUT_TYPE.D3D_SIT_STRUCTURED => ShaderInputType.StructuredBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_BYTEADDRESS => ShaderInputType.ByteAddressBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWSTRUCTURED => ShaderInputType.RWStructuredBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWBYTEADDRESS => ShaderInputType.RWByteAddressBuffer,
_ => throw new ArgumentOutOfRangeException(nameof(type), "Unsupported shader input type")
};
}
}
internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
{
private UniquePtr<IDxcCompiler3> _compiler;
private UniquePtr<IDxcUtils> _utils;
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
// TODO: This should be shader variant specific cache instead of pass specific.
private readonly Dictionary<ulong, GraphicsCompiledResult> _compiledResults;
private bool _disposed;
public DXCShaderCompiler()
{
// Initialize DXC _compiler.Get() and _utils.Get()
var dxccID = CLSID.CLSID_DxcCompiler;
var dxcuID = CLSID.CLSID_DxcUtils;
IDxcCompiler3* pCompiler = default;
IDxcUtils* pUtils = default;
ThrowIfFailed(DxcCreateInstance(&dxccID, __uuidof(pCompiler), (void**)&pCompiler));
ThrowIfFailed(DxcCreateInstance(&dxcuID, __uuidof(pUtils), (void**)&pUtils));
_compiler.Attach(pCompiler);
_utils.Attach(pUtils);
_compiledResults = new Dictionary<ulong, GraphicsCompiledResult>();
}
~DXCShaderCompiler()
{
Dispose();
}
private Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* pReflectionBlob)
{
ID3D12ShaderReflection* pReflection = default;
try
{
// Create DXC _utils.Get() to parse reflection data
var dxcuID = CLSID.CLSID_DxcUtils;
// Create reflection interface from blob
var reflectionBuffer = new DxcBuffer
{
Ptr = pReflectionBlob->GetBufferPointer(),
Size = pReflectionBlob->GetBufferSize(),
Encoding = DXC_CP_ACP
};
ThrowIfFailed(_utils.Get()->CreateReflection(&reflectionBuffer, __uuidof(pReflection), (void**)&pReflection));
D3D12_SHADER_DESC shaderDesc;
ThrowIfFailed(pReflection->GetDesc(&shaderDesc));
var reflectionData = new ShaderReflectionData();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
ThrowIfFailed(pReflection->GetResourceBindingDesc(i, &bindDesc));
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
if (resourceName == null)
{
return Result.Failure("Failed to get resource name from reflection data.");
}
var info = new ResourceBindingInfo
{
Name = resourceName,
Type = ToInputType(bindDesc.Type),
BindPoint = bindDesc.BindPoint,
BindCount = bindDesc.BindCount,
Space = bindDesc.Space
};
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
}
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
}
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
}
reflectionData.ResourcesBindings.Add(info);
}
return reflectionData;
}
finally
{
pReflection->Release();
}
}
public Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator)
{
ObjectDisposedException.ThrowIf(_disposed, this);
using ComPtr<IDxcIncludeHandler> includeHandler = default;
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
var finalShaderCodeResult = BuildFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
if (finalShaderCodeResult.IsFailure)
{
return Result.Failure(finalShaderCodeResult.Error);
}
var finalShaderCode = finalShaderCodeResult.Value;
fixed (byte* pCode = System.Text.Encoding.UTF8.GetBytes(finalShaderCode))
{
var sizeInBytes = System.Text.Encoding.UTF8.GetByteCount(finalShaderCode);
ThrowIfFailed(_utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, DXC_CP_UTF8, sourceBlob.GetAddressOf()));
}
var argsArray = GetCompilerArguments(in config);
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
using ComPtr<IDxcResult> result = default;
try
{
// Compile shader
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC_CP_UTF8
};
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, __uuidof(result.Get()), (void**)&result));
// Check compilation result
HRESULT hrStatus;
result.Get()->GetStatus(&hrStatus);
if (hrStatus.FAILED)
{
// Get error messages
IDxcBlobEncoding* pErrorBlob = default;
result.Get()->GetErrorBuffer(&pErrorBlob);
if (pErrorBlob != null)
{
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
pErrorBlob->Release();
return Result.Failure($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
return Result.Failure("DXC shader compilation failed with unknown error.");
}
}
// Get compiled bytecode
using ComPtr<IDxcBlob> bytecodeBlob = default;
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
ShaderReflectionData reflectionData = default;
if (config.options.HasFlag(CompilerOption.KeepReflections))
{
using ComPtr<IDxcBlob> reflection = default;
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof(reflection.Get()), (void**)&reflection, null).SUCCEEDED)
{
reflectionData = PerformDXCReflection(reflection).GetValueOrDefault();
}
}
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new ShaderCompileResult
{
bytecode = bytecode,
reflectionData = reflectionData,
hashCode = XxHash64.HashToUInt64(bytecode)
};
}
finally
{
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
}
}
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords)
{
ObjectDisposedException.ThrowIf(_disposed, this);
string[] fullDefines;
var totalDefineCount = descriptor.defines.Length + additionalConfig.defines.Length;
if (totalDefineCount == 0)
{
fullDefines = Array.Empty<string>();
}
else
{
fullDefines = new string[totalDefineCount];
descriptor.defines.CopyTo(fullDefines);
additionalConfig.defines.CopyTo(fullDefines.AsSpan(descriptor.defines.Length));
}
var injectedCodeBuilder = new StringBuilder();
injectedCodeBuilder.AppendLine(descriptor.shader.propertiesCode);
injectedCodeBuilder.AppendLine(descriptor.hlsl);
injectedCodeBuilder.AppendLine(additionalConfig.injectedCode);
var injectedCode = injectedCodeBuilder.ToString();
ShaderCompileResult tsResult = default;
var tsEntry = descriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
includes = descriptor.includes,
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
injectedCode = injectedCode,
stage = ShaderStage.TaskShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
tsResult = result.Value;
}
ShaderCompileResult msResult;
var msEntry = descriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
includes = descriptor.includes,
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
injectedCode = injectedCode,
stage = ShaderStage.MeshShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
msResult = result.Value;
}
else
{
return Result.Failure("Mesh shader expected.");
}
ShaderCompileResult psResult;
var psEntry = descriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines,
includes = descriptor.includes,
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
injectedCode = injectedCode,
stage = ShaderStage.PixelShader,
model = additionalConfig.model,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
psResult = result.Value;
}
else
{
return Result.Failure("Pixel shader expected.");
}
var compiled = new GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult,
};
var passHash = RHIUtility.CreateShaderPassKey(descriptor.identifier, compiled.HashCode);
var variantHash = RHIUtility.CreateShaderVariantKey(passHash, in keywords);
_compiledResults[variantHash] = compiled;
return compiled;
}
public Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_compiledResults.TryGetValue(key, out var compiledResult))
{
return compiledResult;
}
return Error.NotFound;
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var kvp in _compiledResults)
{
kvp.Value.Dispose();
}
_compiler.Dispose();
_utils.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -16,15 +16,6 @@
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\dxcompiler.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\win-x64\native\dxil.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.1.0" />
</ItemGroup>

View File

@@ -17,11 +17,6 @@ public interface IGraphicsEngine : IDisposable
get;
}
IShaderCompiler ShaderCompiler
{
get;
}
IPipelineLibrary PipelineLibrary
{
get;

View File

@@ -6,6 +6,6 @@ public interface IPipelineLibrary : IDisposable
{
void SaveLibraryToDisk(string filePath);
bool HasPipelineStateObject(UInt128 key);
Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ref readonly ShaderCompileResult compiled);
Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode);
Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csByteCode);
}

View File

@@ -1,7 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.RHI;
@@ -9,7 +8,6 @@ namespace Ghost.Graphics.RHI;
public struct ShaderCompileResult : IDisposable
{
public UnsafeArray<byte> bytecode;
public ShaderReflectionData reflectionData;
public ulong hashCode;
public readonly bool IsCreated => bytecode.IsCreated;
@@ -20,42 +18,36 @@ public struct ShaderCompileResult : IDisposable
}
}
public struct GraphicsCompiledResult : IDisposable
public struct GraphicsCompiledResult
{
private ulong _hashCode;
public ulong tsResultHash;
public ulong msResultHash;
public ulong psResultHash;
public ShaderCompileResult tsResult;
public ShaderCompileResult msResult;
public ShaderCompileResult psResult;
public readonly ulong HashCode => Hash.Combine64(tsResultHash, msResultHash, psResultHash);
}
public Key64<GraphicsCompiledResult> HashCode
public unsafe struct ComputeCompileResult
{
public fixed ulong resultHash[8];
public readonly int count;
public ulong HashCode
{
get
{
if (_hashCode == 0)
{
_hashCode = Hash.Combine64(tsResult.hashCode, msResult.hashCode, psResult.hashCode);
}
return _hashCode;
var a = Hash.Combine64(resultHash[0], resultHash[1], resultHash[2], resultHash[3]);
var b = Hash.Combine64(resultHash[4], resultHash[5], resultHash[6], resultHash[7]);
return Hash.Combine64(a, b);
}
}
public void Dispose()
{
tsResult.Dispose();
msResult.Dispose();
psResult.Dispose();
}
}
public ref struct ShaderCompilationConfig
{
public ReadOnlySpan<string> defines;
public ReadOnlySpan<string> includes;
public string shaderPath;
public string shaderCode;
public string entryPoint;
public string? injectedCode;
public ShaderStage stage;
public ShaderModel model;
public CompilerOptimizeLevel optimizeLevel;
@@ -154,7 +146,7 @@ public readonly struct ShaderReflectionData
public interface IShaderCompiler : IDisposable
{
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
Result<Key64<ShaderCompileResult>> Compile(ref readonly ShaderCompilationConfig config);
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords);
Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key);
Result<ShaderCompileResult, Error> GetCompiledCache(Key64<ShaderCompileResult> key);
}

View File

@@ -156,11 +156,10 @@ public static class RHIUtility
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Key64<ShaderVariant> CreateShaderVariantKey(Key64<ShaderPass> passKey, ref readonly LocalKeywordSet keywords)
public static Key64<ShaderVariant> CreateShaderVariantKey(ulong passKey, ref readonly LocalKeywordSet keywords)
{
var passHash = passKey.Value;
var keywordHash = keywords.GetHash64();
return new Key64<ShaderVariant>(Hash.Combine64(passHash, keywordHash));
return new Key64<ShaderVariant>(Hash.Combine64(passKey, keywordHash));
}
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
@@ -192,10 +191,10 @@ public static class RHIUtility
return new Key128<GraphicsPipeline>(new UInt128(hi, lo));
}
public static Key128<ComputePipeline> CreateComputePipelineKey(Key64<ShaderVariant> shaderVariantKey, ulong compiledHash)
public static Key128<ComputePipeline> CreateComputePipelineKey(Key64<ShaderVariant> shaderVariantKey)
{
var shaderHash = shaderVariantKey.Value;
var stateHash = compiledHash;
var stateHash = ~shaderVariantKey.Value;
// Simple XOR mix. Not as robust as the graphics pipeline key, but sufficient for compute shaders which have fewer variants.
var hi = shaderHash ^ (stateHash + 0x9E3779B97F4A7C15ul) ^ (shaderHash * 0xD6E8FEB86659FD93ul);
var lo = stateHash ^ (shaderHash + 0xC2B2AE3D27D4EB4Ful) ^ (stateHash * 0x165667B19E3779F9ul);

View File

@@ -50,14 +50,14 @@ public struct Material : IResourceReleasable
public PipelineState options;
}
private Identifier<Shader> _shader;
private Handle<Shader> _shader;
private UnsafeArray<PipelineOverride> _passPipelineOverride;
private bool _isDirty;
internal CBufferCache _cBufferCache;
internal LocalKeywordSet _keywordMask;
public readonly Identifier<Shader> Shader => _shader;
public readonly Handle<Shader> Shader => _shader;
public readonly bool IsDirty => _isDirty;
public int ActivePassIndex
@@ -71,7 +71,7 @@ public struct Material : IResourceReleasable
get; set;
}
public Error SetShader(Identifier<Shader> shaderId, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
public Error SetShader(Handle<Shader> shaderId, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
{
if (!shaderId.IsValid)
{

View File

@@ -179,7 +179,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the mesh data buffer on the GPU.
/// </summary>
public Handle<GPUBuffer> ObjectDataBuffer
public Handle<GPUBuffer> MeshDataBuffer
{
get; internal set;
}
@@ -364,7 +364,7 @@ public struct Mesh : IResourceReleasable
database.ReleaseResource(MeshLetBuffer.AsResource());
database.ReleaseResource(MeshletVerticesBuffer.AsResource());
database.ReleaseResource(MeshletTrianglesBuffer.AsResource());
database.ReleaseResource(ObjectDataBuffer.AsResource());
database.ReleaseResource(MeshDataBuffer.AsResource());
}
}

View File

@@ -10,22 +10,21 @@ namespace Ghost.Graphics.Core;
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
public readonly unsafe ref struct RenderContext
{
private readonly IGraphicsEngine _engine;
private readonly ResourceManager _resourceManager;
private readonly IGraphicsEngine _engine;
private readonly ICommandBuffer _cmd;
public ICommandBuffer CommandBuffer => _cmd;
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
public ResourceManager ResourceManager => _resourceManager;
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
internal RenderContext(IGraphicsEngine engine, ResourceManager resourceManager, ICommandBuffer cmd)
internal RenderContext(ResourceManager resourceManager, IGraphicsEngine engine, ICommandBuffer cmd)
{
_engine = engine;
_resourceManager = resourceManager;
_engine = engine;
_cmd = cmd;
}
@@ -242,10 +241,10 @@ public readonly unsafe ref struct RenderContext
meshletTrianglesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
};
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
var bufferHandle = meshData.MeshDataBuffer.AsResource();
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshData.ObjectDataBuffer, data);
UploadBuffer(meshData.MeshDataBuffer, data);
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
}

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.RenderPipeline;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
@@ -179,22 +178,11 @@ public struct RenderView
public RenderingLayerMask renderingLayerMask;
}
public struct RenderRequest: IDisposable
public struct RenderRequest
{
public RenderView view;
public int swapChainIndex;
public Handle<GPUTexture> colorTarget;
public Handle<GPUTexture> depthTarget;
public RenderList opaqueRenderList;
public RenderList transparentRenderList;
public RenderList shadowCasterRenderList;
public void Dispose()
{
opaqueRenderList.Dispose();
transparentRenderList.Dispose();
shadowCasterRenderList.Dispose();
}
}

View File

@@ -87,8 +87,10 @@ public partial struct Shader : IResourceReleasable
public readonly int PassCount => _shaderPasses.Count;
public readonly uint PropertyBufferSize => _propertyBufferSize;
internal Shader(GraphicsShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
internal Shader(GraphicsShaderDescriptor descriptor, ReadOnlySpan<GraphicsCompiledResult> compiledResults)
{
Debug.Assert(descriptor.passes.Length == compiledResults.Length);
_propertyBufferSize = descriptor.propertyBufferSize;
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
@@ -98,7 +100,7 @@ public partial struct Shader : IResourceReleasable
{
ref readonly var pass = ref descriptor.passes[i];
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResult.HashCode);
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResults[i].HashCode);
var keywords = default(LocalKeywordSet);
if (pass.keywords.Length > 0)
@@ -141,7 +143,7 @@ public partial struct Shader : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly int GetLocalKeywordIndex(int globalKeywordID)
internal int GetLocalKeywordIndex(int globalKeywordID)
{
if (_keywordIDToLocal.TryGetValue(globalKeywordID, out var localIndex))
{
@@ -152,7 +154,7 @@ public partial struct Shader : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetPassIndex(Identifier<ShaderPass> passID)
public int GetPassIndex(Identifier<ShaderPass> passID)
{
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
{
@@ -163,7 +165,7 @@ public partial struct Shader : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetPassIndex(string passName)
public int GetPassIndex(string passName)
{
if (_passIDToLocal.TryGetValue(GetPassID(passName), out var index))
{
@@ -174,13 +176,13 @@ public partial struct Shader : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref ShaderPass GetPassReference(int index)
public ref ShaderPass GetPassReference(int index)
{
return ref _shaderPasses[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Result<ShaderPass, Error> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
public Result<ShaderPass, Error> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
{
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
{
@@ -200,25 +202,52 @@ public partial struct Shader : IResourceReleasable
}
}
public unsafe partial struct ComputeShader
public unsafe partial struct ComputeShader : IResourceReleasable
{
private fixed ulong _entryHash[8];
private readonly int _entryPointCount;
private readonly uint _propertyBufferSize;
private LocalKeywordSet _localKeywordSet;
private UnsafeHashMap<int, int> _keywordIDToLocal;
public readonly uint PropertyBufferSize => _propertyBufferSize;
internal ComputeShader(ComputeShaderDescriptor descriptor, ReadOnlySpan<ShaderCompileResult> compiledResults)
{
Debug.Assert(descriptor.entryPoints.Length == compiledResults.Length);
Debug.Assert(descriptor.shaderCodes.Length == compiledResults.Length);
_propertyBufferSize = descriptor.propertyBufferSize;
_entryPointCount = descriptor.entryPoints.Length;
for (var i = 0; i < descriptor.entryPoints.Length; i++)
_entryPointCount = descriptor.shaderCodes.Length;
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
{
_entryHash[i] = Hash.Combine64(descriptor.identifier, compiledResults[i].hashCode);
}
var localKeywordIndex = 0;
for (var i = 0; i < descriptor.keywords.Length; i++)
{
var group = descriptor.keywords[i];
if (group.keywords == null)
{
continue;
}
if (group.space == KeywordSpace.Local)
{
foreach (var kw in group.keywords)
{
var kwID = Shader.GetKeywordID(kw);
var idx = localKeywordIndex++;
_localKeywordSet.SetKeyword(idx, true);
_keywordIDToLocal.TryAdd(kwID, idx);
}
}
}
}
public ulong GetEntryHash(int entryPointIndex)
@@ -226,4 +255,20 @@ public unsafe partial struct ComputeShader
Debug.Assert(entryPointIndex >= 0 && entryPointIndex < _entryPointCount);
return _entryHash[entryPointIndex];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int GetLocalKeywordIndex(int globalKeywordID)
{
if (_keywordIDToLocal.TryGetValue(globalKeywordID, out var localIndex))
{
return localIndex;
}
return -1;
}
public void ReleaseResource(IResourceDatabase database)
{
_keywordIDToLocal.Dispose();
}
}

View File

@@ -71,6 +71,11 @@ public sealed class RenderGraph : IDisposable
_blackboard = new RenderGraphBlackboard();
}
public RenderGraph(ResourceManager resourceManager, IGraphicsEngine graphicsEngine)
: this(resourceManager, graphicsEngine.ResourceAllocator, graphicsEngine.ResourceDatabase, graphicsEngine.PipelineLibrary, graphicsEngine.ShaderCompiler)
{
}
/// <summary>
/// Resets the render graph for a new frame.
/// </summary>

View File

@@ -36,6 +36,7 @@ public interface IRasterRenderContext : IRenderGraphContext
public interface IComputeRenderContext : IRenderGraphContext
{
void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex);
void DispatchCompute(uint3 threadGroupCount);
}
@@ -141,8 +142,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var r = _resourceManager.GetMaterialReference(material);
if (r.IsFailure)
{
_activePerMaterialData = Handle<GPUBuffer>.Invalid;
return;
throw new InvalidOperationException($"Failed to get material reference for material handle {material}.");
}
ref readonly var mat = ref r.Value;
@@ -158,8 +158,8 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
return;
}
ref readonly var shader = ref shaderResult.Value;
ref readonly var pass = ref shader.GetPassReference(material.ActivePassIndex);
ref var shader = ref shaderResult.Value;
ref var pass = ref shader.GetPassReference(material.ActivePassIndex);
var passPipelineHash = new PassPipelineHash(_rtvFormats, _dsvFormat);
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
@@ -169,9 +169,9 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash);
if (!_pipelineLibrary.HasPipeline(pipelineKey))
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
{
var compiledCacheResult = _shaderCompiler.LoadCompiledCache(shaderVariantKey);
var compiledCacheResult = _shaderCompiler.GetCompiledCache(shaderVariantKey);
if (compiledCacheResult.IsFailure)
{
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
@@ -199,9 +199,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
_activePerMeshData = Handle<GPUBuffer>.Invalid;
_activeMeshIndexCount = 0;
return;
throw new InvalidOperationException($"Failed to get mesh reference for mesh handle {mesh}.");
}
ref readonly var meshRef = ref r.Value;
@@ -210,7 +208,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
public void SetActiveMesh(ref readonly Mesh mesh)
{
_activePerMeshData = mesh.ObjectDataBuffer;
_activePerMeshData = mesh.MeshDataBuffer;
_activeMeshIndexCount = mesh.IndexCount;
}
@@ -239,12 +237,41 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
_commandBuffer.DispatchMesh(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
}
public void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex)
{
var r = _resourceManager.GetComputeShaderReference(computeShader);
if (r.IsFailure)
{
throw new InvalidOperationException($"Failed to get compute shader reference for handle {computeShader}.");
}
ref var shader = ref r.Value;
var entryHash = shader.GetEntryHash((int)entryIndex);
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
var pipelineKey = RHIUtility.CreateComputePipelineKey(variantKey);
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
{
var compiledCacheResult = _shaderCompiler.GetCompiledCache(variantKey);
if (compiledCacheResult.IsFailure)
{
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
}
var psoDes = new ComputePSODescriptor
{
VariantKey = variantKey,
};
var compiled = compiledCacheResult.Value;
_pipelineLibrary.CreateComputePipeline(in psoDes, in compiled).GetValueOrThrow();
}
}
public void DispatchCompute(uint3 threadGroupCount)
{
throw new NotImplementedException();
}
public ICommandBuffer GetCommandBufferUnsafe()
{
return _commandBuffer;

View File

@@ -24,10 +24,9 @@ public interface IRenderPipeline : IDisposable
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
}
public static class RenderPipelineUtility
{
public static bool GetViewAndProjectionMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
public static bool GetVPMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
{
Handle<GPUTexture> rtHandle;
if (request.swapChainIndex < 0)

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.Mathematics;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -280,7 +281,7 @@ public class RenderSystem : IDisposable
{
cmd.Begin(frameResource.CommandAllocator);
var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
var ctx = new RenderContext(_resourceManager, _graphicsEngine, cmd);
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
_swapChainManager.TransitionToPresent(cmd);

View File

@@ -5,7 +5,6 @@ using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
namespace Ghost.Graphics;
@@ -29,11 +28,13 @@ public sealed partial class ResourceManager : IDisposable
private UnsafeSlotMap<Mesh> _meshes;
private UnsafeSlotMap<Material> _materials;
private UnsafeList<Shader> _shaders; // TODO: Use SlotMap?
private UnsafeSlotMap<Shader> _shaders;
private UnsafeSlotMap<ComputeShader> _computeShaders;
private readonly MaterialPaletteStore _materialPalettes;
private ulong _submittedFrame;
private int _writeLock;
private bool _disposed;
@@ -45,7 +46,8 @@ public sealed partial class ResourceManager : IDisposable
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
_shaders = new UnsafeSlotMap<Shader>(16, Allocator.Persistent);
_computeShaders = new UnsafeSlotMap<ComputeShader>(16, Allocator.Persistent);
_materialPalettes = new MaterialPaletteStore();
}
@@ -67,6 +69,18 @@ public sealed partial class ResourceManager : IDisposable
EndFramePool(completedFrame);
}
public void EnterParallelRead()
{
Debug.Assert(!_disposed);
Volatile.Write(ref _writeLock, 1);
}
public void ExitParallelRead()
{
Debug.Assert(!_disposed);
Volatile.Write(ref _writeLock, 0);
}
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
@@ -77,80 +91,139 @@ public sealed partial class ResourceManager : IDisposable
{
Debug.Assert(!_disposed);
var vertexBufferDesc = new BufferDesc
var spinner = new SpinWait();
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
spinner.SpinOnce();
}
var indexBufferDesc = new BufferDesc
try
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
var vertexBufferDesc = new BufferDesc
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
var objectBufferDesc = new BufferDesc
var indexBufferDesc = new BufferDesc
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
var objectBufferDesc = new BufferDesc
{
Size = (uint)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, "VertexBuffer");
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, "IndexBuffer");
var objectBuffer = _resourceAllocator.CreateBuffer(in objectBufferDesc, "ObjectBuffer");
var mesh = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshDataBuffer = objectBuffer,
};
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
finally
{
Size = (uint)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, "VertexBuffer");
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, "IndexBuffer");
var objectBuffer = _resourceAllocator.CreateBuffer(in objectBufferDesc, "ObjectBuffer");
var mesh = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
ObjectDataBuffer = objectBuffer,
};
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
Volatile.Write(ref _writeLock, 0);
}
}
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
/// <returns>An <see cref="Handle{Material}"/> representing the newly created material.</returns>
public Handle<Material> CreateMaterial(Handle<Shader> shader)
{
Debug.Assert(!_disposed);
var material = new Material();
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
var spinner = new SpinWait();
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
{
return Handle<Material>.Invalid;
spinner.SpinOnce();
}
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
try
{
var material = new Material();
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
{
return Handle<Material>.Invalid;
}
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}
finally
{
Volatile.Write(ref _writeLock, 0);
}
}
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <returns>An <see cref="Handle{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
public Identifier<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor, ReadOnlySpan<GraphicsCompiledResult> compiledResults)
{
Debug.Assert(!_disposed);
var shader = new Shader(descriptor, in compiledResult);
var spinner = new SpinWait();
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
{
spinner.SpinOnce();
}
var id = _shaders.Count;
_shaders.Add(shader);
return new Identifier<Shader>(id);
try
{
var shader = new Shader(descriptor, compiledResults);
var id = _shaders.Add(shader, out var generation);
return new Handle<Shader>(id, generation);
}
finally
{
Volatile.Write(ref _writeLock, 0);
}
}
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor, ReadOnlySpan<ShaderCompileResult> compiledResults)
{
Debug.Assert(!_disposed);
var spinner = new SpinWait();
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
{
spinner.SpinOnce();
}
try
{
var computeShader = new ComputeShader(descriptor, compiledResults);
var id = _computeShaders.Add(computeShader, out var generation);
return new Handle<ComputeShader>(id, generation);
}
finally
{
Volatile.Write(ref _writeLock, 0);
}
}
/// <summary>
@@ -309,44 +382,91 @@ public sealed partial class ResourceManager : IDisposable
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
public bool HasShader(Identifier<Shader> id)
public bool HasShader(Handle<Shader> id)
{
Debug.Assert(!_disposed);
return id.Value >= 0 && id.Value < _shaders.Count;
return _shaders.Contains(id.ID, id.Generation);
}
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <param name="handle">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
public RefResult<Shader, Error> GetShaderReference(Handle<Shader> handle)
{
if (!HasShader(id))
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
return RefResult<Shader, Error>.Success(ref shader);
}
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
public void ReleaseShader(Identifier<Shader> id)
/// <param name="handle">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
public void ReleaseShader(Handle<Shader> handle)
{
Debug.Assert(!_disposed);
if (!HasShader(id))
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
ref var shader = ref _shaders[id.Value];
_shaders.Remove(handle.ID, handle.Generation);
shader.ReleaseResource(_resourceDatabase);
}
/// <summary>
/// Determines whether a compute shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the compute shader to check for existence.</param>
/// <returns>true if a compute shader with the specified identifier exists; otherwise, false.</returns>
public bool HasComputeShader(Handle<ComputeShader> id)
{
Debug.Assert(!_disposed);
return _computeShaders.Contains(id.ID, id.Generation);
}
/// <summary>
/// Returns a reference to the compute shader associated with the specified identifier.
/// </summary>
/// <param name="handle">The identifier of the compute shader to retrieve. Must refer to a valid ComputeShader.</param>
/// <returns>A result containing a reference to the compute shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
public RefResult<ComputeShader, Error> GetComputeShaderReference(Handle<ComputeShader> handle)
{
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<ComputeShader, Error>.Success(ref computeShader);
}
/// <summary>
/// Releases the compute shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="handle">The identifier of the compute shader to release. Must refer to a valid, previously created ComputeShader.</param>
public void ReleaseComputeShader(Handle<ComputeShader> handle)
{
Debug.Assert(!_disposed);
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
_computeShaders.Remove(handle.ID, handle.Generation);
computeShader.ReleaseResource(_resourceDatabase);
}
public void Dispose()
{
if (_disposed)

View File

@@ -1,7 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics;
namespace Ghost.Graphics.Services;
public class ResourceUploadBatch
{

View File

@@ -0,0 +1,11 @@
namespace Ghost.Graphics.Services;
public class ShaderLibrary : IDisposable
{
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@@ -1,7 +1,7 @@
using Ghost.Graphics.RHI;
using System.Diagnostics.CodeAnalysis;
namespace Ghost.Graphics;
namespace Ghost.Graphics.Services;
internal sealed class SwapChainRecord
{

View File

@@ -3,6 +3,12 @@
#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl"
#define GLOBAL_BINDLESS_SIG \
"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | " \
"CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED | " \
"SAMPLER_HEAP_DIRECTLY_INDEXED), " \
"RootConstants(num32BitConstants=3, b0, space=0)"
// TODO: This should be auto generated to match the c# side.
struct GraphicsPushConstantData
@@ -59,6 +65,9 @@ struct MeshData
GraphicsPushConstantData g_PushConstantData : register(b0);
#elif defined(__COMPUTE__)
ComputePushConstantData g_PushConstantData : register(b0);
#elif defined(__WORK_GRAPH__)
#define WorkGraphPushConstantData ComputePushConstantData
WorkGraphPushConstantData g_PushConstantData : register(b0);
#endif
#endif // GHOST_PROPERTIES_HLSL