public static EffectFile Read(string filename) { filename = Path.GetFullPath(filename); var txt = File.ReadAllText(filename); txt = ProcessIncludes(Path.GetFileName(filename), txt, Path.GetDirectoryName(filename)); if (txt == null) { return(null); } var effectFile = new EffectFile(); List <string> features = new List <string>(); using (var reader = new StringReader(txt)) { bool inMultilineComment = false; StringBuilder currentBlock = null; string currentBlockName = null; bool blockIsFragment = false; int lineNumber = 1; string ln; while ((ln = reader.ReadLine()) != null) { int mlEnd = -1; if (!inMultilineComment) { var mlStart = ln.IndexOf("/*", StringComparison.Ordinal); if (mlStart != -1) { mlEnd = ln.IndexOf("*/", StringComparison.Ordinal); if (mlEnd == -1) { inMultilineComment = true; } } } else { mlEnd = ln.IndexOf("*/", StringComparison.Ordinal); if (mlEnd != -1) { inMultilineComment = false; } } var idx = ln.IndexOf('@'); if (idx != -1 && !inMultilineComment && mlEnd < 0) { bool valid = true; for (int i = 0; i < idx; i++) { if (!char.IsWhiteSpace(ln[i])) { valid = false; break; } } if (valid) { var directive = ln.Substring(idx + 1).Trim(); directive = directive.Replace("\t", " "); var vals = directive.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (vals.Length > 0) { switch (vals[0].ToLowerInvariant()) { case "name": case "vertex": case "fragment": case "feature": case "lazy": if (currentBlock != null) { if (blockIsFragment) { effectFile.FragmentSource = currentBlock.ToString(); } else { effectFile.VertexSource = currentBlock.ToString(); } } currentBlock = null; break; } switch (vals[0].ToLowerInvariant()) { case "name": effectFile.Name = directive.Substring(directive.IndexOf("name") + 4).Trim(); break; case "vertex": if (effectFile.VertexSource != null) { Console.Error.WriteLine($"Duplicate vertex block at {lineNumber}"); return(null); } currentBlock = new StringBuilder(); blockIsFragment = false; break; case "fragment": if (effectFile.FragmentSource != null) { Console.Error.WriteLine($"Duplicate fragment block at {lineNumber}"); return(null); } currentBlock = new StringBuilder(); blockIsFragment = true; break; case "feature": features.Add(vals[1]); break; case "lazy": effectFile.Lazy = true; break; default: Console.Error.WriteLine($"Invalid directive {ln} at {lineNumber}"); break; } } } } else { if (currentBlock != null) { currentBlock.AppendLine(ln); } } lineNumber++; } if (currentBlock != null) { if (blockIsFragment) { effectFile.FragmentSource = currentBlock.ToString(); } else { effectFile.VertexSource = currentBlock.ToString(); } } } if (string.IsNullOrWhiteSpace(effectFile.Name)) { effectFile.Name = Path.GetFileNameWithoutExtension(filename); } effectFile.Name = SaneName(effectFile.Name); effectFile.Features = features.ToArray(); if (effectFile.VertexSource == null) { Console.Error.WriteLine("Vertex source not specified"); return(null); } if (effectFile.FragmentSource == null) { Console.Error.WriteLine("Fragment source not specified"); return(null); } return(effectFile); }
static int Main(string[] args) { bool shouldShowHelp = false; var codeOpts = new CodeGenOptions(); string glslValidator = "glslangValidator"; var imports = new List <string>(); var options = new OptionSet { { "o|output=", "output directory", n => codeOpts.OutputDirectory = n }, { "g|glslangValidator=", "glslangValidator path", g => glslValidator = g }, { "b|brotli", "compress with brotli (.NET Core only)", b => codeOpts.Brotli = b != null }, { "l|log", "generate logging code", l => codeOpts.Log = l != null }, { "x|logmethod=", "logging method name", x => codeOpts.LogMethod = x.Trim() }, { "t|type=", "shader type name", t => codeOpts.ShaderType = t.Trim() }, { "i|import=", "import namespace", i => imports.Add(i.Trim()) }, { "n|namespace=", "generated code namespace", n => codeOpts.Namespace = n.Trim() }, { "c|compilemethod=", "shader compile method name", c => codeOpts.ShaderCompileMethod = c.Trim() }, { "p|private", "generate internal classes", p => codeOpts.Public = p == null }, { "h|help", "show this message and exit", h => shouldShowHelp = h != null }, }; List <string> input = null; try { input = options.Parse(args); } catch (OptionException e) { Console.Write("shaderprocessor: "); Console.WriteLine(e.Message); Console.WriteLine("Try `shaderprocessor --help' for more information."); return(1); } if (shouldShowHelp || input?.Count < 1) { WriteHelp(options); return(0); } if (string.IsNullOrEmpty(codeOpts.OutputDirectory)) { Console.WriteLine("Output directory must be specified."); Console.WriteLine("Try `shaderprocessor --help' for more information."); return(2); } foreach (var i in input) { if (!File.Exists(i)) { Console.Error.WriteLine($"File does not exist {i}"); return(2); } } codeOpts.Imports = imports.ToArray(); List <EffectFile> effects = new List <EffectFile>(); foreach (var i in input) { var fx = EffectFile.Read(i); if (fx == null) { Console.Error.WriteLine($"Error reading file {i}"); return(1); } effects.Add(fx); } //Don't rely on filesystem sorting, makes merges less crappy effects.Sort((x, y) => String.Compare(x.Name, y.Name, StringComparison.Ordinal)); Glslang.ToolPath = GetPath(glslValidator); if (Glslang.ToolPath != null) { foreach (var fx in effects) { for (int i = 0; i < 2; i++) { //Validate for 310 es and 430 var ver = (i == 1) ? "430" : "310 es\nprecision highp float;\nprecision highp int;"; var vs = (i == 1) ? new[] { "VERTEX_SHADER", "FEATURES430" } : new[] { "VERTEX_SHADER" }; var fs = (i == 1) ? new[] { "FRAGMENT_SHADER", "FEATURES430" } : new[] { "FRAGMENT_SHADER" }; //Default defines //VERTEX_SHADER //FRAGMENT_SHADER if (!Glslang.ValidateShader($"{fx.Name} vertex shader", "vert", InsertDefine(fx.VertexSource, ver, vs))) { return(1); } if (!Glslang.ValidateShader($"{fx.Name} fragment shader", "frag", InsertDefine(fx.FragmentSource, ver, fs))) { return(1); } //Validate syntax for all combinations of preprocessor defs foreach (var featureSet in FeatureHelper.Permute(null, fx.Features)) { var vdefs = vs.Concat(featureSet); var fdefs = fs.Concat(featureSet); var vsName = $"{fx.Name} vertex shader ({string.Join(" | ", featureSet)})"; var fsName = $"{fx.Name} fragment shader ({string.Join(" | ", featureSet)}"; if (!Glslang.ValidateShader(vsName, "vert", InsertDefine(fx.VertexSource, ver, vdefs))) { return(1); } if (!Glslang.ValidateShader(fsName, "frag", InsertDefine(fx.FragmentSource, ver, fdefs))) { return(1); } } } fx.VertexSource = "#define VERTEX_SHADER\n#line 1\n" + fx.VertexSource; fx.FragmentSource = "#define FRAGMENT_SHADER\n#line 1\n" + fx.FragmentSource; } } else { Console.Error.WriteLine("WARNING: Glslang not found. Skipping validation."); } Generate(codeOpts, effects); return(0); }
public static string Generate(CodeGenOptions opts, EffectFile fx, Dictionary <string, int> enumVals) { var compileUnit = new CodeCompileUnit(); var nsroot = new CodeNamespace(opts.Namespace); nsroot.Imports.Add(new CodeNamespaceImport("System")); foreach (var import in opts.Imports) { nsroot.Imports.Add(new CodeNamespaceImport(import)); } compileUnit.Namespaces.Add(nsroot); var genclass = new CodeTypeDeclaration(fx.Name); if (!opts.Public) { genclass.TypeAttributes = TypeAttributes.Class; } nsroot.Types.Add(genclass); var vsrc = new CodeMemberField(typeof(byte[]), "vertex_bytes"); vsrc.Attributes = MemberAttributes.Private | MemberAttributes.Static; vsrc.InitExpression = ByteArray(Compress.GetBytes(fx.VertexSource, opts.Brotli)); genclass.Members.Add(vsrc); var fsrc = new CodeMemberField(typeof(byte[]), "fragment_bytes"); fsrc.Attributes = MemberAttributes.Private | MemberAttributes.Static; fsrc.InitExpression = ByteArray(Compress.GetBytes(fx.FragmentSource, opts.Brotli)); genclass.Members.Add(fsrc); var variants = new CodeMemberField(new CodeTypeReference(opts.ShaderType, 1), "variants"); variants.Attributes = MemberAttributes.Static; genclass.Members.Add(variants); var iscompiled = new CodeMemberField(typeof(bool), "iscompiled"); iscompiled.Attributes = MemberAttributes.Private | MemberAttributes.Static; iscompiled.InitExpression = new CodePrimitiveExpression(false); genclass.Members.Add(iscompiled); int mask = 0; foreach (var feature in fx.Features) { mask |= enumVals[feature]; } var getIdx = new CodeMemberMethod(); getIdx.Name = "GetIndex"; getIdx.Attributes = MemberAttributes.Private | MemberAttributes.Static; getIdx.ReturnType = new CodeTypeReference(typeof(int)); getIdx.Parameters.Add(new CodeParameterDeclarationExpression("ShaderFeatures", "features")); //Mask out invalid flags getIdx.Statements.Add(new CodeVariableDeclarationStatement( "ShaderFeatures", "masked", new CodeBinaryOperatorExpression(new CodeArgumentReferenceExpression("features"), CodeBinaryOperatorType.BitwiseAnd, new CodeCastExpression("ShaderFeatures", new CodePrimitiveExpression(mask))))); //Continue int idx = 1; foreach (var permutation in FeatureHelper.Permute("", fx.Features)) { int flag = 0; foreach (var s in permutation) { flag |= enumVals[s]; } var expr = new CodeCastExpression("ShaderFeatures", new CodePrimitiveExpression(flag)); var cond = new CodeConditionStatement(new CodeBinaryOperatorExpression( new CodeArgumentReferenceExpression("masked"), CodeBinaryOperatorType.ValueEquality, expr), new CodeMethodReturnStatement(new CodePrimitiveExpression(idx++))); getIdx.Statements.Add(cond); } getIdx.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(0))); genclass.Members.Add(getIdx); var getShader = new CodeMemberMethod(); getShader.Name = "Get"; getShader.Attributes = MemberAttributes.Public | MemberAttributes.Static; getShader.ReturnType = new CodeTypeReference(opts.ShaderType); getShader.Parameters.Add(new CodeParameterDeclarationExpression("ShaderFeatures", "features")); var retval = new CodeArrayIndexerExpression(new CodeFieldReferenceExpression(null, "variants"), new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(null, "GetIndex"), new CodeArgumentReferenceExpression("features"))); if (fx.Lazy) { var stmt = new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(null, "Compile")); getShader.Statements.Add(stmt); } getShader.Statements.Add(new CodeMethodReturnStatement(retval)); genclass.Members.Add(getShader); var getZero = new CodeMemberMethod(); getZero.Name = "Get"; getZero.Attributes = MemberAttributes.Public | MemberAttributes.Static; getZero.ReturnType = new CodeTypeReference(opts.ShaderType); if (fx.Lazy) { var stmt = new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(null, "Compile")); getZero.Statements.Add(stmt); } var zeroRet = new CodeArrayIndexerExpression(new CodeFieldReferenceExpression(null, "variants"), new CodePrimitiveExpression(0)); getZero.Statements.Add(new CodeMethodReturnStatement(zeroRet)); genclass.Members.Add(getZero); getShader.ReturnType = new CodeTypeReference(opts.ShaderType); var compile = new CodeMemberMethod(); compile.Name = "Compile"; compile.Attributes = MemberAttributes.Public | MemberAttributes.Static; //Once only compile.Statements.Add(new CodeConditionStatement(new CodeFieldReferenceExpression(null, "iscompiled"), new CodeMethodReturnStatement())); compile.Statements.Add(new CodeAssignStatement( new CodeFieldReferenceExpression(null, "iscompiled"), new CodePrimitiveExpression(true) )); //Log if (opts.Log) { compile.Statements.Add( new CodeMethodInvokeExpression(null, opts.LogMethod, new CodePrimitiveExpression($"Compiling {fx.Name}")) ); } //Decompress code compile.Statements.Add(new CodeVariableDeclarationStatement(typeof(string), "vertsrc")); compile.Statements.Add(new CodeVariableDeclarationStatement(typeof(string), "fragsrc")); compile.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("vertsrc"), new CodeMethodInvokeExpression(null, "ShCompHelper.FromArray", new CodeFieldReferenceExpression(null, "vertex_bytes")) ) ); compile.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("fragsrc"), new CodeMethodInvokeExpression(null, "ShCompHelper.FromArray", new CodeFieldReferenceExpression(null, "fragment_bytes")) ) ); var vertRef = new CodeVariableReferenceExpression("vertsrc"); var fragRef = new CodeVariableReferenceExpression("fragsrc"); var compMeth = new CodeMethodReferenceExpression(null, opts.ShaderCompileMethod); //Init array compile.Statements.Add(new CodeAssignStatement( new CodeFieldReferenceExpression(null, "variants"), new CodeArrayCreateExpression(opts.ShaderType, idx)) ); //Compile null variant compile.Statements.Add(new CodeAssignStatement( new CodeArrayIndexerExpression(new CodeFieldReferenceExpression(null, "variants"), new CodePrimitiveExpression(0)), new CodeMethodInvokeExpression(compMeth, vertRef, fragRef, new CodePrimitiveExpression("")) )); //Compile all variants idx = 1; foreach (var permutation in FeatureHelper.Permute(null, fx.Features)) { var builder = new StringBuilder(); builder.AppendLine(); foreach (var def in permutation) { builder.Append("#define ").AppendLine(def); } builder.AppendLine("#line 1"); compile.Statements.Add(new CodeAssignStatement( new CodeArrayIndexerExpression(new CodeFieldReferenceExpression(null, "variants"), new CodePrimitiveExpression(idx++)), new CodeMethodInvokeExpression(compMeth, vertRef, fragRef, new CodePrimitiveExpression(builder.ToString())) )); } genclass.Members.Add(compile); return(GenCodeUnit(compileUnit)); }