private static void CreateGLInterop(GLSpec spec, DirectoryInfo outDir, string api) { // Get the unique list of all commands List <GLCommand> commands = new List <GLCommand>(); foreach (var version in spec.Versions) { if (version.Api != api) { continue; } foreach (var c in version.Commands) { if (commands.Find(c2 => c2.Name == c.Name) == null) { commands.Add(c); } } } // Just to be pretty, sort them by name :) commands.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); // Create the interop file containing all of the delegate types and instances using (var writer = new CodeWriter(Path.Combine(outDir.FullName, api + "Interop.cs"))) { WriteFileHeader(writer, spec); writer.WriteOpenBrace(); writer.WriteLine("internal static class {0}Interop", api); writer.WriteOpenBrace(); WriteDelegates(writer, "public", commands); writer.WriteCloseBrace(); writer.WriteCloseBrace(); } }
static void Main(string[] args) { // Isolated mode generates files like we always have, with each C# file being completely standalone // with its own set of delegates under the hood. Without isolated mode, the new default is to generate // a single interop type per assembly and have each GL version share those function pointers. This // reduces assembly size by removing lots and lots of duplicate delegate types and function pointers. bool isolated = args.Contains("--isolated"); // Since error handling code adds size to the generated assemblies, there is now an option to remove // it for people who want extremely trim assemblies and don't mind dealing with harder-to-read error // messages when function pointers fail to load. bool errorHandling = !args.Contains("--no-error-check"); // Walk up to the solution file so we can then go into GL-CS and write to the C# files directly DirectoryInfo directory = new DirectoryInfo(Directory.GetCurrentDirectory()); while (directory != null && !File.Exists(Path.Combine(directory.FullName, "GL-CS.sln"))) { directory = directory.Parent; } // If the solution wasn't found (maybe we're not running from the bin directory) // we'll just write out to the current directory if (directory == null) { directory = new DirectoryInfo(Directory.GetCurrentDirectory()); } // Create our two directories for output files var glDirectory = new DirectoryInfo(Path.Combine(directory.FullName, "GL-CS")); var glesDirectory = new DirectoryInfo(Path.Combine(directory.FullName, "GLES-CS")); // Load the spec var spec = GLSpec.FromFile("gl.xml"); //using (var stream = File.Create("spec.xml")) //{ // var serializer = new XmlSerializer(typeof(GLSpec)); // serializer.Serialize(stream, spec); //} // In non-isolated mode we have to build up a list of all the commands in all versions of // the module, and write them into a single GLInterop type. if (!isolated) { CreateGLInterop(spec, glDirectory, "GL"); CreateGLInterop(spec, glesDirectory, "GLES"); } foreach (var version in spec.Versions) { var dir = version.Api == "GL" ? glDirectory : glesDirectory; using (var writer = new CodeWriter(Path.Combine(dir.FullName, version.Name + ".cs"))) { WriteFileHeader(writer, spec); writer.WriteOpenBrace(); writer.WriteLine("public static class {0}", version.Name); writer.WriteOpenBrace(); writer.WriteLine("#region Enums"); foreach (var e in version.Enums) { string type = IsUint(e.Value) ? "uint" : "ulong"; writer.WriteLine("public static {0} {1} = {2};", type, e.Key, e.Value); } writer.WriteLine("#endregion"); writer.WriteLine(); writer.WriteLine("#region Commands"); foreach (var c in version.Commands) { StringBuilder builder = new StringBuilder("public static "); builder.Append(ConvertGLType(c.ReturnType)); builder.AppendFormat(" {0}(", c.Name); BuildParameterList(c, builder); builder.Append(")"); writer.WriteLine(builder.ToString()); writer.WriteOpenBrace(); builder.Clear(); if (c.ReturnType != "void") { builder.Append("return "); } if (!isolated) { builder.Append(version.Api); builder.Append("Interop."); } builder.AppendFormat("{0}Ptr(", c.Name); if (c.Parameters.Count > 0) { foreach (var p in c.Parameters) { var name = p.Name; // Add @ to start of any names that are C# keywords to avoid conflict if (name == "params" || name == "string" || name == "ref" || name == "base") { name = "@" + name; } builder.AppendFormat("{0}, ", name); } builder.Length -= 2; } builder.Append(");"); writer.WriteLine(builder.ToString()); writer.WriteCloseBrace(); } writer.WriteLine("#endregion"); if (isolated) { writer.WriteLine(); writer.WriteLine("#region Command Delegates"); WriteDelegates(writer, "private", version.Commands); writer.WriteLine("#endregion"); } writer.WriteLine(); writer.WriteLine("#region Interop"); writer.WriteLine("public static Func<string, IntPtr> GetProcAddress = null;"); writer.WriteLine(); writer.WriteLine("public static void LoadAllFunctions()"); writer.WriteOpenBrace(); foreach (var c in version.Commands) { var delegateName = isolated ? c.Name : (version.Api + "Interop." + c.Name); string getFuncPtrCode = string.Format("{0}Ptr = ({0}Func)Marshal.GetDelegateForFunctionPointer(GetProcAddress(\"{1}\"), typeof({0}Func));", delegateName, c.Name); if (errorHandling) { writer.WriteLine("try {{ {0} }}", getFuncPtrCode); writer.WriteLine("catch {{ throw new InvalidOperationException(\"Failed to get function pointer for '{0}'.\"); }}", c.Name); } else { writer.WriteLine(getFuncPtrCode); } } writer.WriteCloseBrace(); writer.WriteLine(); writer.WriteLine("public static void LoadFunction(string name)"); writer.WriteOpenBrace(); if (errorHandling) { writer.WriteLine("try"); writer.WriteOpenBrace(); } writer.WriteLine("var memberInfo = typeof({0}).GetField(name + \"Ptr\", BindingFlags.{1} | BindingFlags.Static);", isolated ? version.Name : (version.Api + "Interop"), isolated ? "NonPublic" : "Public"); if (errorHandling) { writer.WriteLine("Debug.Assert(memberInfo != null, string.Format(\"Failed to find function delegate. Ensure '{0}' is a valid OpenGL function.\", name));"); } writer.WriteLine("var procAddr = GetProcAddress(name);"); if (errorHandling) { writer.WriteLine("Debug.Assert(procAddr != IntPtr.Zero, string.Format(\"Failed to find function address. Ensure '{0}' is a valid OpenGL function.\", name));"); } writer.WriteLine("var funcPtr = Marshal.GetDelegateForFunctionPointer(procAddr, memberInfo.FieldType);"); if (errorHandling) { writer.WriteLine("Debug.Assert(funcPtr != null, string.Format(\"Failed to convert function address to delegate for '{0}'.\", name));"); } writer.WriteLine("memberInfo.SetValue(null, funcPtr);"); if (errorHandling) { writer.WriteCloseBrace(); writer.WriteLine("catch"); writer.WriteOpenBrace(); writer.WriteLine("throw new InvalidOperationException(string.Format(\"Failed to load function '{0}'.\", name));"); writer.WriteCloseBrace(); } writer.WriteCloseBrace(); writer.WriteLine("#endregion"); writer.WriteCloseBrace(); writer.WriteCloseBrace(); } } }
private static void CreateGLInterop(GLSpec spec, DirectoryInfo outDir, string api) { // Get the unique list of all commands List<GLCommand> commands = new List<GLCommand>(); foreach (var version in spec.Versions) { if (version.Api != api) { continue; } foreach (var c in version.Commands) { if (commands.Find(c2 => c2.Name == c.Name) == null) { commands.Add(c); } } } // Just to be pretty, sort them by name :) commands.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); // Create the interop file containing all of the delegate types and instances using (var writer = new CodeWriter(Path.Combine(outDir.FullName, api + "Interop.cs"))) { WriteFileHeader(writer, spec); writer.WriteOpenBrace(); writer.WriteLine("internal static class {0}Interop", api); writer.WriteOpenBrace(); WriteDelegates(writer, "public", commands); writer.WriteCloseBrace(); writer.WriteCloseBrace(); } }
static void Main(string[] args) { // Isolated mode generates files like we always have, with each C# file being completely standalone // with its own set of delegates under the hood. Without isolated mode, the new default is to generate // a single interop type per assembly and have each GL version share those function pointers. This // reduces assembly size by removing lots and lots of duplicate delegate types and function pointers. bool isolated = args.Contains("--isolated"); // Since error handling code adds size to the generated assemblies, there is now an option to remove // it for people who want extremely trim assemblies and don't mind dealing with harder-to-read error // messages when function pointers fail to load. bool errorHandling = !args.Contains("--no-error-check"); // Walk up to the solution file so we can then go into GL-CS and write to the C# files directly DirectoryInfo directory = new DirectoryInfo(Directory.GetCurrentDirectory()); while (directory != null && !File.Exists(Path.Combine(directory.FullName, "GL-CS.sln"))) { directory = directory.Parent; } // If the solution wasn't found (maybe we're not running from the bin directory) // we'll just write out to the current directory if (directory == null) { directory = new DirectoryInfo(Directory.GetCurrentDirectory()); } // Create our two directories for output files var glDirectory = new DirectoryInfo(Path.Combine(directory.FullName, "GL-CS")); var glesDirectory = new DirectoryInfo(Path.Combine(directory.FullName, "GLES-CS")); // Load the spec var spec = GLSpec.FromFile(Path.Combine(directory.FullName, "GLCSGen", "gl.xml")); //using (var stream = File.Create("spec.xml")) //{ // var serializer = new XmlSerializer(typeof(GLSpec)); // serializer.Serialize(stream, spec); //} // In non-isolated mode we have to build up a list of all the commands in all versions of // the module, and write them into a single GLInterop type. if (!isolated) { CreateGLInterop(spec, glDirectory, "GL"); CreateGLInterop(spec, glesDirectory, "GLES"); } foreach (var version in spec.Versions) { var dir = version.Api == "GL" ? glDirectory : glesDirectory; using (var writer = new CodeWriter(Path.Combine(dir.FullName, version.Name + ".cs"))) { WriteFileHeader(writer, spec); writer.WriteOpenBrace(); writer.WriteLine("public static class {0}", version.Name); writer.WriteOpenBrace(); writer.WriteLine("#region Enums"); foreach (var e in version.Enums) { string type = IsUint(e.Value) ? "uint" : "ulong"; writer.WriteLine("public static {0} {1} = {2};", type, e.Key, e.Value); } writer.WriteLine("#endregion"); writer.WriteLine(); writer.WriteLine("#region Commands"); foreach (var c in version.Commands) { StringBuilder builder = new StringBuilder("public static "); builder.Append(ConvertGLType(c.ReturnType)); builder.AppendFormat(" {0}(", c.Name); BuildParameterList(c, builder); builder.Append(")"); writer.WriteLine(builder.ToString()); writer.WriteOpenBrace(); builder.Clear(); if (c.ReturnType != "void") { builder.Append("return "); } if (!isolated) { builder.Append(version.Api); builder.Append("Interop."); } builder.AppendFormat("{0}Ptr(", c.Name); if (c.Parameters.Count > 0) { foreach (var p in c.Parameters) { var name = p.Name; // Add @ to start of any names that are C# keywords to avoid conflict if (name == "params" || name == "string" || name == "ref" || name == "base") { name = "@" + name; } builder.AppendFormat("{0}, ", name); } builder.Length -= 2; } builder.Append(");"); writer.WriteLine(builder.ToString()); writer.WriteCloseBrace(); } writer.WriteLine("#endregion"); if (isolated) { writer.WriteLine(); writer.WriteLine("#region Command Delegates"); WriteDelegates(writer, "private", version.Commands); writer.WriteLine("#endregion"); } writer.WriteLine(); writer.WriteLine("#region Interop"); writer.WriteLine("public static Func<string, IntPtr> GetProcAddress = null;"); writer.WriteLine(); writer.WriteLine("public static void LoadAllFunctions()"); writer.WriteOpenBrace(); foreach (var c in version.Commands) { var delegateName = isolated ? c.Name : (version.Api + "Interop." + c.Name); string getFuncPtrCode = string.Format("{0}Ptr = ({0}Func)Marshal.GetDelegateForFunctionPointer(GetProcAddress(\"{1}\"), typeof({0}Func));", delegateName, c.Name); if (errorHandling) { writer.WriteLine("try {{ {0} }}", getFuncPtrCode); writer.WriteLine("catch {{ throw new InvalidOperationException(\"Failed to get function pointer for '{0}'.\"); }}", c.Name); } else { writer.WriteLine(getFuncPtrCode); } } writer.WriteCloseBrace(); writer.WriteLine(); writer.WriteLine("public static void LoadFunction(string name)"); writer.WriteOpenBrace(); if (errorHandling) { writer.WriteLine("try"); writer.WriteOpenBrace(); } writer.WriteLine("var memberInfo = typeof({0}).GetField(name + \"Ptr\", BindingFlags.{1} | BindingFlags.Static);", isolated ? version.Name : (version.Api + "Interop"), isolated ? "NonPublic" : "Public"); if (errorHandling) { writer.WriteLine("Debug.Assert(memberInfo != null, string.Format(\"Failed to find function delegate. Ensure '{0}' is a valid OpenGL function.\", name));"); } writer.WriteLine("var procAddr = GetProcAddress(name);"); if (errorHandling) { writer.WriteLine("Debug.Assert(procAddr != IntPtr.Zero, string.Format(\"Failed to find function address. Ensure '{0}' is a valid OpenGL function.\", name));"); } writer.WriteLine("var funcPtr = Marshal.GetDelegateForFunctionPointer(procAddr, memberInfo.FieldType);"); if (errorHandling) { writer.WriteLine("Debug.Assert(funcPtr != null, string.Format(\"Failed to convert function address to delegate for '{0}'.\", name));"); } writer.WriteLine("memberInfo.SetValue(null, funcPtr);"); if (errorHandling) { writer.WriteCloseBrace(); writer.WriteLine("catch"); writer.WriteOpenBrace(); writer.WriteLine("throw new InvalidOperationException(string.Format(\"Failed to load function '{0}'.\", name));"); writer.WriteCloseBrace(); } writer.WriteCloseBrace(); writer.WriteLine("#endregion"); writer.WriteCloseBrace(); writer.WriteCloseBrace(); } } }