Files
GhostEngine/src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
Misaki 1a91811621 Refactor asset pipeline to use file paths, improve import
- Switched asset handler interfaces and implementations to use file paths instead of FileStreams for all operations.
- Refactored mesh asset structure and parsing, moved meshlet logic to MeshProcessor, and introduced hierarchical MeshNode types.
- Updated texture asset handling: switched to bits-per-channel, improved mipmap/cubemap generation, and SPMD HDRI support.
- Updated shader asset handlers to use file paths and split code compilation logic.
- Improved asset registry: added event debouncing, better path handling, and import time/hash tracking.
- Added source generator for IAssetSettings registration to support polymorphic JSON serialization.
- Updated dependencies and tests; various minor fixes and cleanups.
2026-04-25 18:23:21 +09:00

325 lines
11 KiB
C#

using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderParser;
using Misaki.HighPerformance.Utilities;
using System.Text;
namespace Ghost.DSL.ShaderCompiler;
public struct DSLShaderError
{
public string message;
public int line;
public int column;
public override readonly string ToString()
{
return $"Error at {line}:{column} - {message}";
}
}
public static class DSLShaderCompiler
{
private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
{
if (semantic == null)
{
return parent;
}
return new PipelineState
{
ZTest = semantic.zTest ?? parent.ZTest,
ZWrite = semantic.zWrite ?? parent.ZWrite,
Cull = semantic.cull ?? parent.Cull,
Blend = semantic.blend ?? parent.Blend,
ColorMask = semantic.colorMask ?? parent.ColorMask
};
}
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
{
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
};
for (var i = 0; i < descriptor.Passes.Length; i++)
{
descriptor.Passes[i].shader = descriptor;
}
return descriptor;
}
public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(Stream stream)
{
using var reader = new StreamReader(stream);
return CompileGraphicsShaderCode(reader.ReadToEnd());
}
public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(string shaderPath)
{
if (!File.Exists(shaderPath))
{
return Result.Failure("Shader file not found: " + shaderPath);
}
var code = File.ReadAllText(shaderPath);
return CompileGraphicsShaderCode(code);
}
public static Result<GraphicsShaderDescriptor> CompileGraphicsShaderCode(string shaderCode)
{
try
{
// Use ANTLR4 parser
var parseErrors = new List<DSLShaderError>();
var shaderModels = AntlrShaderCompiler.ParseShaders(shaderCode, parseErrors);
if (parseErrors.Count != 0)
{
var errorMessages = new StringBuilder();
foreach (var error in parseErrors)
{
errorMessages.AppendLine(error.ToString());
}
return Result.Failure("Failed to parse shader due to errors:\n" + errorMessages.ToString());
}
if (shaderModels.Count == 0)
{
return Result.Failure("No shader found in the provided file.");
}
// Convert to semantics
var model = AntlrShaderCompiler.ConvertToSemantics(shaderModels[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.Failure("Failed to compile shader due to errors:\n" + errorMessages.ToString());
}
var result = ResolveShader(model);
if (result.IsFailure)
{
return result;
}
return result.Value;
}
catch (Exception ex)
{
return Result.Failure("Failed to compile shader: " + ex.Message);
}
}
public static Result<ComputeShaderDescriptor> CompileComputeShader(Stream stream)
{
using var reader = new StreamReader(stream);
return CompileComputeShaderCode(reader.ReadToEnd());
}
public static Result<ComputeShaderDescriptor> CompileComputeShader(string shaderPath)
{
if (!File.Exists(shaderPath))
{
return Result.Failure("Shader file not found: " + shaderPath);
}
var code = File.ReadAllText(shaderPath);
return CompileComputeShaderCode(code);
}
public static Result<ComputeShaderDescriptor> CompileComputeShaderCode(string shaderCode)
{
try
{
var parseErrors = new List<DSLShaderError>();
var shaderModels = AntlrShaderCompiler.ParseComputeShaders(shaderCode, parseErrors);
if (parseErrors.Count != 0)
{
var errorMessages = new StringBuilder();
foreach (var error in parseErrors)
{
errorMessages.AppendLine(error.ToString());
}
return Result.Failure("Failed to parse compute shader due to errors:\n" + errorMessages.ToString());
}
if (shaderModels.Count == 0)
{
return Result.Failure("No compute shader found in the provided file.");
}
var model = AntlrShaderCompiler.ConvertToComputeSemantics(shaderModels[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.Failure("Failed to compile compute shader due to errors:\n" + errorMessages.ToString());
}
var result = ResolveComputeShader(model);
if (result.IsFailure)
{
return result;
}
return result.Value;
}
catch (Exception ex)
{
return Result.Failure("Failed to compile compute shader: " + ex.Message);
}
}
public static Result<ComputeShaderDescriptor> ResolveComputeShader(DSLComputeShaderSemantics semantics)
{
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
{
propertyInfo = default;
}
var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
for (var 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
{
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>()
};
}
}