- 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.
325 lines
11 KiB
C#
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>()
|
|
};
|
|
}
|
|
}
|