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

@@ -1,3 +1,4 @@
using Ghost.Core.Graphics;
using Misaki.HighPerformance.Mathematics;
using System.Numerics;
using System.Reflection;
@@ -7,25 +8,6 @@ using System.Text.RegularExpressions;
namespace Ghost.DSL.Generator;
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;
}
}
internal static partial class ShaderStructGenerator
{
private struct ShaderFieldInfo

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderParser;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.InteropServices;
using System.Text;
@@ -43,51 +44,121 @@ internal static class DSLShaderCompiler
};
}
private static Result<string> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode, string? properties)
{
string shaderCode;
if (shaderPath == "hlsl_block")
{
if (string.IsNullOrEmpty(injectedCode))
{
return Result.Failure("Shader code is empty. Either provide a valid shader path or inject shader code directly.");
}
shaderCode = string.Empty;
}
else
{
if (!File.Exists(shaderPath))
{
return Result.Failure("Shader file not found: " + shaderPath);
}
shaderCode = File.ReadAllText(shaderPath);
}
var sb = new StringBuilder();
foreach (var includePath in includes)
{
sb.AppendLine($"#include \"{includePath}\"");
}
if (!string.IsNullOrEmpty(properties))
{
sb.AppendLine($"#line 0 \"properties\"");
sb.AppendLine(properties);
}
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();
}
// TODO: Implement shader inheritance resolution, including property and pass merging.
// Currently, we just ignore inheritance.
public static Result<GraphicsShaderDescriptor> ResolveShader(DSLShaderSemantics semantics)
{
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
{
propertyInfo = default;
}
var passes = semantics.passes == null ? Array.Empty<PassDescriptor>() : new PassDescriptor[semantics.passes.Count];
for (var i = 0; i < passes.Length; i++)
{
var pass = semantics.passes![i];
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
var result = BuildFinalShaderCode(pass.amplificationShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
if (result.IsFailure)
{
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
}
var amplificationShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.amplificationShader.entry };
result = BuildFinalShaderCode(pass.meshShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
if (result.IsFailure)
{
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
}
var meshShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.meshShader.entry };
result = BuildFinalShaderCode(pass.pixelShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
if (result.IsFailure)
{
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
}
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
passes[i] = new PassDescriptor
{
identifier = GetPassUniqueId(semantics, pass),
name = pass.name,
amplificationShaderCode = amplificationShaderCode,
meshShaderCode = meshShaderCode,
pixelShaderCode = pixelShaderCode,
localPipeline = localPipeline,
defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
};
}
var descriptor = new GraphicsShaderDescriptor
{
name = semantics.name,
propertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel,
passes = passes
};
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var info))
for (int i = 0; i < descriptor.passes.Length; i++)
{
info = default;
}
descriptor.propertiesCode = info.code ?? string.Empty;
descriptor.propertyBufferSize = info.size;
descriptor.shaderModel = semantics.shaderModel;
if (semantics.passes != null)
{
descriptor.passes = new PassDescriptor[semantics.passes.Count];
for (var i = 0; i < semantics.passes.Count; i++)
{
var pass = semantics.passes[i];
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
descriptor.passes[i] = new PassDescriptor
{
shader = descriptor,
identifier = GetPassUniqueId(semantics, pass),
name = pass.name,
taskShader = pass.taskShader,
meshShader = pass.meshShader,
pixelShader = pass.pixelShader,
localPipeline = localPipeline,
defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
includes = pass.includes?.ToArray() ?? Array.Empty<string>(),
keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>(),
hlsl = pass.hlsl
};
}
}
else
{
descriptor.passes = Array.Empty<PassDescriptor>();
descriptor.passes[i].shader = descriptor;
}
return descriptor;
@@ -199,28 +270,32 @@ internal static class DSLShaderCompiler
public static Result<ComputeShaderDescriptor> ResolveComputeShader(DSLComputeShaderSemantics semantics)
{
var descriptor = new ComputeShaderDescriptor
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
{
propertyInfo = default;
}
var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
for (int i = 0; i < shaderCodes.Length; i++)
{
var result = BuildFinalShaderCode(semantics.entryPoints[i].shaderPath, semantics.includes.AsSpan(), semantics.hlsl, propertyInfo.code);
if (result.IsFailure)
{
return Result.Failure($"Failed to build shader code for entry point '{semantics.entryPoints[i].entry}': {result.Message}");
}
shaderCodes[i] = new ShaderCode { code = result.Value, entryPoint = semantics.entryPoints[i].entry };
}
return new ComputeShaderDescriptor
{
identifier = XxHash64.HashToUInt64(MemoryMarshal.AsBytes(semantics.name.AsSpan())),
name = semantics.name,
propertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel,
shaderCodes = shaderCodes,
defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
};
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var info))
{
info = default;
}
descriptor.propertiesCode = info.code ?? string.Empty;
descriptor.propertyBufferSize = info.size;
descriptor.shaderModel = semantics.shaderModel;
descriptor.hlsl = semantics.hlsl;
descriptor.defines = semantics.defines?.ToArray() ?? Array.Empty<string>();
descriptor.includes = semantics.includes?.ToArray() ?? Array.Empty<string>();
descriptor.keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>();
descriptor.entryPoints = semantics.entryPoints?.ToArray() ?? Array.Empty<ShaderEntryPoint>();
return descriptor;
}
}

View File

@@ -8,6 +8,14 @@ public enum PropertyScope
Local,
}
public struct ShaderEntryPoint
{
public string entry;
public string shaderPath;
public readonly bool IsCreated => !string.IsNullOrEmpty(entry) && !string.IsNullOrEmpty(shaderPath);
}
public class PipelineSemantic
{
public ZTest? zTest;
@@ -20,7 +28,7 @@ public class PipelineSemantic
public class PassSemantic
{
public string name = string.Empty;
public ShaderEntryPoint taskShader;
public ShaderEntryPoint amplificationShader;
public ShaderEntryPoint meshShader;
public ShaderEntryPoint pixelShader;
public string? hlsl;
@@ -46,5 +54,5 @@ public class DSLComputeShaderSemantics
public List<string>? defines;
public List<string>? includes;
public List<KeywordsGroup>? keywords;
public List<ShaderEntryPoint>? entryPoints;
public List<ShaderEntryPoint> entryPoints = null!;
}

View File

@@ -169,7 +169,7 @@ public class AntlrShaderCompiler
semantics.entryPoints ??= new List<ShaderEntryPoint>();
semantics.entryPoints.Add(new ShaderEntryPoint
{
shader = entry.ShaderPath,
shaderPath = entry.ShaderPath,
entry = entry.EntryPoint
});
}
@@ -355,7 +355,7 @@ public class AntlrShaderCompiler
var entryType = entry.EntryType.ToLower();
var shaderEntry = new ShaderEntryPoint
{
shader = entry.ShaderPath,
shaderPath = entry.ShaderPath,
entry = entry.EntryPoint
};
@@ -368,7 +368,7 @@ public class AntlrShaderCompiler
semantic.pixelShader = shaderEntry;
break;
case "as":
semantic.taskShader = shaderEntry;
semantic.amplificationShader = shaderEntry;
break;
default:
errors.Add(new DSLShaderError
@@ -381,7 +381,7 @@ public class AntlrShaderCompiler
}
}
if (semantic.meshShader.shader == null || semantic.pixelShader.shader == null)
if (semantic.meshShader.shaderPath == null || semantic.pixelShader.shaderPath == null)
{
errors.Add(new DSLShaderError
{

View File

@@ -0,0 +1,23 @@
namespace Ghost.DSL;
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);
}
}