- Switched image loading/saving from Magick.NET to native stb_image (Ghost.StbI), removing Magick.NET dependency. - Added Ghost.StbI project with native DLL, P/Invoke bindings, and wrapper. - Refactored TextureAssetHandler and TextureProcessor for stb_image, memory-mapped IO, and HDR/16-bit support. - Split IAssetHandler into IImportableAssetHandler and IPackableAssetHandler; updated interfaces to use FileStream. - Added shader and mesh asset handlers (GraphicsShaderAssetHandler, ComputeShaderAssetHandler, FBXAssetHandler). - Improved asset registry/catalog path handling and naming consistency. - Updated asset import pipeline to use new interfaces and trigger engine reimport. - Enhanced UI toolbar button styles and EditPage layout. - Added StbIBindingTest, DisableRuntimeMarshalling, and native wrapper attributes. - Updated wrapper generator for regex derivesFrom; added stbi.json config. - Removed Magick.NET reference; added Ghost.StbI and Ghost.Ufbx references. - Miscellaneous bugfixes and code cleanup.
419 lines
13 KiB
C#
419 lines
13 KiB
C#
using Antlr4.Runtime;
|
|
using Ghost.Core.Graphics;
|
|
using Ghost.DSL.ShaderCompiler;
|
|
using Ghost.DSL.ShaderParser.Model;
|
|
|
|
namespace Ghost.DSL.ShaderParser;
|
|
|
|
public class AntlrShaderCompiler
|
|
{
|
|
public static List<GraphicsShaderModel> ParseShaders(string source, List<DSLShaderError> errors)
|
|
{
|
|
try
|
|
{
|
|
var inputStream = new AntlrInputStream(source);
|
|
var lexer = new GhostShaderLexer(inputStream);
|
|
|
|
// Capture lexer errors
|
|
lexer.RemoveErrorListeners();
|
|
var lexerErrorListener = new ErrorListener(errors);
|
|
lexer.AddErrorListener(lexerErrorListener);
|
|
|
|
var tokenStream = new CommonTokenStream(lexer);
|
|
var parser = new GhostShaderParser(tokenStream);
|
|
|
|
// Capture parser errors
|
|
parser.RemoveErrorListeners();
|
|
var parserErrorListener = new ErrorListener(errors);
|
|
parser.AddErrorListener(parserErrorListener);
|
|
|
|
var tree = parser.shaderFile();
|
|
|
|
if (errors.Count > 0)
|
|
{
|
|
return new List<GraphicsShaderModel>();
|
|
}
|
|
|
|
var visitor = new ShaderVisitor();
|
|
visitor.Visit(tree);
|
|
|
|
return visitor.Shaders;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Unexpected error during parsing: {ex.Message}",
|
|
line = -1,
|
|
column = -1
|
|
});
|
|
return new List<GraphicsShaderModel>();
|
|
}
|
|
}
|
|
|
|
public static List<ComputeShaderModel> ParseComputeShaders(string source, List<DSLShaderError> errors)
|
|
{
|
|
errors = new List<DSLShaderError>();
|
|
|
|
try
|
|
{
|
|
var inputStream = new AntlrInputStream(source);
|
|
var lexer = new GhostShaderLexer(inputStream);
|
|
|
|
// Capture lexer errors
|
|
lexer.RemoveErrorListeners();
|
|
var lexerErrorListener = new ErrorListener(errors);
|
|
lexer.AddErrorListener(lexerErrorListener);
|
|
|
|
var tokenStream = new CommonTokenStream(lexer);
|
|
var parser = new GhostComputeShaderParser(tokenStream);
|
|
|
|
// Capture parser errors
|
|
parser.RemoveErrorListeners();
|
|
var parserErrorListener = new ErrorListener(errors);
|
|
parser.AddErrorListener(parserErrorListener);
|
|
|
|
var tree = parser.computeFile();
|
|
|
|
if (errors.Count > 0)
|
|
{
|
|
return new List<ComputeShaderModel>();
|
|
}
|
|
|
|
var visitor = new ComputeShaderVisitor();
|
|
visitor.Visit(tree);
|
|
|
|
return visitor.ComputeShaders;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Unexpected error during parsing: {ex.Message}",
|
|
line = -1,
|
|
column = -1
|
|
});
|
|
return new List<ComputeShaderModel>();
|
|
}
|
|
}
|
|
|
|
private static bool TryGetShaderModel(string model, List<DSLShaderError> errors, out ShaderModel shaderModel)
|
|
{
|
|
if (string.IsNullOrEmpty(model))
|
|
{
|
|
shaderModel = ShaderModel.SM_6_6; // Default to lowest supported shader model for compute shaders
|
|
}
|
|
else
|
|
{
|
|
switch (model)
|
|
{
|
|
case "6_6":
|
|
shaderModel = ShaderModel.SM_6_6;
|
|
break;
|
|
case "6_7":
|
|
shaderModel = ShaderModel.SM_6_7;
|
|
break;
|
|
case "6_8":
|
|
shaderModel = ShaderModel.SM_6_8;
|
|
break;
|
|
default:
|
|
shaderModel = default;
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Unknown shader model '{model}'.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static DSLComputeShaderSemantics? ConvertToComputeSemantics(ComputeShaderModel model, out List<DSLShaderError> errors)
|
|
{
|
|
errors = new List<DSLShaderError>();
|
|
|
|
if (string.IsNullOrWhiteSpace(model.Name))
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = "Compute shader name cannot be empty.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
return null;
|
|
}
|
|
|
|
var semantics = new DSLComputeShaderSemantics
|
|
{
|
|
name = model.Name,
|
|
defines = model.Defines?.Defines,
|
|
includes = model.Includes?.Includes,
|
|
hlsl = model.Hlsl?.Code
|
|
};
|
|
|
|
if (TryGetShaderModel(model.ShaderModel, errors, out var shaderModel))
|
|
{
|
|
semantics.shaderModel = shaderModel;
|
|
}
|
|
|
|
if (model.Keywords != null)
|
|
{
|
|
semantics.keywords = new List<KeywordsGroup>();
|
|
foreach (var group in model.Keywords.Groups)
|
|
{
|
|
var keywordGroup = new KeywordsGroup
|
|
{
|
|
space = group.Scope?.ToLower() == "global" ? KeywordSpace.Global : KeywordSpace.Local,
|
|
keywords = group.Keywords
|
|
};
|
|
semantics.keywords.Add(keywordGroup);
|
|
}
|
|
}
|
|
|
|
foreach (var entry in model.ShaderEntries)
|
|
{
|
|
var entryType = entry.EntryType.ToLower();
|
|
if (entryType == "cs")
|
|
{
|
|
semantics.entryPoints ??= new List<ShaderEntryPoint>();
|
|
semantics.entryPoints.Add(new ShaderEntryPoint
|
|
{
|
|
shaderPath = entry.ShaderPath,
|
|
entry = entry.EntryPoint
|
|
});
|
|
}
|
|
else
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Unknown compute shader entry type '{entry.EntryType}'. Expected 'compute' or 'cs'.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
}
|
|
}
|
|
|
|
if (semantics.entryPoints == null)
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Compute shader '{model.Name}' must contain a compute/cs entry declaration.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
}
|
|
|
|
if (semantics.entryPoints != null && semantics.entryPoints.Count > 8)
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Compute shader '{model.Name}' cannot have more than 8 entry points.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
}
|
|
|
|
return semantics;
|
|
}
|
|
|
|
private static PipelineSemantic? ConvertPipeline(PipelineBlockModel? pipeline, List<DSLShaderError> errors)
|
|
{
|
|
if (pipeline == null || pipeline.Statements.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var semantic = new PipelineSemantic();
|
|
|
|
foreach (var (key, value) in pipeline.Statements)
|
|
{
|
|
switch (key.ToLower())
|
|
{
|
|
case "ztest":
|
|
semantic.zTest = value.ToLower() switch
|
|
{
|
|
"disabled" => ZTest.Disabled,
|
|
"less" => ZTest.Less,
|
|
"less_equal" => ZTest.LessEqual,
|
|
"equal" => ZTest.Equal,
|
|
"greater_equal" => ZTest.GreaterEqual,
|
|
"greater" => ZTest.Greater,
|
|
"not_equal" => ZTest.NotEqual,
|
|
"always" => ZTest.Always,
|
|
_ => ZTest.Disabled
|
|
};
|
|
break;
|
|
case "zwrite":
|
|
semantic.zWrite = value.ToLower() == "on" ? ZWrite.On : ZWrite.Off;
|
|
break;
|
|
case "cull":
|
|
semantic.cull = value.ToLower() switch
|
|
{
|
|
"off" => Cull.Off,
|
|
"front" => Cull.Front,
|
|
"back" => Cull.Back,
|
|
_ => Cull.Off
|
|
};
|
|
break;
|
|
case "blend":
|
|
semantic.blend = value.ToLower() switch
|
|
{
|
|
"opaque" => Blend.Opaque,
|
|
"alpha" => Blend.Alpha,
|
|
"additive" => Blend.Additive,
|
|
"multiply" => Blend.Multiply,
|
|
"premultiplied_alpha" => Blend.PremultipliedAlpha,
|
|
_ => Blend.Opaque
|
|
};
|
|
break;
|
|
case "color_mask":
|
|
semantic.colorMask = value.ToLower() == "all" ? ColorWriteMask.All : ColorWriteMask.None;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return semantic;
|
|
}
|
|
|
|
private static PassSemantic? ConvertPass(PassBlockModel pass, List<DSLShaderError> errors)
|
|
{
|
|
var semantic = new PassSemantic
|
|
{
|
|
name = pass.Name,
|
|
hlsl = pass.Hlsl?.Code,
|
|
defines = pass.Defines?.Defines,
|
|
includes = pass.Includes?.Includes,
|
|
localPipeline = ConvertPipeline(pass.LocalPipeline, errors)
|
|
};
|
|
|
|
if (pass.Keywords != null)
|
|
{
|
|
semantic.keywords = new List<KeywordsGroup>();
|
|
foreach (var group in pass.Keywords.Groups)
|
|
{
|
|
var keywordGroup = new KeywordsGroup
|
|
{
|
|
space = group.Scope?.ToLower() == "global" ? KeywordSpace.Global : KeywordSpace.Local,
|
|
keywords = group.Keywords
|
|
};
|
|
semantic.keywords.Add(keywordGroup);
|
|
}
|
|
}
|
|
|
|
foreach (var entry in pass.ShaderEntries)
|
|
{
|
|
var entryType = entry.EntryType.ToLower();
|
|
var shaderEntry = new ShaderEntryPoint
|
|
{
|
|
shaderPath = entry.ShaderPath,
|
|
entry = entry.EntryPoint
|
|
};
|
|
|
|
switch (entryType)
|
|
{
|
|
case "ms":
|
|
semantic.meshShader = shaderEntry;
|
|
break;
|
|
case "ps":
|
|
semantic.pixelShader = shaderEntry;
|
|
break;
|
|
case "as":
|
|
semantic.amplificationShader = shaderEntry;
|
|
break;
|
|
default:
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Unknown shader entry type '{entry.EntryType}'.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (semantic.meshShader.shaderPath == null || semantic.pixelShader.shaderPath == null)
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = $"Pass '{pass.Name}' must contain a mesh/ms shader and a pixel/ps shader declaration.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
}
|
|
|
|
return semantic;
|
|
}
|
|
|
|
public static DSLShaderSemantics? ConvertToSemantics(GraphicsShaderModel model, out List<DSLShaderError> errors)
|
|
{
|
|
errors = new List<DSLShaderError>();
|
|
|
|
if (string.IsNullOrWhiteSpace(model.Name))
|
|
{
|
|
errors.Add(new DSLShaderError
|
|
{
|
|
message = "Shader name cannot be empty.",
|
|
line = 0,
|
|
column = 0
|
|
});
|
|
return null;
|
|
}
|
|
|
|
var semantics = new DSLShaderSemantics
|
|
{
|
|
name = model.Name,
|
|
pipeline = ConvertPipeline(model.Pipeline, errors)
|
|
};
|
|
|
|
if (TryGetShaderModel(model.ShaderModel, errors, out var shaderModel))
|
|
{
|
|
semantics.shaderModel = shaderModel;
|
|
}
|
|
|
|
foreach (var pass in model.Passes)
|
|
{
|
|
var passSemantic = ConvertPass(pass, errors);
|
|
if (passSemantic != null)
|
|
{
|
|
semantics.passes ??= new List<PassSemantic>();
|
|
semantics.passes.Add(passSemantic);
|
|
}
|
|
}
|
|
|
|
return semantics;
|
|
}
|
|
|
|
private class ErrorListener : BaseErrorListener, IAntlrErrorListener<int>, IAntlrErrorListener<IToken>
|
|
{
|
|
private readonly List<DSLShaderError> _errors;
|
|
|
|
public ErrorListener(List<DSLShaderError> errors)
|
|
{
|
|
_errors = errors;
|
|
}
|
|
|
|
public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
|
|
{
|
|
_errors.Add(new DSLShaderError
|
|
{
|
|
message = msg,
|
|
line = line,
|
|
column = charPositionInLine
|
|
});
|
|
}
|
|
|
|
public new void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
|
|
{
|
|
_errors.Add(new DSLShaderError
|
|
{
|
|
message = msg,
|
|
line = line,
|
|
column = charPositionInLine
|
|
});
|
|
}
|
|
}
|
|
}
|