Refactor folder structure
This commit is contained in:
4
src/Editor/Ghost.DSL/AssemblyInfo.cs
Normal file
4
src/Editor/Ghost.DSL/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Shader.Test")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Graphics")]
|
||||
305
src/Editor/Ghost.DSL/Generator/ShaderStructGenerator.cs
Normal file
305
src/Editor/Ghost.DSL/Generator/ShaderStructGenerator.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
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
|
||||
{
|
||||
public string name;
|
||||
public Type fieldType;
|
||||
|
||||
public ShaderFieldInfo(string name, Type fieldType)
|
||||
{
|
||||
this.name = name;
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
public ShaderFieldInfo(FieldInfo fieldInfo)
|
||||
: this(fieldInfo.Name, fieldInfo.FieldType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private const int _HLSL_VECTOR_REGISTER_SIZE = 16; // 16 bytes (128 bits) for float4
|
||||
|
||||
private static void GenerateEnumHLSL(Type type, StringBuilder sb)
|
||||
{
|
||||
if (!type.IsEnum)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} is not an enum.");
|
||||
}
|
||||
|
||||
var enumName = type.Name;
|
||||
//var underlyingType = Enum.GetUnderlyingType(space);
|
||||
//var underlyingTypeName = underlyingType switch
|
||||
//{
|
||||
// Type t when t == typeof(byte) || t == typeof(short) || t == typeof(int) => "int",
|
||||
// Type t when t == typeof(sbyte) || t == typeof(ushort) || t == typeof(uint) => "uint",
|
||||
// _ => throw new InvalidOperationException($"Unsupported underlying space {underlyingType.FullName} for enum {enumName}."),
|
||||
//};
|
||||
|
||||
// sb.Append(@$"
|
||||
//enum {enumName} : {underlyingTypeName}
|
||||
//{{");
|
||||
var names = Enum.GetNames(type);
|
||||
var values = Enum.GetValuesAsUnderlyingType(type);
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = $"{CamelCaseToUnderscoreRegex().Replace(enumName, "_$1")}_{names[i]}";
|
||||
var value = values.GetValue(i);
|
||||
// sb.Append(@$"
|
||||
//{name} = {Value},");
|
||||
sb.Append(@$"
|
||||
#define {name.ToUpperInvariant()} {value}"); // Use #define for capability. Enum is only support for newer HLSL versions.
|
||||
}
|
||||
// sb.AppendLine(@"
|
||||
//};");
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
public static int FindNextFieldThatFits(FieldInfo[] fields, bool[] looked, int startIndex, int size, out int foundIndex)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
foundIndex = -1;
|
||||
return size;
|
||||
}
|
||||
|
||||
var bestFitIndex = -1;
|
||||
var bestFitSize = 0;
|
||||
|
||||
for (var j = startIndex; j < fields.Length; j++)
|
||||
{
|
||||
if (looked[j])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var nextField = fields[j];
|
||||
var nextSize = Marshal.SizeOf(nextField.FieldType);
|
||||
if (nextSize <= size)
|
||||
{
|
||||
if (nextSize == size)
|
||||
{
|
||||
foundIndex = j;
|
||||
return nextSize;
|
||||
}
|
||||
|
||||
if (nextSize > bestFitSize)
|
||||
{
|
||||
bestFitSize = nextSize;
|
||||
bestFitIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFitIndex != -1)
|
||||
{
|
||||
foundIndex = bestFitIndex;
|
||||
return bestFitSize;
|
||||
}
|
||||
|
||||
foundIndex = -1;
|
||||
return size;
|
||||
}
|
||||
|
||||
private static void GenerateStructHLSL(Type type, PackingRules packingRules, StringBuilder sb)
|
||||
{
|
||||
if (!type.IsValueType || type.IsPrimitive)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} is not a struct.");
|
||||
}
|
||||
|
||||
var structName = type.Name;
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(static f => f.FieldType.IsValueType).ToArray();
|
||||
|
||||
var shaderFields = new ShaderFieldInfo[fields.Length];
|
||||
if (packingRules == PackingRules.Aligned)
|
||||
{
|
||||
var sortedFields = new List<ShaderFieldInfo>(fields.Length);
|
||||
var looked = new bool[fields.Length];
|
||||
var paddingIndex = 0;
|
||||
|
||||
// Sort the fields to align them to HLSL vector registers (16 bytes)
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
if (looked[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var field = fields[i];
|
||||
var size = Marshal.SizeOf(field.FieldType);
|
||||
|
||||
sortedFields.Add(new ShaderFieldInfo(field));
|
||||
|
||||
var registerRemaining = _HLSL_VECTOR_REGISTER_SIZE - (size % _HLSL_VECTOR_REGISTER_SIZE);
|
||||
while (true)
|
||||
{
|
||||
var nextSize = FindNextFieldThatFits(fields, looked, i + 1, registerRemaining, out var nextIndex);
|
||||
if (nextSize == 0 || nextIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
looked[i] = true;
|
||||
looked[nextIndex] = true;
|
||||
|
||||
sortedFields.Add(new ShaderFieldInfo(fields[nextIndex]));
|
||||
|
||||
registerRemaining -= nextSize;
|
||||
}
|
||||
|
||||
if (registerRemaining != 0)
|
||||
{
|
||||
// Add padding if necessary
|
||||
var count = registerRemaining / sizeof(float);
|
||||
for (var p = 0; p < count; p++)
|
||||
{
|
||||
sortedFields.Add(new ShaderFieldInfo($"_padding{paddingIndex++}", typeof(float)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shaderFields = sortedFields.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
shaderFields[i] = new ShaderFieldInfo(fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(@$"
|
||||
struct {structName}
|
||||
{{");
|
||||
foreach (var field in shaderFields)
|
||||
{
|
||||
var fieldType = field.fieldType;
|
||||
var fieldName = field.name;
|
||||
|
||||
string hlslType;
|
||||
switch (fieldType)
|
||||
{
|
||||
case Type t when t == typeof(float):
|
||||
hlslType = "float";
|
||||
break;
|
||||
case Type t when t == typeof(double):
|
||||
hlslType = "double";
|
||||
break;
|
||||
case Type t when t == typeof(int):
|
||||
hlslType = "int";
|
||||
break;
|
||||
case Type t when t == typeof(uint):
|
||||
hlslType = "uint";
|
||||
break;
|
||||
case Type t when t == typeof(bool):
|
||||
hlslType = "bool";
|
||||
break;
|
||||
case Type t when t == typeof(Vector2):
|
||||
hlslType = "float2";
|
||||
break;
|
||||
case Type t when t == typeof(Vector3):
|
||||
hlslType = "float3";
|
||||
break;
|
||||
case Type t when t == typeof(Vector4):
|
||||
hlslType = "float4";
|
||||
break;
|
||||
case Type t when t == typeof(Matrix4x4):
|
||||
hlslType = "float4x4";
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (fieldType.Namespace == typeof(float2).Namespace)
|
||||
{
|
||||
if (fieldType.Name.StartsWith("float")
|
||||
|| fieldType.Name.StartsWith("double")
|
||||
|| fieldType.Name.StartsWith("int")
|
||||
|| fieldType.Name.StartsWith("uint")
|
||||
|| fieldType.Name.StartsWith("bool"))
|
||||
{
|
||||
hlslType = fieldType.Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported field type: {fieldType.FullName} in struct {structName}.");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(@$"
|
||||
{hlslType} {fieldName};");
|
||||
}
|
||||
|
||||
sb.AppendLine(@"
|
||||
};");
|
||||
}
|
||||
|
||||
public static void GenerateHLSL(ReadOnlySpan<Type> types, PackingRules packingRules, string outputSource)
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(outputSource)))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"The directory for the output source '{outputSource}' does not exist.");
|
||||
}
|
||||
|
||||
var hlslDefine = $"{Path.GetFileNameWithoutExtension(outputSource).ToUpperInvariant().Replace('.', '_')}_HLSL";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(@$"// Auto-generated HLSL code, please do not edit this file directly.
|
||||
|
||||
#ifndef {hlslDefine}
|
||||
#define {hlslDefine}");
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
GenerateEnumHLSL(type, sb);
|
||||
}
|
||||
else if (type.IsValueType && !type.IsPrimitive)
|
||||
{
|
||||
GenerateStructHLSL(type, packingRules, sb);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sb.Append(@"
|
||||
#endif");
|
||||
|
||||
var hlslCode = sb.ToString();
|
||||
File.WriteAllText(outputSource, hlslCode);
|
||||
}
|
||||
|
||||
[GeneratedRegex("(?<=[a-z])([A-Z])")]
|
||||
private static partial Regex CamelCaseToUnderscoreRegex();
|
||||
}
|
||||
31
src/Editor/Ghost.DSL/Ghost.DSL.csproj
Normal file
31
src/Editor/Ghost.DSL/Ghost.DSL.csproj
Normal file
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.13.1" />
|
||||
<PackageReference Include="Antlr4BuildTasks" Version="12.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Antlr4 Include="Grammar\GhostShaderLexer.g4">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<Listener>false</Listener>
|
||||
<Visitor>true</Visitor>
|
||||
</Antlr4>
|
||||
<Antlr4 Include="Grammar\GhostShaderParser.g4">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<Listener>false</Listener>
|
||||
<Visitor>true</Visitor>
|
||||
</Antlr4>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Runtime/Ghost.Core/Ghost.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
38
src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4
Normal file
38
src/Editor/Ghost.DSL/Grammar/GhostShaderLexer.g4
Normal file
@@ -0,0 +1,38 @@
|
||||
lexer grammar GhostShaderLexer;
|
||||
|
||||
// Keywords
|
||||
SHADER: 'shader';
|
||||
PROPERTIES: 'properties';
|
||||
PIPELINE: 'pipeline';
|
||||
PASS: 'pass';
|
||||
DEFINES: 'defines';
|
||||
KEYWORDS: 'keywords';
|
||||
INCLUDES: 'includes';
|
||||
GLOBAL: 'global';
|
||||
LOCAL: 'local';
|
||||
HLSL: 'hlsl';
|
||||
|
||||
// Punctuation
|
||||
LBRACE: '{';
|
||||
RBRACE: '}';
|
||||
LPAREN: '(';
|
||||
RPAREN: ')';
|
||||
LBRACK: '[';
|
||||
RBRACK: ']';
|
||||
SEMICOLON: ';';
|
||||
COMMA: ',';
|
||||
EQUALS: '=';
|
||||
COLON: ':';
|
||||
|
||||
// Literals
|
||||
STRING_LITERAL: '"' (~["\r\n] | '\\' .)* '"';
|
||||
NUMBER: [0-9]+ ('.' [0-9]+)? | '.' [0-9]+;
|
||||
IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
|
||||
|
||||
// Whitespace and Comments
|
||||
WS: [ \t\r\n]+ -> skip;
|
||||
LINE_COMMENT: '//' ~[\r\n]* -> skip;
|
||||
BLOCK_COMMENT: '/*' .*? '*/' -> skip;
|
||||
|
||||
|
||||
ANY_CHAR: . ;
|
||||
99
src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4
Normal file
99
src/Editor/Ghost.DSL/Grammar/GhostShaderParser.g4
Normal file
@@ -0,0 +1,99 @@
|
||||
parser grammar GhostShaderParser;
|
||||
|
||||
options {
|
||||
tokenVocab = GhostShaderLexer;
|
||||
}
|
||||
|
||||
// Top-level rule
|
||||
shaderFile: shader+ EOF;
|
||||
|
||||
shader:
|
||||
SHADER STRING_LITERAL LBRACE
|
||||
shaderBody
|
||||
RBRACE;
|
||||
|
||||
shaderBody:
|
||||
(propertiesBlock | pipelineBlock | passBlock | functionCall)*;
|
||||
|
||||
// Properties block
|
||||
propertiesBlock:
|
||||
PROPERTIES LBRACE
|
||||
propertyDeclaration*
|
||||
RBRACE;
|
||||
|
||||
propertyDeclaration:
|
||||
scope? IDENTIFIER IDENTIFIER (EQUALS LBRACE propertyInitializer RBRACE)? SEMICOLON;
|
||||
|
||||
scope:
|
||||
GLOBAL | LOCAL;
|
||||
|
||||
propertyInitializer:
|
||||
(NUMBER | IDENTIFIER) (COMMA (NUMBER | IDENTIFIER))*;
|
||||
|
||||
// Pipeline block
|
||||
pipelineBlock:
|
||||
PIPELINE LBRACE
|
||||
pipelineStatement*
|
||||
RBRACE;
|
||||
|
||||
pipelineStatement:
|
||||
IDENTIFIER EQUALS IDENTIFIER SEMICOLON;
|
||||
|
||||
// Pass block
|
||||
passBlock:
|
||||
PASS STRING_LITERAL LBRACE
|
||||
passBody
|
||||
RBRACE;
|
||||
|
||||
// Template
|
||||
passBody:
|
||||
(definesBlock | includesBlock | keywordsBlock | pipelineBlock | hlslBlock | shaderEntry)*;
|
||||
|
||||
definesBlock:
|
||||
DEFINES LBRACE
|
||||
defineStatement*
|
||||
RBRACE;
|
||||
|
||||
defineStatement:
|
||||
IDENTIFIER SEMICOLON;
|
||||
|
||||
includesBlock:
|
||||
INCLUDES LBRACE
|
||||
includeStatement*
|
||||
RBRACE;
|
||||
|
||||
includeStatement:
|
||||
STRING_LITERAL SEMICOLON;
|
||||
|
||||
keywordsBlock:
|
||||
KEYWORDS LBRACE
|
||||
keywordStatement*
|
||||
RBRACE;
|
||||
|
||||
keywordStatement:
|
||||
scope? IDENTIFIER (COMMA IDENTIFIER)* SEMICOLON;
|
||||
|
||||
hlslBlock:
|
||||
HLSL LBRACE
|
||||
hlslBody
|
||||
RBRACE;
|
||||
|
||||
// Recursively matches content, ensuring braces are balanced.
|
||||
hlslBody:
|
||||
(
|
||||
~(LBRACE | RBRACE) // Match ANY token except open/close braces
|
||||
|
|
||||
LBRACE hlslBody RBRACE // Or match a nested block recursively
|
||||
)*;
|
||||
|
||||
shaderEntry:
|
||||
IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON;
|
||||
|
||||
functionCall:
|
||||
IDENTIFIER LPAREN functionArguments? RPAREN SEMICOLON;
|
||||
|
||||
functionArguments:
|
||||
functionArgument (COMMA functionArgument)*;
|
||||
|
||||
functionArgument:
|
||||
STRING_LITERAL | NUMBER | IDENTIFIER;
|
||||
327
src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
Normal file
327
src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.DSL.ShaderParser;
|
||||
using System.Runtime.CompilerServices;
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DSLShaderCompiler
|
||||
{
|
||||
private const string _GLOBAL_PROPERTY_FILE_NAME = "GlobalData.g.hlsl";
|
||||
private const string _GENERATED_FILE_HEADER = "// Auto-generated shader file. Please do not edit this file directly.";
|
||||
|
||||
private static string GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass)
|
||||
{
|
||||
return $"{shader.name}_{pass.name}";
|
||||
}
|
||||
|
||||
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 int LayoutCBufferProperties(Span<PropertyDescriptor> properties)
|
||||
{
|
||||
if (properties.IsEmpty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var currentOffset = 0;
|
||||
|
||||
foreach (ref var prop in properties)
|
||||
{
|
||||
var size = prop.type.GetSize();
|
||||
|
||||
if ((currentOffset % 16) + size > 16)
|
||||
{
|
||||
currentOffset = (currentOffset + 15) & ~15;
|
||||
}
|
||||
|
||||
prop.offset = currentOffset;
|
||||
prop.size = size;
|
||||
|
||||
currentOffset += size;
|
||||
}
|
||||
|
||||
return (currentOffset + 15) & ~15;
|
||||
}
|
||||
|
||||
// TODO: Implement shader inheritance resolution, including property and pass merging.
|
||||
// Currently, we just ignore inheritance.
|
||||
public static ShaderDescriptor ResolveShader(DSLShaderSemantics semantics)
|
||||
{
|
||||
var descriptor = new ShaderDescriptor
|
||||
{
|
||||
name = semantics.name,
|
||||
hlsl = semantics.hlsl
|
||||
};
|
||||
|
||||
var shaderGlobalProperties = semantics.properties?
|
||||
.Where(p => p.scope == PropertyScope.Global)
|
||||
.Select(p => new PropertyDescriptor
|
||||
{
|
||||
name = p.name,
|
||||
type = p.type,
|
||||
defaultValue = p.defaultValue
|
||||
}).ToArray();
|
||||
|
||||
var shaderLocalProperties = semantics.properties?
|
||||
.Where(p => p.scope == PropertyScope.Local)
|
||||
.Select(p => new PropertyDescriptor
|
||||
{
|
||||
name = p.name,
|
||||
type = p.type,
|
||||
defaultValue = p.defaultValue
|
||||
}).ToArray();
|
||||
|
||||
descriptor.globalProperties = shaderGlobalProperties ?? Array.Empty<PropertyDescriptor>();
|
||||
descriptor.properties = shaderLocalProperties ?? Array.Empty<PropertyDescriptor>();
|
||||
descriptor.cbufferSize = LayoutCBufferProperties(descriptor.properties);
|
||||
|
||||
if (semantics.passes != null)
|
||||
{
|
||||
descriptor.passes = new PassDescriptor[semantics.passes.Count];
|
||||
for (int i = 0; i < semantics.passes.Count; i++)
|
||||
{
|
||||
var pass = semantics.passes[i];
|
||||
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
||||
descriptor.passes[i] = new PassDescriptor
|
||||
{
|
||||
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>();
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static Result<ShaderDescriptor> CompileShader(string shaderPath, string generatedOutputDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
var source = File.ReadAllText(shaderPath);
|
||||
|
||||
// Use ANTLR4 parser
|
||||
var shaderModels = AntlrShaderCompiler.ParseShaders(source, out var 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 desc = ResolveShader(model);
|
||||
var globalPropResult = GenerateGlobalProperties(desc.globalProperties, generatedOutputDirectory);
|
||||
if (globalPropResult.IsFailure)
|
||||
{
|
||||
return Result.Failure("Failed to generate global properties: " + globalPropResult.Message);
|
||||
}
|
||||
|
||||
var generatedResult = GenerateShaderCode(desc, generatedOutputDirectory);
|
||||
if (generatedResult.IsFailure)
|
||||
{
|
||||
return Result.Failure("Failed to generate pass files: " + generatedResult.Message);
|
||||
}
|
||||
|
||||
foreach (ref var pass in desc.passes.AsSpan())
|
||||
{
|
||||
if (pass.includes == null)
|
||||
{
|
||||
pass.includes = new string[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Resize(ref pass.includes, pass.includes.Length + 2);
|
||||
// Shift existing includes to make room for the two new includes at the front.
|
||||
pass.includes.AsSpan(0, pass.includes.Length - 2).CopyTo(pass.includes.AsSpan(2));
|
||||
}
|
||||
|
||||
pass.includes[0] = globalPropResult.Value;
|
||||
pass.includes[1] = generatedResult.Value;
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure("Failed to compile shader: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ShaderPropertyTypeToHLSLType(ShaderPropertyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ShaderPropertyType.Float => "float",
|
||||
ShaderPropertyType.Float2 => "float2",
|
||||
ShaderPropertyType.Float3 => "float3",
|
||||
ShaderPropertyType.Float4 => "float4",
|
||||
ShaderPropertyType.Int => "int",
|
||||
ShaderPropertyType.Int2 => "int2",
|
||||
ShaderPropertyType.Int3 => "int3",
|
||||
ShaderPropertyType.Int4 => "int4",
|
||||
ShaderPropertyType.UInt => "uint",
|
||||
ShaderPropertyType.UInt2 => "uint2",
|
||||
ShaderPropertyType.UInt3 => "uint3",
|
||||
ShaderPropertyType.UInt4 => "uint4",
|
||||
ShaderPropertyType.Bool => "bool",
|
||||
ShaderPropertyType.Bool2 => "bool2",
|
||||
ShaderPropertyType.Bool3 => "bool3",
|
||||
ShaderPropertyType.Bool4 => "bool4",
|
||||
// NOTE: Textures here are bindless, represented as uint (descriptor index).
|
||||
ShaderPropertyType.Texture2D => "TEXTURE2D",
|
||||
ShaderPropertyType.Texture3D => "TEXTURE3D",
|
||||
ShaderPropertyType.TextureCube => "TEXTURECUBE",
|
||||
ShaderPropertyType.Texture2DArray => "TEXTURE2D_ARRAY",
|
||||
ShaderPropertyType.TextureCubeArray => "TEXTURECUBE_ARRAY",
|
||||
ShaderPropertyType.Sampler => "SAMPLER",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unsupported shader property type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
public static Result<string> GenerateShaderCode(ShaderDescriptor descriptor, string targetDirectory)
|
||||
{
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
return Result.Failure("Target directory does not exist.");
|
||||
}
|
||||
|
||||
var outputFileName = descriptor.name.Replace('/', '_');
|
||||
var outputFilePath = Path.Combine(targetDirectory, outputFileName + ".g.hlsl");
|
||||
var outputDirectory = Path.GetDirectoryName(outputFilePath);
|
||||
|
||||
if (!Directory.Exists(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory!);
|
||||
}
|
||||
|
||||
using var fileStream = File.CreateText(outputFilePath);
|
||||
var fileDefine = outputFileName.Replace('/', '_').ToUpperInvariant() + "_G_HLSL";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(_GENERATED_FILE_HEADER);
|
||||
sb.AppendLine(@$"
|
||||
#ifndef {fileDefine}
|
||||
#define {fileDefine}
|
||||
|
||||
#include ""F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl""");
|
||||
|
||||
sb.Append(@"
|
||||
struct PerMaterialData
|
||||
{");
|
||||
foreach (var prop in descriptor.properties)
|
||||
{
|
||||
sb.Append($@"
|
||||
{ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};");
|
||||
}
|
||||
sb.Append(@"
|
||||
};");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(@$"
|
||||
#endif // {fileDefine}");
|
||||
|
||||
fileStream.Write(sb.ToString());
|
||||
|
||||
return outputFilePath;
|
||||
}
|
||||
|
||||
public static Result<string> GenerateGlobalProperties(ReadOnlySpan<PropertyDescriptor> globalProperties, string targetDirectory)
|
||||
{
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
return Result.Failure("Target directory does not exist.");
|
||||
}
|
||||
|
||||
var globalFilePath = Path.Combine(targetDirectory, _GLOBAL_PROPERTY_FILE_NAME);
|
||||
using var globalFileStream = File.CreateText(globalFilePath);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(_GENERATED_FILE_HEADER);
|
||||
sb.Append(@"
|
||||
#ifndef GLOBALDATA_G_HLSL
|
||||
#define GLOBALDATA_G_HLSL
|
||||
|
||||
#include ""F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl""
|
||||
|
||||
struct GlobalData
|
||||
{");
|
||||
foreach (var prop in globalProperties)
|
||||
{
|
||||
sb.Append($@"
|
||||
{ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};");
|
||||
}
|
||||
sb.AppendLine(@"
|
||||
};
|
||||
|
||||
#endif // GLOBALDATA_G_HLSL");
|
||||
globalFileStream.Write(sb.ToString());
|
||||
|
||||
return globalFilePath;
|
||||
}
|
||||
}
|
||||
48
src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs
Normal file
48
src/Editor/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Ghost.Core.Graphics;
|
||||
|
||||
namespace Ghost.DSL.ShaderCompiler;
|
||||
|
||||
public enum PropertyScope
|
||||
{
|
||||
Global,
|
||||
Local,
|
||||
}
|
||||
|
||||
public class PropertySemantic
|
||||
{
|
||||
public PropertyScope scope;
|
||||
public ShaderPropertyType type;
|
||||
public string name = string.Empty;
|
||||
public object? defaultValue;
|
||||
}
|
||||
|
||||
public class PipelineSemantic
|
||||
{
|
||||
public ZTest? zTest;
|
||||
public ZWrite? zWrite;
|
||||
public Cull? cull;
|
||||
public Blend? blend;
|
||||
public ColorWriteMask? colorMask;
|
||||
}
|
||||
|
||||
public class PassSemantic
|
||||
{
|
||||
public string name = string.Empty;
|
||||
public ShaderEntryPoint taskShader;
|
||||
public ShaderEntryPoint meshShader;
|
||||
public ShaderEntryPoint pixelShader;
|
||||
public string? hlsl;
|
||||
public List<string>? defines;
|
||||
public List<string>? includes;
|
||||
public List<KeywordsGroup>? keywords;
|
||||
public PipelineSemantic? localPipeline;
|
||||
}
|
||||
|
||||
public class DSLShaderSemantics
|
||||
{
|
||||
public string name = string.Empty;
|
||||
public string? hlsl;
|
||||
public List<PropertySemantic>? properties;
|
||||
public PipelineSemantic? pipeline;
|
||||
public List<PassSemantic>? passes;
|
||||
}
|
||||
383
src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs
Normal file
383
src/Editor/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
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<ShaderModel> ParseShaders(string source, out 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 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<ShaderModel>();
|
||||
}
|
||||
|
||||
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<ShaderModel>();
|
||||
}
|
||||
}
|
||||
|
||||
public static DSLShaderSemantics? ConvertToSemantics(ShaderModel 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,
|
||||
properties = ConvertProperties(model.Properties, errors),
|
||||
pipeline = ConvertPipeline(model.Pipeline, errors)
|
||||
};
|
||||
|
||||
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 static List<PropertySemantic>? ConvertProperties(PropertiesBlockModel? properties, List<DSLShaderError> errors)
|
||||
{
|
||||
if (properties == null || properties.Properties.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new List<PropertySemantic>();
|
||||
var usedNames = new HashSet<string>();
|
||||
|
||||
foreach (var prop in properties.Properties)
|
||||
{
|
||||
if (usedNames.Contains(prop.Name))
|
||||
{
|
||||
errors.Add(new DSLShaderError
|
||||
{
|
||||
message = $"Duplicate property name '{prop.Name}'.",
|
||||
line = 0,
|
||||
column = 0
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var semantic = new PropertySemantic
|
||||
{
|
||||
name = prop.Name,
|
||||
scope = prop.Scope?.ToLower() == "global" ? PropertyScope.Global : PropertyScope.Local,
|
||||
type = ParsePropertyType(prop.Type, errors)
|
||||
};
|
||||
|
||||
if (prop.Initializer.Count > 0)
|
||||
{
|
||||
semantic.defaultValue = ParsePropertyValue(semantic.type, prop.Initializer, errors);
|
||||
}
|
||||
|
||||
usedNames.Add(prop.Name);
|
||||
result.Add(semantic);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ShaderPropertyType ParsePropertyType(string type, List<DSLShaderError> errors)
|
||||
{
|
||||
return type.ToLower() switch
|
||||
{
|
||||
"float" => ShaderPropertyType.Float,
|
||||
"float2" => ShaderPropertyType.Float2,
|
||||
"float3" => ShaderPropertyType.Float3,
|
||||
"float4" => ShaderPropertyType.Float4,
|
||||
"float4x4" => ShaderPropertyType.Float4x4,
|
||||
"int" => ShaderPropertyType.Int,
|
||||
"int2" => ShaderPropertyType.Int2,
|
||||
"int3" => ShaderPropertyType.Int3,
|
||||
"int4" => ShaderPropertyType.Int4,
|
||||
"uint" => ShaderPropertyType.UInt,
|
||||
"uint2" => ShaderPropertyType.UInt2,
|
||||
"uint3" => ShaderPropertyType.UInt3,
|
||||
"uint4" => ShaderPropertyType.UInt4,
|
||||
"bool" => ShaderPropertyType.Bool,
|
||||
"bool2" => ShaderPropertyType.Bool2,
|
||||
"bool3" => ShaderPropertyType.Bool3,
|
||||
"bool4" => ShaderPropertyType.Bool4,
|
||||
"tex2d" => ShaderPropertyType.Texture2D,
|
||||
"tex3d" => ShaderPropertyType.Texture3D,
|
||||
"texcube" => ShaderPropertyType.TextureCube,
|
||||
"texcube_arr" => ShaderPropertyType.TextureCubeArray,
|
||||
"tex2d_arr" => ShaderPropertyType.Texture2DArray,
|
||||
"sampler" => ShaderPropertyType.Sampler,
|
||||
_ => ShaderPropertyType.None
|
||||
};
|
||||
}
|
||||
|
||||
private static object? ParsePropertyValue(ShaderPropertyType type, List<string> values, List<DSLShaderError> errors)
|
||||
{
|
||||
// For textures, the value is an identifier (e.g., "white", "black")
|
||||
if (type is ShaderPropertyType.Texture2D or ShaderPropertyType.Texture3D or ShaderPropertyType.TextureCube)
|
||||
{
|
||||
return values.Count > 0 ? values[0] : null;
|
||||
}
|
||||
|
||||
// For samplers, no default value
|
||||
if (type == ShaderPropertyType.Sampler)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// For numeric types, parse the values
|
||||
try
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ShaderPropertyType.Float => values.Count > 0 ? float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0f,
|
||||
ShaderPropertyType.Float2 => values.Count >= 2 ? new Misaki.HighPerformance.Mathematics.float2(
|
||||
float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.Float3 => values.Count >= 3 ? new Misaki.HighPerformance.Mathematics.float3(
|
||||
float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.Float4 => values.Count >= 4 ? new Misaki.HighPerformance.Mathematics.float4(
|
||||
float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(values[3], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.Int => values.Count > 0 ? int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0,
|
||||
ShaderPropertyType.Int2 => values.Count >= 2 ? new Misaki.HighPerformance.Mathematics.int2(
|
||||
int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.Int3 => values.Count >= 3 ? new Misaki.HighPerformance.Mathematics.int3(
|
||||
int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.Int4 => values.Count >= 4 ? new Misaki.HighPerformance.Mathematics.int4(
|
||||
int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture),
|
||||
int.Parse(values[3], System.Globalization.CultureInfo.InvariantCulture)) : default,
|
||||
ShaderPropertyType.UInt => values.Count > 0 ? uint.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0u,
|
||||
ShaderPropertyType.Bool => values.Count > 0 && (values[0] == "1" || values[0].ToLower() == "true"),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add(new DSLShaderError
|
||||
{
|
||||
message = $"Failed to parse property value: {ex.Message}",
|
||||
line = 0,
|
||||
column = 0
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
"lessequal" => ZTest.LessEqual,
|
||||
"equal" => ZTest.Equal,
|
||||
"greaterequal" => ZTest.GreaterEqual,
|
||||
"greater" => ZTest.Greater,
|
||||
"notequal" => 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,
|
||||
"premultipliedalpha" => 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
|
||||
{
|
||||
shader = entry.ShaderPath,
|
||||
entry = entry.EntryPoint
|
||||
};
|
||||
|
||||
switch (entryType)
|
||||
{
|
||||
case "mesh" or "ms":
|
||||
semantic.meshShader = shaderEntry;
|
||||
break;
|
||||
case "pixel" or "ps":
|
||||
semantic.pixelShader = shaderEntry;
|
||||
break;
|
||||
case "task" or "ts":
|
||||
semantic.taskShader = shaderEntry;
|
||||
break;
|
||||
default:
|
||||
errors.Add(new DSLShaderError
|
||||
{
|
||||
message = $"Unknown shader entry type '{entry.EntryType}'.",
|
||||
line = 0,
|
||||
column = 0
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (semantic.meshShader.shader == null || semantic.pixelShader.shader == 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;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/Editor/Ghost.DSL/ShaderParser/Model/ShaderModel.cs
Normal file
78
src/Editor/Ghost.DSL/ShaderParser/Model/ShaderModel.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace Ghost.DSL.ShaderParser.Model;
|
||||
|
||||
public class ShaderModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public PropertiesBlockModel? Properties { get; set; }
|
||||
public PipelineBlockModel? Pipeline { get; set; }
|
||||
public List<PassBlockModel> Passes { get; set; } = new();
|
||||
public List<FunctionCallModel> FunctionCalls { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PropertiesBlockModel
|
||||
{
|
||||
public List<PropertyDeclarationModel> Properties { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PropertyDeclarationModel
|
||||
{
|
||||
public string? Scope { get; set; }
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<string> Initializer { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PipelineBlockModel
|
||||
{
|
||||
public Dictionary<string, string> Statements { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PassBlockModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public PipelineBlockModel? LocalPipeline { get; set; }
|
||||
public DefinesBlockModel? Defines { get; set; }
|
||||
public IncludesBlockModel? Includes { get; set; }
|
||||
public KeywordsBlockModel? Keywords { get; set; }
|
||||
public HlslBlockModel? Hlsl { get; set; }
|
||||
public List<ShaderEntryModel> ShaderEntries { get; set; } = new();
|
||||
}
|
||||
|
||||
public class DefinesBlockModel
|
||||
{
|
||||
public List<string> Defines { get; set; } = new();
|
||||
}
|
||||
|
||||
public class IncludesBlockModel
|
||||
{
|
||||
public List<string> Includes { get; set; } = new();
|
||||
}
|
||||
|
||||
public class KeywordsBlockModel
|
||||
{
|
||||
public List<KeywordGroupModel> Groups { get; set; } = new();
|
||||
}
|
||||
|
||||
public class KeywordGroupModel
|
||||
{
|
||||
public string? Scope { get; set; }
|
||||
public List<string> Keywords { get; set; } = new();
|
||||
}
|
||||
|
||||
public class HlslBlockModel
|
||||
{
|
||||
public string Code { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ShaderEntryModel
|
||||
{
|
||||
public string EntryType { get; set; } = string.Empty; // "mesh", "pixel", "task", etc.
|
||||
public string ShaderPath { get; set; } = string.Empty;
|
||||
public string EntryPoint { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class FunctionCallModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<string> Arguments { get; set; } = new();
|
||||
}
|
||||
261
src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs
Normal file
261
src/Editor/Ghost.DSL/ShaderParser/ShaderVisitor.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using Antlr4.Runtime.Misc;
|
||||
using Ghost.DSL.ShaderParser.Model;
|
||||
|
||||
namespace Ghost.DSL.ShaderParser;
|
||||
|
||||
public class ShaderVisitor : GhostShaderParserBaseVisitor<object>
|
||||
{
|
||||
public List<ShaderModel> Shaders { get; } = new();
|
||||
|
||||
public override object VisitShaderFile([NotNull] GhostShaderParser.ShaderFileContext context)
|
||||
{
|
||||
foreach (var shaderContext in context.shader())
|
||||
{
|
||||
var shader = (ShaderModel)VisitShader(shaderContext);
|
||||
Shaders.Add(shader);
|
||||
}
|
||||
return Shaders;
|
||||
}
|
||||
|
||||
public override object VisitShader([NotNull] GhostShaderParser.ShaderContext context)
|
||||
{
|
||||
var shader = new ShaderModel
|
||||
{
|
||||
Name = StripQuotes(context.STRING_LITERAL().GetText())
|
||||
};
|
||||
|
||||
var shaderBody = context.shaderBody();
|
||||
if (shaderBody != null)
|
||||
{
|
||||
foreach (var propBlock in shaderBody.propertiesBlock())
|
||||
{
|
||||
shader.Properties = (PropertiesBlockModel)VisitPropertiesBlock(propBlock);
|
||||
}
|
||||
|
||||
foreach (var pipelineBlock in shaderBody.pipelineBlock())
|
||||
{
|
||||
shader.Pipeline = (PipelineBlockModel)VisitPipelineBlock(pipelineBlock);
|
||||
}
|
||||
|
||||
foreach (var passBlock in shaderBody.passBlock())
|
||||
{
|
||||
shader.Passes.Add((PassBlockModel)VisitPassBlock(passBlock));
|
||||
}
|
||||
|
||||
foreach (var funcCall in shaderBody.functionCall())
|
||||
{
|
||||
shader.FunctionCalls.Add((FunctionCallModel)VisitFunctionCall(funcCall));
|
||||
}
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
public override object VisitPropertiesBlock([NotNull] GhostShaderParser.PropertiesBlockContext context)
|
||||
{
|
||||
var properties = new PropertiesBlockModel();
|
||||
|
||||
foreach (var propDecl in context.propertyDeclaration())
|
||||
{
|
||||
properties.Properties.Add((PropertyDeclarationModel)VisitPropertyDeclaration(propDecl));
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public override object VisitPropertyDeclaration([NotNull] GhostShaderParser.PropertyDeclarationContext context)
|
||||
{
|
||||
var property = new PropertyDeclarationModel
|
||||
{
|
||||
Type = context.IDENTIFIER(0).GetText(),
|
||||
Name = context.IDENTIFIER(1).GetText()
|
||||
};
|
||||
|
||||
if (context.scope() != null)
|
||||
{
|
||||
property.Scope = context.scope().GetText();
|
||||
}
|
||||
|
||||
if (context.propertyInitializer() != null)
|
||||
{
|
||||
var init = context.propertyInitializer();
|
||||
foreach (var number in init.NUMBER())
|
||||
{
|
||||
property.Initializer.Add(number.GetText());
|
||||
}
|
||||
foreach (var identifier in init.IDENTIFIER())
|
||||
{
|
||||
property.Initializer.Add(identifier.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
public override object VisitPipelineBlock([NotNull] GhostShaderParser.PipelineBlockContext context)
|
||||
{
|
||||
var pipeline = new PipelineBlockModel();
|
||||
|
||||
foreach (var statement in context.pipelineStatement())
|
||||
{
|
||||
var key = statement.IDENTIFIER(0).GetText();
|
||||
var value = statement.IDENTIFIER(1).GetText();
|
||||
pipeline.Statements[key] = value;
|
||||
}
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
public override object VisitPassBlock([NotNull] GhostShaderParser.PassBlockContext context)
|
||||
{
|
||||
var pass = new PassBlockModel
|
||||
{
|
||||
Name = StripQuotes(context.STRING_LITERAL().GetText())
|
||||
};
|
||||
|
||||
var passBody = context.passBody();
|
||||
if (passBody != null)
|
||||
{
|
||||
foreach (var definesBlock in passBody.definesBlock())
|
||||
{
|
||||
pass.Defines = (DefinesBlockModel)VisitDefinesBlock(definesBlock);
|
||||
}
|
||||
|
||||
foreach (var includesBlock in passBody.includesBlock())
|
||||
{
|
||||
pass.Includes = (IncludesBlockModel)VisitIncludesBlock(includesBlock);
|
||||
}
|
||||
|
||||
foreach (var keywordsBlock in passBody.keywordsBlock())
|
||||
{
|
||||
pass.Keywords = (KeywordsBlockModel)VisitKeywordsBlock(keywordsBlock);
|
||||
}
|
||||
|
||||
foreach (var pipelineBlock in passBody.pipelineBlock())
|
||||
{
|
||||
pass.LocalPipeline = (PipelineBlockModel)VisitPipelineBlock(pipelineBlock);
|
||||
}
|
||||
|
||||
foreach (var hlslBlock in passBody.hlslBlock())
|
||||
{
|
||||
pass.Hlsl = (HlslBlockModel)VisitHlslBlock(hlslBlock);
|
||||
}
|
||||
|
||||
foreach (var shaderEntry in passBody.shaderEntry())
|
||||
{
|
||||
pass.ShaderEntries.Add((ShaderEntryModel)VisitShaderEntry(shaderEntry));
|
||||
}
|
||||
}
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
public override object VisitDefinesBlock([NotNull] GhostShaderParser.DefinesBlockContext context)
|
||||
{
|
||||
var defines = new DefinesBlockModel();
|
||||
|
||||
foreach (var defineStmt in context.defineStatement())
|
||||
{
|
||||
defines.Defines.Add(defineStmt.IDENTIFIER().GetText());
|
||||
}
|
||||
|
||||
return defines;
|
||||
}
|
||||
|
||||
public override object VisitIncludesBlock([NotNull] GhostShaderParser.IncludesBlockContext context)
|
||||
{
|
||||
var includes = new IncludesBlockModel();
|
||||
|
||||
foreach (var includeStmt in context.includeStatement())
|
||||
{
|
||||
includes.Includes.Add(StripQuotes(includeStmt.STRING_LITERAL().GetText()));
|
||||
}
|
||||
|
||||
return includes;
|
||||
}
|
||||
|
||||
public override object VisitKeywordsBlock([NotNull] GhostShaderParser.KeywordsBlockContext context)
|
||||
{
|
||||
var keywords = new KeywordsBlockModel();
|
||||
|
||||
foreach (var keywordStmt in context.keywordStatement())
|
||||
{
|
||||
var group = new KeywordGroupModel();
|
||||
|
||||
if (keywordStmt.scope() != null)
|
||||
{
|
||||
group.Scope = keywordStmt.scope().GetText();
|
||||
}
|
||||
|
||||
foreach (var identifier in keywordStmt.IDENTIFIER())
|
||||
{
|
||||
group.Keywords.Add(identifier.GetText());
|
||||
}
|
||||
|
||||
keywords.Groups.Add(group);
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
public override object VisitHlslBlock([NotNull] GhostShaderParser.HlslBlockContext context)
|
||||
{
|
||||
var hlsl = new HlslBlockModel();
|
||||
|
||||
// Get the text between the braces
|
||||
var start = context.LBRACE().Symbol.StopIndex + 1;
|
||||
var stop = context.RBRACE().Symbol.StartIndex - 1;
|
||||
|
||||
if (stop >= start)
|
||||
{
|
||||
var input = context.Start.InputStream;
|
||||
hlsl.Code = input.GetText(new Antlr4.Runtime.Misc.Interval(start, stop));
|
||||
}
|
||||
|
||||
return hlsl;
|
||||
}
|
||||
|
||||
public override object VisitShaderEntry([NotNull] GhostShaderParser.ShaderEntryContext context)
|
||||
{
|
||||
var entry = new ShaderEntryModel
|
||||
{
|
||||
EntryType = context.IDENTIFIER().GetText(),
|
||||
ShaderPath = StripQuotes(context.STRING_LITERAL(0).GetText()),
|
||||
EntryPoint = StripQuotes(context.STRING_LITERAL(1).GetText())
|
||||
};
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public override object VisitFunctionCall([NotNull] GhostShaderParser.FunctionCallContext context)
|
||||
{
|
||||
var funcCall = new FunctionCallModel
|
||||
{
|
||||
Name = context.IDENTIFIER().GetText()
|
||||
};
|
||||
|
||||
if (context.functionArguments() != null)
|
||||
{
|
||||
foreach (var arg in context.functionArguments().functionArgument())
|
||||
{
|
||||
var text = arg.GetText();
|
||||
if (text.StartsWith('"'))
|
||||
{
|
||||
text = StripQuotes(text);
|
||||
}
|
||||
funcCall.Arguments.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
return funcCall;
|
||||
}
|
||||
|
||||
private static string StripQuotes(string text)
|
||||
{
|
||||
if (text.Length >= 2 && text.StartsWith('"') && text.EndsWith('"'))
|
||||
{
|
||||
return text.Substring(1, text.Length - 2);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user