// Copyright (c) 2019 Jean-Philippe Bruyère // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Xml; using System.Linq; using System.Net; using System.Text.RegularExpressions; namespace vk.generator { #region ExtensionsMethods public static class ExtensionsMethods { public static string RemoveTagsAndFlagsSuffix(this string s) { if (string.IsNullOrEmpty(s)) return s; string tmp = s.Replace("Flags", ""); foreach (string tag in Generator.tags) { if (tmp.EndsWith(tag, StringComparison.Ordinal)) { tmp = tmp.Remove(tmp.Length - tag.Length); break; } } return tmp; } public static string RemoveCommonLeadingPart (this string s, string other) { string tmp = ""; string tmp2 = other.Replace ("Flags", ""); foreach (string tag in Generator.tags) { if (tmp2.EndsWith (tag, StringComparison.Ordinal)) { tmp2 = tmp2.Remove (tmp2.Length - tag.Length); break; } } for (int i = 0; i < tmp2.Length; i++) { if (s[i] != tmp2[i]) { tmp = s.Substring (i); break; } } if (string.IsNullOrEmpty (tmp)) tmp = s.Substring (tmp2.Length); if (char.IsNumber (tmp[0])) { tmp2 = tmp2.Replace ("Type", ""); return tmp2.Substring (2) + tmp; } return tmp; } public static string ConvertUnderscoredUpperCasedNameToCamelCase (this string name) { string[] tmp = name.Split ('_'); List result = new List (); for (int i = 0; i < tmp.Length; i++) { string s = tmp[i]; if (s == "BIT") continue; if ((i == tmp.Length - 1 && Generator.tags.Contains (s)) || (char.IsDigit (s[0]) && s.Substring (1) == "D")) result.Add (s); else result.Add (s[0] + s.Substring (1).ToLower ()); } return result.Aggregate ((a, b) => a + b); } public static string[] SplitCamelCased (this string cc) { List result = new List (); string tmp = ""; for (int i = 0; i < cc.Length; i++) { if (char.IsUpper (cc[i])) { if (!string.IsNullOrEmpty (tmp)) result.Add (tmp); tmp = "" + cc[i]; } else tmp += cc[i]; } if (!string.IsNullOrEmpty (tmp)) result.Add (tmp); return result.ToArray (); } } #endregion /// /// Code produced by this generator is greatly inspired from the work of https://github.com/mellinoe, but the generator /// has been recreated from scratch to be more like a simple functional parser easyer to tweak. /// public class Generator { [Conditional ("LOG_VK_NET_GEN")] static void log_vk_net_gen (ConsoleColor c, string msg) { Console.ForegroundColor = c; Console.WriteLine ($"[GEN] {msg}"); Console.ResetColor(); } const int extBase = 1000000000; const int extBlockSize = 1000; /// Main namespace for generated code. public const string vknamespace = "Vulkan"; /// Static class encapsulating constants and methods public const string vkCommonCmdClassName = "Vk"; /// Headers for generated c# files public const string heading = "/* autogenerated with http://github.com/jpbruyere/vk.net */"; /// using to include in every generated c# files public static string[] defaultUsings = { "System" }; /* credit to https://github.com/mellinoe for this list*/ public static Dictionary knownTypes = new Dictionary { {"uint8_t", "byte"}, {"int8_t", "byte"}, {"char", "byte"}, {"uint16_t", "ushort"}, {"uint32_t", "uint"}, {"uint64_t", "ulong"}, {"int16_t", "short"}, {"int32_t", "int"}, {"int64_t", "long"}, {"size_t", "UIntPtr"}, {"VkSampleMask", "uint"}, {"VkDeviceSize", "ulong"}, {"VkDeviceAddress", "ulong"}, {"HMONITOR", "IntPtr"}, { "DWORD", "uint" }, { "ANativeWindow", "Android.ANativeWindow" }, { "MirConnection", "Mir.MirConnection" }, { "MirSurface", "Mir.MirSurface" }, { "wl_display", "Wayland.wl_display" }, { "wl_surface", "Wayland.wl_surface" }, { "Display", "Xlib.Display" }, { "Window", "Xlib.Window" }, { "VisualID", "Xlib.VisualID" }, { "RROutput", "IntPtr" }, { "HINSTANCE", "Win32.HINSTANCE" }, { "HWND", "Win32.HWND" }, { "HANDLE", "Win32.HANDLE" }, { "SECURITY_ATTRIBUTES", "Win32.SECURITY_ATTRIBUTES" }, { "LPCWSTR", "IntPtr" }, { "xcb_connection_t", "Xcb.xcb_connection_t" }, { "xcb_window_t", "Xcb.xcb_window_t" }, { "xcb_visualid_t", "Xcb.xcb_visualid_t" }, //those are not checked, just putted there for compiling { "GgpFrameToken", "uint" }, //cant find header to check length { "zx_handle_t", "ulong" }, { "GgpStreamDescriptor", "ulong" }, { "PFN_vkVoidFunction","IntPtr" }, { "AHardwareBuffer", "Android.ANativeWindow" },//must be corrected { "IDirectFB","IntPtr" }, { "IDirectFBSurface","IntPtr" }, { "_screen_window","IntPtr" }, { "_screen_context","IntPtr" }, { "StdVideoH265ProfileIdc","IntPtr" }, { "StdVideoH264ProfileIdc","IntPtr" }, { "VkRemoteAddressNV","IntPtr" }, { "CAMetalLayer", "IntPtr" } }; static string[] reservedNames = { "event", "object" }; static string[] csValueTypes = { "byte", "int", "uint", "float", "ushort", "ulong", "IntPtr", "UIntPtr" }; static string[] skipGenStruct = { "VkClearColorValue", "VkTransformMatrixKHR" }; static Dictionary paramTypeAliases = new Dictionary { { "void*", new string[] {"IntPtr"} }, { "char*", new string[] {"string"} } }; //function pointers to retrieve before any other vulkan api call. //those command should not move in other iface than common to be preload by normal dyn load static string[] preloadedCommands = { "vkCreateInstance", "vkDestroyInstance", "vkGetDeviceProcAddr", "vkGetInstanceProcAddr", "vkEnumerateInstanceExtensionProperties", "vkEnumerateInstanceLayerProperties", "vkEnumerateInstanceVersion"}; public static Dictionary aliases = new Dictionary (); static void AddAlias (string from, string to) { if (aliases.ContainsKey (from)) log_vk_net_gen (ConsoleColor.Red, $"aliases list constains already an alias for: {from}"); else aliases.Add (from, to); log_vk_net_gen (ConsoleColor.DarkGray, $"adding alias {from} -> {to}"); } enum EnumTypes { bitmask, @enum }; enum ExtensionType { None, Device, Instance } enum TypeCategories {none, basetype, bitmask, define, @enum, funcpointer, group, handle, include, @struct, @union }; /// /// array proxies list to created to avoid fixed arrays /// /// size /// type /// static Dictionary arrayProxies = new Dictionary(); //contains raw types names static List structurePointerProxies = new List(100); static Dictionary paramsDefs = new Dictionary (100); public static List tags = new List (); class Definition { public List definedBy = new List(); protected string name; public string comment; public string extends; public override string ToString () => this.GetType ().Name + ":" + name; public string Name { get { return reservedNames.Contains (name) ? "_" + name : name; } set { name = value; } } public virtual string CSName { get { string tmp = resolveAlias(name); return (Generator.knownTypes.ContainsKey (tmp) ? Generator.knownTypes[tmp] : tmp); } } } class StructDef : TypeDef { public string structextends; public List members = new List (); } class TypeDef : Definition { public TypeCategories category; public string baseType; public string requires; public string bitvalues; public string parent; public bool nonDispatchable => category == TypeCategories.handle && baseType == "VK_DEFINE_NON_DISPATCHABLE_HANDLE"; public bool tryGetRequirementsTypeDef (out TypeDef reqType) { reqType = requires == null ? null : types.FirstOrDefault (t=>t.name == requires); return reqType != null; } public bool tryGetBaseType (out TypeDef _baseType) { _baseType = baseType == null ? null : types.FirstOrDefault (t=>t.Name == baseType); return _baseType != null; } public bool IsValueType => csValueTypes.Contains (CSName); public bool IsEnum => category == TypeCategories.bitmask || category == TypeCategories.@enum; public string enumDefName => IsEnum ? requires == null ? bitvalues == null ? name : bitvalues : requires : null; public EnumDef enumDef { get { string enumDefStr = enumDefName; return (enumDefStr == null) ? null : enums.FirstOrDefault (e=>e.Name == enumDefStr); } } public override string CSName { get { if (IsEnum && enumDef == null) {//enums without values TypeDef _baseType = null; if (tryGetBaseType (out _baseType)) { while (_baseType.tryGetBaseType (out TypeDef bT)) _baseType = bT; return _baseType.CSName; } } string tmp = resolveAlias (name); if (Generator.knownTypes.ContainsKey (tmp)) return Generator.knownTypes[tmp]; if (requires != "vk_platform" && tryGetRequirementsTypeDef (out TypeDef req) && req.category == TypeCategories.include) return "IntPtr"; return tmp; } } public override string ToString () => $"{category,-10}: {name,-36} {baseType,-20} {requires}{bitvalues}"; } class EnumDef : Definition { //public string require; public EnumTypes type; public int start; public int end; public string vendor; public TypeDef typeDef { get { string tmp = resolveAlias(name); return types.FirstOrDefault (t=>t.Name == tmp); } } public bool tryGetTypeDef (out TypeDef td ) { td = typeDef; return td != null; } public string baseType { get { if (tryGetTypeDef (out TypeDef td)) { TypeDef baseType = null; if (td.tryGetBaseType (out baseType)) { while (baseType.tryGetBaseType (out TypeDef bT)) baseType = bT; return baseType.CSName; } } return type == EnumTypes.@enum ? "int" : "uint"; } } public List values = new List (); public override string ToString() => $"{type,-8}{values.Count,3} {typeDef?.ToString()}"; public IEnumerable AllValues => values.Concat(Generator.extends.Where(e=>e.extends == name)); } class ParamDef : Definition { public bool optional; public string txtbefore; public string txtafter; public static ParamDef parse (XmlNode n) { XmlNode t = n["type"]; ParamDef tmp = new ParamDef (); tmp.name = t.InnerText; tmp.optional = string.Equals (t.Attributes["optional"]?.Value, "true", StringComparison.OrdinalIgnoreCase); if (t.PreviousSibling?.NodeType == XmlNodeType.Text) tmp.txtbefore = t.PreviousSibling.Value; if (t.NextSibling?.NodeType == XmlNodeType.Text) tmp.txtafter = t.NextSibling.Value; if (paramsDefs.ContainsKey (tmp.FullCTypeDecl)) return paramsDefs[tmp.FullCTypeDecl]; paramsDefs.Add (tmp.FullCTypeDecl, tmp); return tmp; } public string FullCTypeDecl => txtbefore + name + txtafter; public int IndirectionLevel { get { if (string.IsNullOrEmpty (txtafter)) return 0; int i = 0; foreach (char c in txtafter) { if (c == '*') i++; } return i; } } public bool tryGetTypeDef (out TypeDef td ) { string tmp = name; td = types.FirstOrDefault (t=>t.Name == tmp); if (td == null) td = types.FirstOrDefault (t=>t.Name == resolveAlias (tmp)); return td != null; } public bool IsStruct => string.IsNullOrEmpty (txtbefore) ? false : txtbefore.Contains ("struct"); public bool IsConst => string.IsNullOrEmpty (txtbefore) ? false : txtbefore.Contains ("const"); public override string ToString () => FullCTypeDecl; } class MemberDef : Definition { public ParamDef paramDef; public bool isReadOnly; public bool optional; public bool externsync; public bool nullTerminated; public string len; public string defaultValue; public string fixedArray; public bool HasDefaultValue => defaultValue != null; public bool GetIsFixedArray (out string[] dims) { dims = fixedArray?.Split (new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries); return dims != null; } public override string ToString () => string.Format ($"memberdef: {paramDef.FullCTypeDecl} {name}"); } class EnumerantValue : Definition, IEquatable{//name is not yet converted to csname!! EnumDef enumDef; /*public EnumDef enumDef { get { if (_enumDef == null && extends != null) return enums.FirstOrDefault (e=>e.Name == extends); return _enumDef; } set => _enumDef = value; }*/ public EnumerantValue (EnumDef enumDef = null) { this.enumDef = enumDef; } public string unusedComment; public string value; public bool isAlias; public bool tryGetAlias (out EnumerantValue aliasEv) { if (enumDef == null && extends != null) aliasEv = enums.First (e=>e.Name == extends).AllValues.FirstOrDefault (v=>v.name == value); else aliasEv = enumDef.AllValues.FirstOrDefault (e=>e.name == value); return aliasEv != null; } public override string ToString () => $"{name,-40}{enumDef?.CSName}.{CSName} = {value}"; public override string CSName { get { string ccValue = name.ConvertUnderscoredUpperCasedNameToCamelCase(); return enumDef == null ? extends == null ? ccValue : ccValue.RemoveCommonLeadingPart (enums.First (e=>e.Name == extends).CSName) : ccValue.RemoveCommonLeadingPart (enumDef.CSName); } } public string ResolvedName = null; public static string GetArrayProxyStructName (string value) { string ccValue = value.ConvertUnderscoredUpperCasedNameToCamelCase(); if (ccValue.StartsWith("Vk", StringComparison.Ordinal)) ccValue = ccValue.Substring (2); if (ccValue.StartsWith("Max", StringComparison.Ordinal)) ccValue = ccValue.Substring (3); if (ccValue.EndsWith("Size", StringComparison.Ordinal)) ccValue = ccValue.Substring (0, ccValue.Length - 4); return $"{ccValue}_proxy"; } public bool Equals (EnumerantValue other) { return name == other.name; } public override bool Equals(object obj){ if(obj == null) return false; return name == (obj as EnumerantValue).name; } public override int GetHashCode(){ return value.GetHashCode(); } } class FuncpointerDef : TypeDef { public ParamDef returnType; public List parameters = new List (); } class CommandDef : FuncpointerDef { public string alias; public string successCodes; public string errorcodes; } static List types = new List (); static List enums = new List (); static List commands = new List (); static List extends = new List (); static List constants = new List (); class InterfaceDef : Definition { public int number; } class FeatureDef : InterfaceDef { public string api; public int major; public int minor; } class ExtensionDef : InterfaceDef { public ExtensionType type; public string supported; public bool Disabled => supported == "disabled"; public string[] requires; } static List features = new List (); static List extensions = new List (); #region code generation static void writePreamble (IndentedTextWriter tw, params string[] additionalUsings) { tw.WriteLine (heading); foreach (string @using in defaultUsings) tw.WriteLine ($"using {@using};"); foreach (string @using in additionalUsings) tw.WriteLine ($"using {@using};"); tw.WriteLine ($"namespace {vknamespace} {{"); } static EnumDef structTypeEnum = null; static void writeStructNew (IndentedTextWriter tw, StructDef sd) { MemberDef md = sd.members.Where (mb => mb.Name == "sType").First (); string csStructName = $"{structTypeEnum.CSName}.{structTypeEnum.AllValues.FirstOrDefault(st=>st.Name == md.defaultValue).CSName}"; tw.WriteLine ($"public static {sd.Name} New () {{"); tw.Indent++; tw.WriteLine ($"return new {sd.Name} {{"); tw.Indent++; tw.WriteLine ($"sType = {csStructName},"); tw.WriteLine ($"pNext = IntPtr.Zero"); tw.Indent--; tw.WriteLine (@"};"); tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine ($"public {sd.Name} (IntPtr pNext) : this () {{"); tw.Indent++; tw.WriteLine ($"sType = {csStructName};"); tw.WriteLine ($"this.pNext = pNext;"); tw.Indent--; tw.WriteLine (@"}"); } static bool tryGetVectorType (string type, int dim, out string vectorType) { vectorType = null; switch (type) { case "float": switch (dim) { case 2: vectorType = "Vector2"; return true; case 3: vectorType = "Vector3"; return true; case 4: vectorType = "Vector4"; return true; default: return false; } case "int": switch (dim) { case 2: vectorType = "Vector2ui"; return true; case 3: vectorType = "Vector3ui"; return true; default: return false; } case "uint": switch (dim) { case 2: vectorType = "Vector2ui"; return true; case 3: vectorType = "Vector3ui"; return true; default: return false; } default: return false; } } static void gen_array_proxies (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"proxies_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, "System.Runtime.InteropServices"); tw.Indent++; foreach (KeyValuePair kvp in arrayProxies) { tw.WriteLine ($"[StructLayout(LayoutKind.Sequential, Size = (int)Vk.{constants.FirstOrDefault(e=>e.Name == kvp.Key).CSName})]"); string structName = EnumerantValue.GetArrayProxyStructName (kvp.Key); string baseType = kvp.Value == "char" ? "byte" : kvp.Value; tw.WriteLine ($"public struct {structName} {{"); tw.Indent++; tw.WriteLine ($"public Span<{baseType}> AsSpan {{"); tw.Indent++; tw.WriteLine ($"get {{"); tw.Indent++; tw.WriteLine ($"Span<{structName}> valSpan = MemoryMarshal.CreateSpan(ref this, 1);"); tw.WriteLine ($"return MemoryMarshal.Cast<{structName}, {baseType}>(valSpan);"); tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); if (kvp.Value == "char") { tw.WriteLine ($"public static implicit operator string ({structName} s) => s.ToString();"); tw.WriteLine ($"public static implicit operator {structName} (string s)"); tw.WriteLine (@"{"); tw.Indent++; tw.WriteLine ($"{structName} tmp = default;"); tw.WriteLine ($"tmp.Update (s);"); tw.WriteLine ($"return tmp;"); tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine ($"public override string ToString()"); tw.WriteLine (@"{"); tw.Indent++; tw.WriteLine ($"Span<{structName}> valSpan = MemoryMarshal.CreateSpan(ref this, 1);"); tw.WriteLine ($"ReadOnlySpan bytes = MemoryMarshal.Cast<{structName}, byte>(valSpan);"); tw.WriteLine ($"return System.Text.Encoding.UTF8.GetString (bytes);"); tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine ($"void Update(string value)"); tw.WriteLine (@"{"); tw.Indent++; tw.WriteLine ($"Span<{structName}> valSpan = MemoryMarshal.CreateSpan(ref this, 1);"); tw.WriteLine ($"Span bytes = MemoryMarshal.Cast<{structName}, byte>(valSpan);"); tw.WriteLine ($"System.Text.Encoding.UTF8.GetBytes (value).CopyTo (bytes);"); tw.Indent--; tw.WriteLine (@"}"); } tw.Indent--; tw.WriteLine (@"}"); } tw.Indent--; tw.WriteLine (@"}"); } } } static void gen_structurePointer_proxies (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"struct_pointer_proxies_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, "System.Runtime.InteropServices", "System.Linq", "System.Collections.Generic", "System.Collections.ObjectModel"); tw.Indent++; foreach (string str in structurePointerProxies) { string baseType = str; bool isUintEnum = false, isIntEnum = false, isIEnumerable = false; if (baseType.EndsWith ('*')) { isUintEnum = true; baseType = baseType.Substring (0, baseType.Length - 1); } else if (baseType.EndsWith ('#')) { isIntEnum = true; baseType = baseType.Substring (0, baseType.Length - 1); } if (baseType.EndsWith ('+')) { isIEnumerable = true; baseType = baseType.Substring (0, baseType.Length - 1); } string ptrType = isIEnumerable ? baseType.Replace('.','_') + "CollectionPtr" : baseType.Replace('.','_') + "Ptr"; tw.WriteLine ($"public class {ptrType} {{"); tw.Indent++; if (isIEnumerable) { tw.WriteLine ($"internal IEnumerable<{baseType}> instance;"); if (isUintEnum) tw.WriteLine ($"internal IntPtr handle => instance.Cast().ToArray().PinPointer();"); else if (isIntEnum) tw.WriteLine ($"internal IntPtr handle => instance.Cast().ToArray().PinPointer();"); else tw.WriteLine ($"internal IntPtr handle => instance.ToArray().PinPointer();"); tw.WriteLine ($"internal int Count => instance.Count();"); tw.WriteLine ($"{ptrType} (IEnumerable<{baseType}> str) {{"); } else { tw.WriteLine ($"internal {baseType} instance;"); if (isUintEnum) tw.WriteLine ($"internal IntPtr handle => ((uint)instance).PinPointer();"); else if (isIntEnum) tw.WriteLine ($"internal IntPtr handle => ((int)instance).PinPointer();"); else tw.WriteLine ($"internal IntPtr handle => instance.PinPointer();"); tw.WriteLine ($"{ptrType} ({baseType} str) {{"); } tw.Indent++; tw.WriteLine ($"instance = str;"); tw.Indent--; tw.WriteLine (@"}"); if (isIEnumerable) { tw.WriteLine ($"public static implicit operator {ptrType} ({baseType} s) => new {ptrType} (new {baseType}[] {{s}});"); tw.WriteLine ($"public static implicit operator {ptrType} (List<{baseType}> s) => new {ptrType} (s);"); tw.WriteLine ($"public static implicit operator {ptrType} ({baseType}[] s) => new {ptrType} (s);"); tw.WriteLine ($"public static implicit operator {ptrType} (Collection<{baseType}> s) => new {ptrType} (s);"); } else tw.WriteLine ($"public static implicit operator {ptrType} ({baseType} s) => new {ptrType} (s);"); tw.Indent--; tw.WriteLine (@"}"); } tw.Indent--; tw.WriteLine (@"}"); } } } //check if ptrProxies are used => if yes must implement IDisposable static string[] getStructurePtrProxies (StructDef sd) { List proxies = new List(); foreach (MemberDef mb in sd.members) { if (!mb.paramDef.tryGetTypeDef (out TypeDef td)) throw new Exception($"type not found {mb.paramDef}"); if (mb.paramDef.IndirectionLevel == 1){ if (mb.paramDef.Name == "char") continue; else if (mb.paramDef.Name != "void" && !csValueTypes.Contains (td.CSName)) { /*if (td == null || (td.category == TypeCategories.basetype && td.baseType == null)) continue;*/ proxies.Add (mb.Name); } } } return (proxies.Count == 0) ? null : proxies.ToArray(); } static void gen_struct (IndentedTextWriter tw, StructDef sd) { bool hasDoc = tryFindRefPage (sd.Name, out refPage rp); if (hasDoc) rp.writeSummaryAndRemarks (tw); if (sd.category == TypeCategories.union) tw.WriteLine($"[StructLayout(LayoutKind.Explicit)]"); else tw.WriteLine ($"[StructLayout(LayoutKind.Sequential)]"); string[] ptrProxies = getStructurePtrProxies (sd); if (ptrProxies == null) tw.WriteLine ($"public partial struct {sd.Name} {{"); else tw.WriteLine ($"public partial struct {sd.Name} : IDisposable {{"); tw.Indent++; foreach (MemberDef mb in sd.members) { string typeStr; if (!mb.paramDef.tryGetTypeDef (out TypeDef td)) throw new Exception($"type not found {mb.paramDef}"); if (mb.paramDef.IndirectionLevel == 1){ if (mb.paramDef.Name == "char") typeStr = "Utf8StringPointer"; else if (mb.paramDef.Name != "void" && !csValueTypes.Contains (td.CSName)) { /*if (td.category == TypeCategories.basetype && td.baseType == null) typeStr = "IntPtr"; else {*/ typeStr = $"{td.CSName}"; string structPointerId = mb.len == null ? typeStr : $"{typeStr}+"; if (td.IsEnum) { EnumDef ed = enums.FirstOrDefault (e => e.Name == td.enumDefName); if (ed.type == EnumTypes.@enum) structPointerId += "#";//integer enum else structPointerId += "*";//unsigned integer enum } if (!structurePointerProxies.Contains (structPointerId)) structurePointerProxies.Add (structPointerId); typeStr += "+"; //} } else typeStr = "IntPtr"; } else if (mb.paramDef.IndirectionLevel > 0 || mb.paramDef.Name.StartsWith ("PFN_", StringComparison.Ordinal)) typeStr = "IntPtr"; else if (td.IsEnum) { EnumDef ed = td.enumDef; if (ed == null)//no value for enum typeStr = td.CSName; else { //enums has to be stored in struct as int or uint for enums are not yet blittable in ms .NET typeStr = ed.baseType; tw.WriteLine ($"{typeStr} _{mb.Name};"); if (hasDoc) rp.writeMemberDocIfFound (tw, mb.Name); else if (!string.IsNullOrEmpty (mb.comment)) tw.WriteLine ($"/// {mb.comment} "); tw.WriteLine ($"public {td.CSName} {mb.Name} {{"); tw.Indent++; tw.WriteLine ($"get => ({td.CSName})_{mb.Name};"); tw.WriteLine ($"set {{ _{mb.Name} = ({typeStr})value; }}"); tw.Indent--; tw.WriteLine (@"}"); continue; } } else typeStr = td.CSName; if (hasDoc) rp.writeMemberDocIfFound (tw, mb.Name); else if (!string.IsNullOrEmpty (mb.comment)) tw.WriteLine ($"/// {mb.comment} "); if (sd.category == TypeCategories.union) tw.WriteLine($"[FieldOffset(0)]"); if (typeStr != "IntPtr" && typeStr != "UIntPtr" && !string.IsNullOrEmpty (mb.fixedArray)) { int dim = 0; string[] dims = mb.fixedArray.Split (new char[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries); if (csValueTypes.Contains (typeStr)) { if (mb.paramDef.Name == "char") typeStr = "char"; else if (int.TryParse (dims[0], out dim) && tryGetVectorType (typeStr, dim, out string vectorType)) { tw.WriteLine ($"public {vectorType} {mb.Name};"); continue; } if (dims.Length != 1) throw new NotImplementedException ("multi dimentional array not implemented"); if (int.TryParse (dims[0], out dim)) throw new NotImplementedException ("litteral dimention size not handled in char* arrays"); string strSize = dims[0]; if (arrayProxies.ContainsKey(strSize)) { if (arrayProxies[strSize] != typeStr) throw new NotImplementedException ("same constant used for different array types"); } else arrayProxies.Add (strSize, typeStr); tw.WriteLine ($"public {EnumerantValue.GetArrayProxyStructName (strSize)} {mb.Name};"); continue; } if (dims.Length != 1) throw new NotImplementedException ("multi dimentional array not implemented"); if (int.TryParse (dims[0], out dim)) { for (int i = 0; i < dim; i++) { tw.WriteLine ($"public {typeStr} {mb.Name}_{i};"); } } else { tw.WriteLine ($"[MarshalAs (UnmanagedType.ByValArray, SizeConst = (int)Vk.{constants.FirstOrDefault(e=>e.Name == dims[0]).CSName})]"); tw.WriteLine ($"public {typeStr}[] {mb.Name};"); } continue; } if (typeStr.EndsWith ("+")) { string origTypeStr = typeStr.Substring (0, typeStr.Length - 1).Replace('.','_'); string len = mb.len?.Split (',')[0]; string ptrType = mb.len == null ? origTypeStr + "Ptr" : origTypeStr + "CollectionPtr"; tw.WriteLine ($"public {ptrType} {mb.Name} {{"); tw.Indent++; tw.WriteLine ($"set {{"); tw.Indent++; tw.WriteLine ($"if (_{mb.Name} != IntPtr.Zero)"); tw.Indent++; tw.WriteLine ($"_{mb.Name}.Unpin ();"); tw.Indent--; tw.WriteLine ($"if (value == null) {{"); tw.Indent++; tw.WriteLine ($"_{mb.Name} = IntPtr.Zero;"); if (mb.len != null) tw.WriteLine ($"{len} = 0;"); tw.WriteLine ($"return;"); tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine ($"_{mb.Name} = value.handle;"); if (mb.len != null) { string targetType = sd.members.FirstOrDefault (m=>m.Name == len).paramDef.CSName; if (targetType == "int") tw.WriteLine ($"{len} = value.Count;"); else if (targetType == "byte") tw.WriteLine ($"{len} = Convert.ToByte (value.Count);"); else tw.WriteLine ($"{len} = ({targetType})value.Count;"); } tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine ($"IntPtr _{mb.Name};"); } else tw.WriteLine ($"public {typeStr} {mb.Name};"); } if (sd.members.Any (mb => mb.Name == "sType" && !string.IsNullOrEmpty (mb.defaultValue))) writeStructNew (tw, sd); if (ptrProxies != null) { tw.WriteLine ($"public void Dispose() {{"); tw.Indent++; foreach (string proxy in ptrProxies) { tw.WriteLine ($"if (_{proxy} != IntPtr.Zero)"); tw.Indent++; tw.WriteLine ($"_{proxy}.Unpin ();"); tw.Indent--; } tw.Indent--; tw.WriteLine (@"}"); } tw.Indent--; tw.WriteLine (@"}"); } static void gen_structs (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"structs_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, "System.Runtime.InteropServices", "System.Numerics", "System.Linq", "System.Collections.Generic"); tw.Indent++; foreach (StructDef sd in types.OfType ()) { if (skipGenStruct.Contains (sd.Name)) continue; gen_struct (tw, sd); } tw.Indent--; tw.WriteLine (@"}"); } } } /* credit to https://github.com/mellinoe for the handle structs*/ static void write_empty_enum_as_struct (IndentedTextWriter tw, EnumDef enumType) { tw.WriteLine (@"[DebuggerDisplay(""{DebuggerDisplay,nq}"")]"); tw.WriteLine ($"public struct {enumType.CSName} : IEquatable<{enumType.CSName}>"); tw.WriteLine (@"{"); tw.Indent++; string hType = "uint"; tw.WriteLine ($"public readonly {hType} Value;"); tw.WriteLine ($"public {enumType.CSName}({hType} existingHandle) {{ Value = existingHandle; }}"); tw.WriteLine ($"public static {enumType.CSName} Null => new {enumType.CSName}(0);"); tw.WriteLine ($"public static implicit operator {enumType.CSName}({hType} flags) => new {enumType.CSName}(flags);"); tw.WriteLine ($"public static bool operator ==({enumType.CSName} left, {enumType.CSName} right) => left.Value == right.Value;"); tw.WriteLine ($"public static bool operator !=({enumType.CSName} left, {enumType.CSName} right) => left.Value != right.Value;"); tw.WriteLine ($"public static bool operator ==({enumType.CSName} left, {hType} right) => left.Value == right;"); tw.WriteLine ($"public static bool operator !=({enumType.CSName} left, {hType} right) => left.Value != right;"); tw.WriteLine ($"public bool Equals({enumType.CSName} h) => Value == h.Value;"); tw.WriteLine ($"public override bool Equals(object obj) => obj is {enumType.CSName} h && Equals(h);"); tw.WriteLine ($"public override int GetHashCode() => Value.GetHashCode();"); tw.WriteLine ($"private string DebuggerDisplay => string.Format(\"{enumType.CSName} [0x{{0}}]\", Value.ToString(\"X\"));"); tw.Indent--; tw.WriteLine (@"}"); } static void gen_enum_value (IndentedTextWriter tw, string enumName, EnumerantValue ev, refPage? refpage = null) { if (refpage.HasValue) refpage.Value.writeMemberDocIfFound (tw, ev.Name); else if (!string.IsNullOrEmpty (ev.comment)) tw.WriteLine ($"/// {ev.comment} "); if (!string.IsNullOrEmpty (ev.unusedComment)) tw.WriteLine ($"[Obsolete(\"{ev.unusedComment}\"]"); uint v; string vName = ev.CSName; if (uint.TryParse (ev.value, out v)) tw.WriteLine ($"{vName,-50} = 0x{v.ToString ("X8")},"); else if (ev.isAlias) { if (ev.tryGetAlias (out EnumerantValue aliasEv) && aliasEv.CSName != vName) tw.WriteLine ($"{vName,-50} = {aliasEv.CSName,10},"); else log_vk_net_gen (ConsoleColor.Red, $"enum value not written: {vName,-50} = {aliasEv.CSName,10}"); } else tw.WriteLine ($"{vName,-50} = {ev.value,10},"); } static void gen_enums (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"enums_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, "System.Diagnostics"); tw.Indent++; foreach (EnumDef ed in enums.Where(e=>e.definedBy.Count()==0)) { if (!ed.tryGetTypeDef (out TypeDef td)) { AddAlias (ed.Name, ed.baseType); continue; } bool hasDoc = tryFindRefPage (ed.Name, out refPage rp); if (hasDoc) { rp.writeSummaryAndRemarks (tw); } else if (!string.IsNullOrEmpty (ed?.comment)) tw.WriteLine ($"///{ed.comment}"); if (ed?.type == EnumTypes.bitmask) { tw.WriteLine (@"[Flags]"); tw.WriteLine ($"public enum {td.CSName} : {ed.baseType} {{"); } else tw.WriteLine ($"public enum {td.CSName} : {ed.baseType} {{"); tw.Indent++; foreach (EnumerantValue ev in ed.values.OrderBy(aa => aa.isAlias)) { if (hasDoc) gen_enum_value (tw, td.CSName, ev, rp); else gen_enum_value (tw, td.CSName, ev); } IEnumerable ext = extends.Where (e => e.extends == ed.Name); foreach (EnumerantValue ev in ext.OrderBy(aa => aa.isAlias)) { if (hasDoc) gen_enum_value (tw, td.CSName, ev, rp); else gen_enum_value (tw, td.CSName, ev); } tw.Indent--; tw.WriteLine (@"}"); } tw.Indent--; tw.WriteLine (@"}"); } } } static void gen_extensions () { using (StreamWriter sr = new StreamWriter (vkNetTargetPath ("extensions"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw); tw.Indent++; tw.WriteLine (@"public static class Ext {"); tw.Indent++; tw.WriteLine (@"public static class I {"); tw.Indent++; foreach (ExtensionDef ed in extensions.Where (e => e.type == ExtensionType.Instance)) { tw.WriteLine ($"public static readonly string {ed.Name,-50} = \"{ed.Name}\";"); } tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine (@"public static class D {"); tw.Indent++; foreach (ExtensionDef ed in extensions.Where (e => e.type == ExtensionType.Device)) { tw.WriteLine ($"public static readonly string {ed.Name,-50} = \"{ed.Name}\";"); } tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); } } } static void gen_funcptrs (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter ($"funcptrs_{englobingStaticClass}_gen.cs", false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw); tw.Indent++; foreach (FuncpointerDef fp in types.OfType()) tw.WriteLine ($"public delegate {fp.returnType.CSName} {fp.Name} ();"); tw.Indent--; tw.WriteLine (@"}"); } } } static void gen_handle (IndentedTextWriter tw, TypeDef hd) { if (tryFindRefPage (hd.Name, out refPage rp)) rp.writeSummaryAndRemarks(tw, true); tw.WriteLine (@"[DebuggerDisplay(""{DebuggerDisplay,nq}"")]"); tw.WriteLine ($"public struct {hd.CSName} : IEquatable<{hd.CSName}>"); tw.WriteLine (@"{"); tw.Indent++; string hType = hd.nonDispatchable ? "ulong" : "IntPtr"; tw.WriteLine ($"public readonly {hType} Handle;"); tw.WriteLine ($"public {hd.CSName}({hType} existingHandle) {{ Handle = existingHandle; }}"); tw.WriteLine ($"public static {hd.CSName} Null => new {hd.CSName}({(hd.nonDispatchable ? "0" : "IntPtr.Zero")});"); tw.WriteLine ($"public static implicit operator {hd.CSName}({hType} handle) => new {hd.CSName}(handle);"); tw.WriteLine ($"public static bool operator ==({hd.CSName} left, {hd.CSName} right) => left.Handle == right.Handle;"); tw.WriteLine ($"public static bool operator !=({hd.CSName} left, {hd.CSName} right) => left.Handle != right.Handle;"); tw.WriteLine ($"public static bool operator ==({hd.CSName} left, {hType} right) => left.Handle == right;"); tw.WriteLine ($"public static bool operator !=({hd.CSName} left, {hType} right) => left.Handle != right;"); tw.WriteLine ($"public bool Equals({hd.CSName} h) => Handle == h.Handle;"); tw.WriteLine ($"public override bool Equals(object obj) => obj is {hd.CSName} h && Equals(h);"); tw.WriteLine ($"public override int GetHashCode() => Handle.GetHashCode();"); tw.WriteLine ($"private string DebuggerDisplay => string.Format(\"{hd.CSName} [0x{{0}}]\", Handle.ToString(\"X\"));"); tw.Indent--; tw.WriteLine (@"}"); } static void gen_handles (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"handles_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, "System.Diagnostics"); tw.Indent++; foreach (TypeDef hd in types.Where(t=>t.category == TypeCategories.handle)) gen_handle (tw, hd); tw.Indent--; tw.WriteLine (@"}"); } } } static void gen_constant_value (IndentedTextWriter tw, EnumerantValue cd) { if (string.IsNullOrEmpty (cd.value)) { log_vk_net_gen (ConsoleColor.DarkRed, "no value for constant: " + cd); return; } string pTrims = cd.value.Replace ("(", ""); pTrims = pTrims.Replace (")", ""); int i = pTrims.Length; while (i > 1 && char.IsLetter (pTrims[i-1])) i--; switch (pTrims.Substring(i).ToLower()) { case "u": tw.WriteLine ($"{"public const uint " + cd.CSName,-50} = {cd.value};"); break; case "ull": tw.WriteLine ($"{"public const ulong " + cd.CSName,-50} = ({pTrims.Remove(pTrims.Length-1)});"); break; case "f": tw.WriteLine ($"{"public const float " + cd.CSName,-50} = {cd.value};"); break; default: tw.WriteLine ($"{"public const uint " + cd.CSName,-50} = {cd.value};"); break; } } static void gen_constants (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath($"constants_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw); tw.Indent++; tw.WriteLine ($"public static partial class {englobingStaticClass} {{"); tw.Indent++; foreach (EnumerantValue cd in constants) gen_constant_value (tw, cd); tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); } } } static string getDefaultParamSig (MemberDef md, bool isLast) { switch (md.paramDef.IndirectionLevel) { case 0: if (!md.paramDef.tryGetTypeDef (out TypeDef td)) throw new Exception ($"type not found: {md.paramDef.Name}"); return (string.Format ($"{td.CSName} {md.Name}{(isLast ? ", " : ")")}")); case 1: return (string.Format ($"IntPtr {md.Name}{(isLast ? ", " : ")")}")); case 2: return (string.Format ($"ref IntPtr {md.Name}{(isLast ? ", " : ")")}")); } return null; } static string getByRefParamSig (MemberDef md, bool isLast) { if (!md.paramDef.tryGetTypeDef (out TypeDef td)) throw new Exception ($"type not found: {md.paramDef.Name}"); string typeName = td.CSName; //md.paramDef.CSName == "void" ? "IntPtr" : md.paramDef.CSName; switch (md.paramDef.IndirectionLevel) { case 1: return md.paramDef.IsConst ? (string.Format ($"ref {typeName} {md.Name}{(isLast ? ", " : ")")}")) : (string.Format ($"out {typeName} {md.Name}{(isLast ? ", " : ")")}")); case 2: return md.paramDef.IsConst ? string.Format ($"ref IntPtr {md.Name}{(isLast ? ", " : ")")}") : string.Format ($"out IntPtr {md.Name}{(isLast ? ", " : ")")}"); } return null; } static void writeCmd (IndentedTextWriter tw, string sig) { tw.WriteLine (@"[CalliRewrite]"); tw.Write ($"public static {sig}"); tw.WriteLine (@" => throw new NotImplementedException();"); } static void gen_command (IndentedTextWriter tw, CommandDef cd) { tw.WriteLine ($"internal static IntPtr {cd.CSName}_ptr;"); List signatures = new List () { "" }; for (int i = 0; i < cd.parameters.Count; i++) { MemberDef md = cd.parameters[i]; List typeSigs = new List () { getDefaultParamSig (md, i < cd.parameters.Count - 1) }; if (md.paramDef.IndirectionLevel == 1 && md.paramDef.Name != "void") typeSigs.Add (getByRefParamSig (md, i < cd.parameters.Count - 1)); string[] prevSigs = new string[signatures.Count]; Array.Copy (signatures.ToArray (), prevSigs, prevSigs.Length); signatures.Clear (); for (int s = 0; s < prevSigs.Length; s++) { signatures.Add (prevSigs[s] + typeSigs[0]); for (int st = 1; st < typeSigs.Count; st++) { signatures.Add (prevSigs[s] + typeSigs[st]); } } } if (tryFindRefPage (cd.Name, out refPage rp)) { foreach (string sig in signatures) { rp.writeSummaryAndRemarks(tw); writeCmd (tw, string.Format ($"{cd.returnType.CSName} {cd.Name} ({sig}")); } } else { foreach (string sig in signatures) writeCmd (tw, string.Format ($"{cd.returnType.CSName} {cd.Name} ({sig}")); } tw.WriteLine (); } static void gen_commands (string englobingStaticClass) { using (StreamWriter sr = new StreamWriter (vkNetTargetPath ($"commands_{englobingStaticClass}"), false, System.Text.Encoding.UTF8)) { using (IndentedTextWriter tw = new IndentedTextWriter (sr)) { writePreamble (tw, vknamespace + ".Generator"); tw.Indent++; tw.WriteLine ($"public static partial class {englobingStaticClass} {{"); tw.Indent++; //func pointers and signature foreach (CommandDef cd in commands) gen_command (tw, cd); #region Function pointers loading (os, inst and dev) //norml dyn loading function tw.WriteLine (@"internal static void LoadFunctionPointers() {"); tw.Indent++; foreach (string preloadCmd in preloadedCommands) { tw.WriteLine ($"{preloadCmd}_ptr = Vk.s_nativeLib.LoadFunctionPointer(\"{preloadCmd}\");"); } tw.Indent--; tw.WriteLine (@"}"); //load instance func pointers tw.WriteLine ($"public static void LoadInstanceFunctionPointers (VkInstance inst) {{"); tw.Indent++; foreach (CommandDef cd in commands){//.Where (cmd=>cmd.definedBy.Count == 0)) { if (!string.IsNullOrEmpty (cd.alias)) continue; if (preloadedCommands.Contains (cd.Name)) continue; tw.WriteLine ($"{cd.Name + "_ptr",-54} = LoadingUtils.GetDelegate (inst, \"{cd.Name}\");"); } tw.Indent--; tw.WriteLine (@"}"); tw.WriteLine (); //load device func pointers tw.WriteLine ($"public static void LoadDeviceFunctionPointers (VkDevice dev) {{"); tw.Indent++; foreach (CommandDef cd in commands){//.Where (cmd=>cmd.definedBy.Count == 0)) { if (!string.IsNullOrEmpty (cd.alias)) continue; if (preloadedCommands.Contains (cd.Name)) continue; tw.WriteLine ($"LoadingUtils.GetDelegate (dev, \"{cd.Name + "\",",-54} ref {cd.Name + "_ptr"});"); } tw.Indent--; tw.WriteLine (@"}"); #endregion tw.Indent--; tw.WriteLine (@"}"); tw.Indent--; tw.WriteLine (@"}"); } } } #endregion #region vk.xml parsing static void readType (XmlNode nType) { if (nType.Attributes ["alias"] != null) { AddAlias (nType.Attributes["name"].Value, nType.Attributes["alias"].Value); return; } TypeCategories category = nType.Attributes["category"] == null ? TypeCategories.none : (TypeCategories)Enum.Parse (typeof (TypeCategories), nType.Attributes["category"].Value, true); switch (category) { case TypeCategories.none: case TypeCategories.basetype: case TypeCategories.handle: case TypeCategories.bitmask: case TypeCategories.@enum: case TypeCategories.include: case TypeCategories.define: types.Add (new TypeDef { category = category, Name = nType.Attributes["name"] == null ? nType["name"]?.InnerText : nType.Attributes["name"].Value, baseType = nType["type"]?.InnerXml, requires = nType.Attributes["requires"]?.Value, bitvalues = nType.Attributes["bitvalues"]?.Value, parent = nType.Attributes["parent"]?.Value }); break; case TypeCategories.funcpointer: FuncpointerDef fp = new FuncpointerDef { category = category, Name = nType["name"].InnerText }; string returnTypeStr = nType.InnerText.Substring(8);//trim leading typedef returnTypeStr = returnTypeStr.Remove (returnTypeStr.IndexOf ('(')).Trim(); switch (returnTypeStr) { case "void": fp.returnType = new ParamDef () { Name = "void" }; break; case "void*": fp.returnType = new ParamDef () { Name = "IntPtr" }; break; default: fp.returnType = new ParamDef () { Name = returnTypeStr }; break; } types.Add (fp); break; case TypeCategories.union: case TypeCategories.@struct: StructDef sd = new StructDef { category = category, Name = nType.Attributes["name"].Value, structextends = nType["structextends"]?.Value, }; foreach (XmlNode m in nType.ChildNodes) { if ((m.NodeType != XmlNodeType.Element)) { log_vk_net_gen (ConsoleColor.Red, $"expecting element, having {m.NodeType}: {m.OuterXml}"); continue; } switch (m.Name) { case "comment": break; case "member": sd.members.Add (parseMember (m)); break; default: log_vk_net_gen (ConsoleColor.Red, $"unknown element in struct def : {m.OuterXml}"); break; } } types.Add (sd); break; default: log_vk_net_gen (ConsoleColor.DarkMagenta, $"Unprocessed type: {nType.OuterXml}"); break; } } public static void readEnum (XmlNode nEnum) { if (nEnum.Attributes ["name"]?.Value == "API Constants") { //constants EnumDef constantsED = new EnumDef { Name = "Vk" }; foreach (XmlNode c in nEnum.ChildNodes) { if (c.Name != "enum") throw new Exception ("unexpected element in enums"); if (c.Attributes ["alias"] != null) { AddAlias (c.Attributes["name"].Value, c.Attributes["alias"].Value); continue; } if (c.Attributes["name"].Value =="VK_MAX_DEVICE_GROUP_SIZE_KHR") Debugger.Break(); constants.Add (new EnumerantValue (constantsED){ Name = c.Attributes["name"].Value, value = c.Attributes["value"].Value, comment = c.Attributes["comment"]?.Value }); } return; } EnumDef ed = new EnumDef { type = (EnumTypes)Enum.Parse (typeof (EnumTypes), nEnum.Attributes["type"].Value, true), Name = nEnum.Attributes["name"].Value }; foreach (XmlNode p in nEnum.ChildNodes) { EnumerantValue ev = new EnumerantValue (ed); if (p.Name == "comment" || p.NodeType == XmlNodeType.Comment) { ev.comment = p.InnerXml; continue; } if (p.Name == "unused") { ev.unusedComment = p.InnerXml; continue; } ev.Name = p.Attributes["name"].Value; ev.comment = p.Attributes["comment"]?.Value; ev.extends = p.Attributes["extends"]?.Value; if (p.Attributes["alias"] != null) { ev.isAlias = true; ev.value = p.Attributes["alias"].Value; }else if(ed.type == EnumTypes.bitmask) { if (p.Attributes["bitpos"] != null) ev.value = (1u << int.Parse (p.Attributes["bitpos"].Value)).ToString (); else ev.value = p.Attributes["value"]?.Value; } else ev.value = p.Attributes["value"].Value; ed.values.Add (ev); } enums.Add (ed); } static void parseRequirements (InterfaceDef iface, XmlNode requirements) { if (requirements.Attributes["comment"]?.Value == "API constants") { //CONSTANTS foreach (XmlNode req in requirements.ChildNodes) { switch (req.Name) { case "enum": if (tryGetConstant (req.Attributes["name"].Value, out EnumerantValue ev)) ev.definedBy.Add (iface.Name); else Debugger.Break(); break; default: log_vk_net_gen (ConsoleColor.Red, $"unhandle requirement tag in constants defs: {req.Attributes["name"].Value}"); break; } } return; } foreach (XmlNode req in requirements.ChildNodes) { switch (req.Name) { case "type": TypeDef tdef = types.FirstOrDefault (td => td.Name == resolveAlias (req.Attributes["name"].Value)); if (tdef == null) log_vk_net_gen (ConsoleColor.Red, $"iface type not found: {req.Attributes["name"].Value}"); else tdef.definedBy.Add (iface.Name); break; case "command": //if (preloadedCommands.Contains (req.Attributes["name"].Value))//dont move those func // continue;//TODO:remove this check CommandDef cd = commands.FirstOrDefault (e => e.Name == resolveAlias (req.Attributes["name"].Value)); if (cd == null) log_vk_net_gen (ConsoleColor.Red, $"extension command not found: {req.Attributes["name"].Value}"); else cd.definedBy.Add (iface.Name); break; case "enum": string eName = req.Attributes["name"].Value; EnumerantValue ev = extends.Where (e => e.Name == eName).FirstOrDefault (); if (ev != null) { ev.definedBy.Add (iface.Name); continue; } if (req.Attributes["value"] != null) { extends.Add (new EnumerantValue { Name = eName, definedBy = new List { iface.Name }, extends = req.Attributes["extends"]?.Value, value = req.Attributes["value"].Value }); } else if (req.Attributes["bitpos"] != null) { extends.Add (new EnumerantValue { Name = eName, definedBy = new List { iface.Name }, extends = req.Attributes["extends"]?.Value, value = (1u << int.Parse (req.Attributes["bitpos"].Value)).ToString () }); } else if (req.Attributes["alias"] != null) { extends.Add (new EnumerantValue { Name = eName, definedBy = new List { iface.Name }, isAlias = true, extends = req.Attributes["extends"]?.Value, value = req.Attributes["alias"].Value }); } else if (req.Attributes["offset"] != null) { int offset = 0, extnumber = 0; int.TryParse (req.Attributes["offset"].Value, out offset); if (req.Attributes["extnumber"] != null) int.TryParse (req.Attributes["extnumber"].Value, out extnumber); int eval = extBase; if (extnumber > 0) eval += offset + (extnumber - 1) * extBlockSize; else eval += offset + (iface.number - 1) * extBlockSize; if (req.Attributes["dir"] != null) eval = -eval; extends.Add (new EnumerantValue { Name = eName, definedBy = new List { iface.Name }, extends = req.Attributes["extends"]?.Value, value = eval.ToString () }); } else //constant if (tryGetConstant (eName, out EnumerantValue cst)) { cst.definedBy.Add (iface.Name); if (req.Attributes["extends"] != null) Debugger.Break(); } else log_vk_net_gen (ConsoleColor.Red, $"[requirements] constant not found: {eName}"); break; case "comment": break; default: log_vk_net_gen (ConsoleColor.DarkRed, $"unknown req: {req.OuterXml}"); break; } } } public static string resolveAlias (string name) { string tmp = name; while (Generator.aliases.ContainsKey (tmp)) //recurse in aliases tmp = Generator.aliases[tmp]; return tmp; } static bool tryGetConstant (string cst, out EnumerantValue ev) { ev = constants.FirstOrDefault (c=>c.Name == resolveAlias (cst)); return ev != null; } public static void readExtension (XmlNode nExt) { ExtensionDef ext = new ExtensionDef (); ext.Name = nExt.Attributes["name"].Value; if (nExt.Attributes["number"] != null) ext.number = int.Parse (nExt.Attributes["number"].Value); if (nExt.Attributes["requires"] != null) ext.requires = nExt.Attributes["requires"].Value.Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (nExt.Attributes["supported"] != null) ext.supported = nExt.Attributes["supported"].Value; if (nExt.Attributes["type"] != null) ext.type = (ExtensionType)Enum.Parse (typeof (ExtensionType), nExt.Attributes["type"].Value, true); foreach (XmlNode n in nExt.ChildNodes) { if (n.Name == "require") parseRequirements (ext, n); else if (n.Name == "remove") log_vk_net_gen (ConsoleColor.Blue, $"extension remove: {n.OuterXml}"); else throw new NotImplementedException(); } extensions.Add (ext); } public static void readFeature (XmlNode nFeat) { FeatureDef fd = new FeatureDef (); fd.number = 1; fd.Name = nFeat.Attributes["name"].Value; fd.api = nFeat.Attributes["api"].Value; string[] version = nFeat.Attributes["number"].Value.Split ('.'); fd.major = int.Parse (version[0]); fd.minor = int.Parse (version[1]); fd.comment = nFeat.Attributes["comment"]?.Value; foreach (XmlNode f in nFeat.ChildNodes) { if (f.Name == "require") parseRequirements (fd, f); else if (f.Name == "remove") log_vk_net_gen (ConsoleColor.DarkBlue, $"feature remove: {f.OuterXml}"); else throw new NotImplementedException(); } features.Add (fd); } static MemberDef parseMember (XmlNode np) { MemberDef md = new MemberDef () { paramDef = ParamDef.parse (np) }; md.len = np.Attributes["len"]?.Value; md.defaultValue = np.Attributes["values"]?.Value; md.optional = string.Equals (np.Attributes["optional"]?.Value, "true", StringComparison.OrdinalIgnoreCase); md.externsync = np.Attributes["externsync"] != null; md.Name = np["name"].InnerText; if (reservedNames.Contains (md.Name)) md.Name = "_" + md.Name; XmlNode ns = np["name"].NextSibling; if (ns?.NodeType == XmlNodeType.Text && ns.Value.StartsWith ("[",StringComparison.Ordinal)) { if (np["enum"] != null) { md.fixedArray = np["enum"].InnerXml; } else md.fixedArray = ns.Value; } md.comment = np["comment"]?.InnerText; return md; } public static void readCommand (XmlNode ncmd) { CommandDef cd = new CommandDef (); if (ncmd.Attributes ["alias"] != null) { AddAlias (ncmd.Attributes ["name"].Value, ncmd.Attributes["alias"].Value); return; } cd.successCodes = ncmd.Attributes["successcodes"]?.Value; cd.errorcodes = ncmd.Attributes["errorcodes"]?.Value; XmlNode n = ncmd["proto"]; cd.returnType = ParamDef.parse (n); cd.Name = n["name"].InnerText; n = n.NextSibling; while (n != null) { if (n.Name == "param") cd.parameters.Add (parseMember (n)); else if (n.Name == "implicitexternsyncparams") cd.comment = n.InnerText; n = n.NextSibling; } commands.Add (cd); } #endregion #region VulkanDoc refpages parsing struct refPage { public string bief; public string description; public void AppendDescription (string desc ) { if (!string.IsNullOrEmpty(description)) description += @"\n"; description += desc; } public string cspecInfos; public Dictionary members; public void writeSummaryAndRemarks (IndentedTextWriter tw, bool writecspecAsRemark = false) { tw.WriteLine ($"///{bief}"); if (description != null || (writecspecAsRemark && cspecInfos != null)) { tw.WriteLine ($"///"); if (writecspecAsRemark) { foreach (string s in splitLines (cspecInfos)) tw.WriteLine ($"///{s}"); } foreach (string s in splitLines (description)) tw.WriteLine ($"///{s}"); tw.WriteLine ($"///"); } } public void writeMemberDocIfFound (IndentedTextWriter tw, string memberName) { if (members == null || !members.ContainsKey (memberName)) return; string[] docs = splitLines (members[memberName]).ToArray(); if (docs.Length == 1) tw.WriteLine ($"///{docs[0]}"); else { tw.WriteLine ($"///"); foreach (string d in docs) tw.WriteLine ($"///{d}"); tw.WriteLine ($"///"); } } } System.Numerics.Vector test; static IEnumerable splitLines (string s) { if (string.IsNullOrEmpty (s)) return new List(); List tmp = s.Split (@"\n").ToList(); int start = tmp.FindIndex (0, tmp.Count, l=>!string.IsNullOrEmpty(l)); if (start<0) return new List(); int end = tmp.Count < 2 ? tmp.Count - 1 : tmp.FindLastIndex (tmp.Count - 1, tmp.Count - start, l=>!string.IsNullOrEmpty(l)); if (end < 0) return tmp.GetRange (start, tmp.Count - start); else return tmp.GetRange (start, end + 1 - start); } static Regex linkRx = new Regex (@"[p|s|d]link:([a-zA-Z0-9_:]*)", RegexOptions.Multiline); static Regex flinkRx = new Regex (@"flink:([a-zA-Z0-9_:]*)", RegexOptions.Multiline); static Regex elinkRx = new Regex (@"elink:([a-zA-Z0-9_:]*)", RegexOptions.Multiline); static Regex externalLink = new Regex (@"link:([^\[]*)\[([^\]]*)\]", RegexOptions.Multiline); static Regex nameRx = new Regex (@"([p|f|s|e]name|code):([a-zA-Z0-9_]*)", RegexOptions.Multiline); //static Regex elinkRx = new Regex (@"[p|f|s|e]link:([a-zA-Z0-9]*)", RegexOptions.Multiline); static string docReplacements (string s) { if (string.IsNullOrEmpty (s)) return ""; string tmp = s.Replace ("<", "<"); tmp = tmp.Replace (">", ">"); tmp = tmp.Replace (@"//", @" //"); tmp = linkRx.Replace(tmp, @""); tmp = tmp.Replace ("::pname:", "."); tmp = nameRx.Replace(tmp, @"$2"); tmp = flinkRx.Replace (tmp, $""); Match match = elinkRx.Match (tmp); while (match.Success) { EnumDef ed = enums.FirstOrDefault (e=>e.Name == match.Groups[1].Value); bool isEnumType = false; if (ed == null) { ed = enums.FirstOrDefault (e=>e.values.Any (v=>v.Name == match.Groups[1].Value)); } else isEnumType = true; int offset = match.Index + match.Length; if (ed == null) { log_vk_net_gen (ConsoleColor.Magenta, $"docReplacements: enum not fount: {match.Groups[1].Value}"); } else { string id = null; if (isEnumType) id = ed.CSName; else { EnumerantValue ev = ed.values.FirstOrDefault (v=>v.Name == match.Groups[1].Value); if (ev.ResolvedName == null) log_vk_net_gen (ConsoleColor.Magenta, $"no resolved name for: {match.Groups[1].Value}"); else id = ev.ResolvedName; } if (id != null) { string replace = $""; tmp = elinkRx.Replace (tmp, replace); offset = match.Index + replace.Length; } } match = elinkRx.Match (tmp,offset); } tmp = externalLink.Replace (tmp, @" $2"); return tmp; } static string readRefPageMembers (StreamReader sr, ref refPage refpage, bool isEnum) { refpage.members = new Dictionary(); string memberName = null; string memberDoc = null; Stack conditionsStack = new Stack(); while (!sr.EndOfStream) { string l = sr.ReadLine().Trim(); if (l.StartsWith ("include::{generated}/api")) continue; if (l.StartsWith ("ifdef::")) { conditionsStack.Push (l); continue; } if (l.StartsWith ("endif::")) { if (!conditionsStack.TryPop (out string condition)) log_vk_net_gen (ConsoleColor.Red, $"refpages: empty condition stack: {l}"); } if (l.StartsWith ("== ") || l.StartsWith (".")) { if (memberName != null) { if (refpage.members.ContainsKey (memberName)) refpage.members[memberName] += @"\n--duplicated--\n" + docReplacements (memberDoc); else refpage.members.Add (memberName, docReplacements (memberDoc)); } return l; } if (isEnum && string.IsNullOrEmpty(l)) { if (memberName != null) { refpage.members.Add (memberName, docReplacements (memberDoc)); return l; } continue; } if (l.StartsWith ("* ename:") || l.StartsWith ("* pname:")) { l = l.Substring (8); int nameEndIdx = l.IndexOf (' '); string newMemberName = nameEndIdx < 0 ? l : l.Substring (0,nameEndIdx); string newMemberDoc = nameEndIdx < 0 ? "" : l.Substring (nameEndIdx + 1); if (memberName != null) { if (newMemberName == memberName) { memberDoc += @"\n" + newMemberDoc; continue; } if (refpage.members.ContainsKey (memberName)) refpage.members[memberName] += @"\n--duplicated--\n" + docReplacements (memberDoc); else refpage.members.Add (memberName, docReplacements (memberDoc)); } memberName = newMemberName; memberDoc = newMemberDoc; continue; } memberDoc += @"\n" + l; } return null; } static bool tryFindRefPage (string name, out refPage refpage) { refpage = default; string path = Path.Combine (vkrefsdir, $"{name}.txt"); if (!File.Exists(path)) return false; bool isEnum = true; using (StreamReader sr = new StreamReader (path)) { string l = null, savedLine = null; while(!sr.EndOfStream) { if (savedLine != null) { l = savedLine; savedLine = null; } else l = sr.ReadLine(); if (l.StartsWith ("= ")) { int idx = l.IndexOf ('('); continue; } if (l.StartsWith ("== Name")) { l = sr.ReadLine(); refpage.bief = l.Substring (name.Length + 3); continue; } if (l.StartsWith ("== C Specification")) { while (!sr.EndOfStream) { l = sr.ReadLine(); if (l.StartsWith ("include::{generated}/api")) continue; if (l.StartsWith ("== ") || l.StartsWith (".")) { savedLine = l; break; } if (!string.IsNullOrEmpty(refpage.cspecInfos)) refpage.cspecInfos += @"\n"; refpage.cspecInfos += docReplacements (l); } continue; } if (l.StartsWith ("== Members") || l.StartsWith ("== Parameters")) { isEnum = false; savedLine = readRefPageMembers (sr, ref refpage, isEnum); continue; } if (l.StartsWith ("== Description")) { if (isEnum) { savedLine = readRefPageMembers (sr, ref refpage, isEnum); if (savedLine != null) continue; } while (!sr.EndOfStream) { l = sr.ReadLine(); if (l.StartsWith ("include::{generated}/api")) continue; if (l.StartsWith ("[source,c]")) { if (sr.ReadLine() != "~~~~") throw new Exception("expecting c source start: ~~~~"); refpage.AppendDescription (""); l = sr.ReadLine(); while (l != "~~~~") { refpage.AppendDescription ($"\t{l}"); l = sr.ReadLine(); } refpage.AppendDescription (""); continue; } if (l.StartsWith ("== ") || l.StartsWith (".")) { savedLine = l; break; } refpage.AppendDescription (docReplacements (l)); } continue; } } } return true; } #endregion const string vkrefsdir = "Vulkan-Docs/gen/refpage"; const string vkNetTargetDir = "build/generated/vk_net"; const string vkeTargetDir = "build/generated/vke"; static string vkNetTargetPath (string fileName) => Path.Combine (vkNetTargetDir, $"{fileName}_gen.cs"); static void printError (string errString) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine (errString); Console.ResetColor(); } public static void Main (string[] args) { if(args.Length < 1) { printError ("[GEN] Usage:\n\tvk.generator path/to/vk.xml"); return; } string vkxmlLocal = args[0]; if (!File.Exists(vkxmlLocal)) { printError ($"[GEN] {Path.Combine (Directory.GetCurrentDirectory(),vkxmlLocal)} not found."); return; } if (!Directory.Exists (vkNetTargetDir)) Directory.CreateDirectory (vkNetTargetDir); XmlDocument doc = new XmlDocument(); doc.Load(vkxmlLocal); foreach (XmlNode p in doc.GetElementsByTagName("tags").Item(0)) tags.Add(p.Attributes["name"].Value.ToString()); foreach (XmlNode p in doc.GetElementsByTagName("types").Item(0)) { if (p.Name == "comment") continue; readType(p); } XmlNodeList enumsNL = doc.GetElementsByTagName("enums"); for (int i = 0; i < enumsNL.Count; i++) readEnum(enumsNL.Item(i)); foreach (XmlNode e in doc.GetElementsByTagName("commands").Item(0)) { if (e.Name == "command") readCommand(e); else log_vk_net_gen (ConsoleColor.Red, $"expecting 'command', having '{e.Name}'\n{{e.OuterXml}}"); } foreach (XmlNode e in doc.GetElementsByTagName("extensions").Item(0)) { if (e.Name == "extension") readExtension(e); else log_vk_net_gen (ConsoleColor.Red, $"expecting 'extension', having '{e.Name}'\n{{e.OuterXml}}"); } XmlNodeList featuresNL = doc.GetElementsByTagName("feature"); for (int i = 0; i < featuresNL.Count; i++) readFeature(featuresNL.Item(i)); var tmp = types.Where (t=>t.IsEnum && t.requires == null && t.bitvalues == null && t.enumDef != null); //alias all the flagbits to the base type foreach (TypeDef td in types.Where (t=>t.IsEnum && t.enumDefName != t.Name)) AddAlias (td.enumDefName, td.Name); structTypeEnum = enums.FirstOrDefault (e=>e.Name == "VkStructureType"); gen_constants ("Vk"); gen_enums ("Vk"); gen_structs ("Vk"); gen_commands ("Vk"); gen_handles ("Vk"); //gen_funcptrs ("Vk"); gen_array_proxies ("Vk"); gen_structurePointer_proxies ("Vk"); gen_extensions (); return; } } }