public static GLSpec FromFile(string xmlFile) { XDocument doc = XDocument.Load(xmlFile); GLSpec spec = new GLSpec(); spec.HeaderComment = doc.Root.Element("comment").Value; foreach (var e in doc.Root.Elements("feature")) { // OpenGL is super special because it simply includes all enums and commands if (e.Attribute("api").Value == "gl" && e.Attribute("number").Value == "1.0") { AddVersion1(spec, e); } // OpenGL 3.0 and up have Compatibility profile and Core profile else if (e.Attribute("api").Value == "gl" && int.Parse(e.Attribute("number").Value[0].ToString()) >= 3) { AddVersion(spec, e, "Compatibility"); AddVersion(spec, e, "Core"); } // OpenGL pre-3.0 and OpenGL ES don't have profiles else { AddVersion(spec, e, string.Empty); } } return(spec); }
private static void WriteFileHeader(CodeWriter writer, GLSpec spec) { writer.WriteLine("// This file was autogenerated by GLCSGen on {0} UTC", DateTime.UtcNow); writer.WriteLine("// Original copyright from gl.xml:"); foreach (var l in spec.HeaderComment.Split('\n', '\r')) { writer.WriteLine("// {0}", l); } writer.WriteLine(); writer.WriteLine("using System;"); writer.WriteLine("using System.Diagnostics;"); writer.WriteLine("using System.Reflection;"); writer.WriteLine("using System.Runtime.InteropServices;"); writer.WriteLine(); writer.WriteLine("namespace OpenGL"); }
/// <summary> /// Super special OpenGL 1 version handler /// </summary> private static void AddVersion1(GLSpec spec, XElement versionElem) { var version = new GLVersion { Api = "GL", Number = "1.0", Profile = string.Empty, Name = "GL10" }; // First we process commands like any other version foreach (var require in versionElem.Elements("require")) { foreach (var commandElem in require.Elements("command")) { var command = new GLCommand { Name = commandElem.Attribute("name").Value }; command.Initialize(commandElem.Document); version.Commands.Add(command); } } // Then we go through all included commands and use all the groups on any parameters // to include all the enums we need from the groups section foreach (var group in (from c in version.Commands from p in c.Parameters select p.Group).Distinct()) { if (string.IsNullOrEmpty(group)) { continue; } foreach (var enumElem in versionElem.Document.Root.Element("groups").Elements("group").Where(e => e.Attribute("name").Value == group).Elements("enum")) { if (!version.Enums.ContainsKey(enumElem.Attribute("name").Value)) { string enumName = enumElem.Attribute("name").Value; version.Enums.Add(enumName, GetEnumValue(versionElem, enumName)); } } } spec.Versions.Add(version); }
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(); } }
/// <summary> /// Creates a given version. /// </summary> private static void AddVersion(GLSpec spec, XElement versionElem, string profile) { var version = new GLVersion { Api = versionElem.Attribute("api").Value.ToUpper(), Number = versionElem.Attribute("number").Value, Profile = profile }; if (version.Api != "GL") { version.Api = "GLES"; } version.Name = version.Api + version.Number.Replace(".", ""); if (!string.IsNullOrEmpty(profile)) { version.Name += "" + profile; } // Add all enums and commands from previous version for (int i = spec.Versions.Count - 1; i >= 0; i--) { var previousVersion = spec.Versions[i]; if (previousVersion.Api == version.Api && (previousVersion.Profile == string.Empty || previousVersion.Profile == version.Profile)) { foreach (var pair in previousVersion.Enums) { version.Enums.Add(pair.Key, pair.Value); } foreach (var command in previousVersion.Commands) { version.Commands.Add(command.Clone()); } // only handle previous one version of same API and profile since it will have folded // all enums/commands in turn break; } } // Include all new enums and commands foreach (var require in versionElem.Elements("require")) { foreach (var enumElem in require.Elements("enum")) { if (!version.Enums.ContainsKey(enumElem.Attribute("name").Value)) { string enumName = enumElem.Attribute("name").Value; version.Enums.Add(enumName, GetEnumValue(versionElem, enumName)); } } foreach (var commandElem in require.Elements("command")) { var command = new GLCommand { Name = commandElem.Attribute("name").Value }; if (version.Commands.Find(c => c.Name == command.Name) != null) { continue; } command.Initialize(commandElem.Document); version.Commands.Add(command); } } // Remove any enums and commands foreach (var remove in versionElem.Elements("remove")) { if (remove.Attribute("profile") == null || remove.Attribute("profile").Value.ToLower() == profile.ToLower()) { foreach (var en in remove.Elements("enum")) { version.Enums.Remove(en.Attribute("name").Value); } foreach (var en in remove.Elements("command")) { version.Commands.RemoveAll(c => c.Name == en.Attribute("name").Value); } } } spec.Versions.Add(version); }
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(); } } }