| 78 | 1 import std/os | 
|  | 2 import std/sugar | 
|  | 3 import std/algorithm | 
|  | 4 import std/strformat | 
|  | 5 import std/strutils | 
|  | 6 import std/sequtils | 
|  | 7 import std/streams | 
|  | 8 import std/tables | 
|  | 9 import httpClient | 
|  | 10 import std/xmlparser | 
|  | 11 import std/xmltree | 
|  | 12 | 
|  | 13 type | 
|  | 14   FileContent = seq[string] | 
|  | 15 | 
|  | 16 const | 
|  | 17   TYPEMAP = { | 
|  | 18     "void": "void", | 
|  | 19     "char": "char", | 
|  | 20     "float": "float32", | 
|  | 21     "double": "float64", | 
|  | 22     "int8_t": "int8", | 
|  | 23     "uint8_t": "uint8", | 
|  | 24     "int16_t": "int16", | 
|  | 25     "uint16_t": "uint16", | 
|  | 26     "int32_t": "int32", | 
|  | 27     "uint32_t": "uint32", | 
|  | 28     "uint64_t": "uint64", | 
|  | 29     "int64_t": "int64", | 
|  | 30     "size_t": "csize_t", | 
|  | 31     "int": "cint", | 
|  | 32     "void*": "pointer", | 
|  | 33     "char*": "cstring", | 
|  | 34     "ptr char": "cstring", | 
|  | 35     "ptr void": "pointer", | 
|  | 36     "VK_DEFINE_HANDLE": "VkHandle", | 
|  | 37     "VK_DEFINE_NON_DISPATCHABLE_HANDLE": "VkNonDispatchableHandle", | 
|  | 38   }.toTable | 
|  | 39   PLATFORM_HEADER_MAP = { | 
| 79 | 40     "X11/Xlib.h": @["xlib", "xlib_xrandr"], | 
|  | 41     "X11/extensions/Xrandr.h": @["xlib_xrandr"], | 
|  | 42     "wayland-client.h": @["wayland"], | 
|  | 43     "windows.h": @["win32"], | 
|  | 44     "xcb/xcb.h": @["xcb"], | 
|  | 45     "directfb.h": @["directfb"], | 
|  | 46     "zircon/types.h": @["fuchsia"], | 
|  | 47     "ggp_c/vulkan_types.h": @["ggp"], | 
|  | 48     "screen/screen.h": @["screen"], | 
|  | 49     "nvscisync.h": @["sci"], | 
|  | 50     "nvscibuf.h": @["sci"], | 
|  | 51     "vk_video/vulkan_video_codec_h264std.h": @["provisional"], | 
|  | 52     "vk_video/vulkan_video_codec_h264std_decode.h": @["provisional"], | 
|  | 53     "vk_video/vulkan_video_codec_h264std_encode.h": @["provisional"], | 
|  | 54     "vk_video/vulkan_video_codec_h265std.h": @["provisional"], | 
|  | 55     "vk_video/vulkan_video_codec_h265std_decode.h": @["provisional"], | 
|  | 56     "vk_video/vulkan_video_codec_h265std_encode.h": @["provisional"], | 
| 78 | 57   }.toTable | 
|  | 58   MAP_KEYWORD = { | 
|  | 59     "object": "theobject", | 
|  | 60     "type": "thetype", | 
|  | 61   }.toTable | 
| 79 | 62   SPECIAL_DEPENDENCIES = { | 
|  | 63     "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline", | 
|  | 64   }.toTable | 
| 78 | 65 | 
|  | 66 # helpers | 
|  | 67 func mapType(typename: string): auto = | 
|  | 68   TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'}) | 
|  | 69 func mapName(thename: string): auto = | 
|  | 70   MAP_KEYWORD.getOrDefault(thename.strip(), thename.strip()).strip(chars={'_'}) | 
|  | 71 func smartParseInt(value: string): int = | 
|  | 72   if value.startsWith("0x"): | 
|  | 73     parseHexInt(value) | 
|  | 74   else: | 
|  | 75     parseInt(value) | 
|  | 76 func hasAttr(node: XmlNode, attr: string): bool = node.attr(attr) != "" | 
|  | 77 func tableSorted(table: Table[int, string]): seq[(int, string)] = | 
|  | 78   result = toSeq(table.pairs) | 
|  | 79   result.sort((a, b) => cmp(a[0], b[0])) | 
|  | 80 | 
|  | 81 # serializers | 
| 80 | 82 func serializeEnum(node: XmlNode, api: XmlNode): seq[string] = | 
| 78 | 83   let name = node.attr("name") | 
|  | 84   if name == "": | 
|  | 85     return result | 
|  | 86 | 
| 80 | 87   var reservedNames: seq[string] | 
|  | 88   for t in api.findAll("type"): | 
|  | 89     reservedNames.add t.attr("name").replace("_", "").toLower() | 
|  | 90 | 
| 78 | 91   # find additional enum defintion in feature definitions | 
|  | 92   var values: Table[int, string] | 
| 80 | 93   for feature in api.findAll("feature"): | 
| 78 | 94     for require in feature.findAll("require"): | 
|  | 95       for theenum in require.findAll("enum"): | 
|  | 96         if theenum.attr("extends") == name: | 
|  | 97           if theenum.hasAttr("offset"): | 
|  | 98             let enumBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | 
|  | 99             var value = smartParseInt(theenum.attr("offset")) + enumBase | 
|  | 100             if theenum.attr("dir") == "-": | 
|  | 101               value = -value | 
|  | 102             values[value] = theenum.attr("name") | 
|  | 103           elif theenum.hasAttr("value"): | 
|  | 104             var value = smartParseInt(theenum.attr("value")) | 
|  | 105             if theenum.attr("dir") == "-": | 
|  | 106               value = -value | 
|  | 107             values[value] = theenum.attr("name") | 
|  | 108           elif theenum.hasAttr("bitpos"): | 
|  | 109             var value = smartParseInt(theenum.attr("bitpos")) | 
|  | 110             if theenum.attr("dir") == "-": | 
|  | 111               value = -value | 
|  | 112             values[value] = theenum.attr("name") | 
|  | 113           elif theenum.hasAttr("alias"): | 
|  | 114             discard | 
|  | 115           else: | 
|  | 116             raise newException(Exception, &"Unknown extension value: {feature}\nvalue:{theenum}") | 
|  | 117   # find additional enum defintion in extension definitions | 
| 80 | 118   for extension in api.findAll("extension"): | 
| 78 | 119     let extensionNumber = parseInt(extension.attr("number")) | 
|  | 120     let enumBase = 1000000000 + (extensionNumber - 1) * 1000 | 
|  | 121     for require in extension.findAll("require"): | 
|  | 122       for theenum in require.findAll("enum"): | 
|  | 123         if theenum.attr("extends") == name: | 
|  | 124           if theenum.hasAttr("offset"): | 
|  | 125             if theenum.hasAttr("extnumber"): | 
|  | 126               let otherBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | 
|  | 127               var value = smartParseInt(theenum.attr("offset")) + otherBase | 
|  | 128               if theenum.attr("dir") == "-": | 
|  | 129                 value = -value | 
|  | 130               values[value] = theenum.attr("name") | 
|  | 131             else: | 
|  | 132               var value = smartParseInt(theenum.attr("offset")) + enumBase | 
|  | 133               if theenum.attr("dir") == "-": | 
|  | 134                 value = -value | 
|  | 135               values[value] = theenum.attr("name") | 
|  | 136           elif theenum.hasAttr("value"): | 
|  | 137             var value = smartParseInt(theenum.attr("value")) | 
|  | 138             if theenum.attr("dir") == "-": | 
|  | 139               value = -value | 
|  | 140             values[value] = theenum.attr("name") | 
|  | 141           elif theenum.hasAttr("bitpos"): | 
|  | 142             var value = smartParseInt(theenum.attr("bitpos")) | 
|  | 143             if theenum.attr("dir") == "-": | 
|  | 144               value = -value | 
|  | 145             values[value] = theenum.attr("name") | 
|  | 146           elif theenum.hasAttr("alias"): | 
|  | 147             discard | 
|  | 148           else: | 
|  | 149             raise newException(Exception, &"Unknown extension value: {extension}\nvalue:{theenum}") | 
|  | 150 | 
|  | 151   # generate enums | 
|  | 152   if node.attr("type") == "enum": | 
|  | 153     for value in node.findAll("enum"): | 
|  | 154       if value.hasAttr("alias"): | 
|  | 155         continue | 
|  | 156       if value.attr("value").startsWith("0x"): | 
|  | 157         values[parseHexInt(value.attr("value"))] = value.attr("name") | 
|  | 158       else: | 
|  | 159         values[smartParseInt(value.attr("value"))] = value.attr("name") | 
|  | 160     if values.len > 0: | 
|  | 161       result.add "  " & name & "* {.size: sizeof(cint).} = enum" | 
|  | 162       for (value, name) in tableSorted(values): | 
| 80 | 163         var thename = name | 
|  | 164         if name.replace("_", "").toLower() in reservedNames: | 
|  | 165           thename = thename & "_ENUM" | 
|  | 166         let enumEntry = &"    {thename} = {value}" | 
| 78 | 167         result.add enumEntry | 
|  | 168 | 
|  | 169   # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) | 
|  | 170   elif node.attr("type") == "bitmask": | 
|  | 171     for value in node.findAll("enum"): | 
| 79 | 172       if value.hasAttr("bitpos"): | 
|  | 173         values[smartParseInt(value.attr("bitpos"))] = value.attr("name") | 
|  | 174       elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos | 
|  | 175         values[smartParseInt(value.attr("value"))] = value.attr("name") | 
| 78 | 176     if values.len > 0: | 
|  | 177       if node.hasAttr("bitwidth"): | 
|  | 178         result.add "  " & name & "* {.size: " & $(smartParseInt(node.attr("bitwidth")) div 8) & ".} = enum" | 
|  | 179       else: | 
|  | 180         result.add "  " & name & "* {.size: sizeof(cint).} = enum" | 
|  | 181       for (bitpos, enumvalue) in tableSorted(values): | 
|  | 182         var value = "00000000000000000000000000000000"# makes the bit mask nicely visible | 
|  | 183         if node.hasAttr("bitwidth"): # assumes this is always 64 | 
|  | 184           value = value & value | 
|  | 185         value[^(bitpos + 1)] = '1' | 
|  | 186         let enumEntry = &"    {enumvalue} = 0b{value}" | 
|  | 187         if not (enumEntry in result): # the specs define duplicate entries for backwards compat | 
|  | 188           result.add enumEntry | 
|  | 189     let cApiName = name.replace("FlagBits", "Flags") | 
|  | 190     if node.hasAttr("bitwidth"): # assumes this is always 64 | 
|  | 191       if values.len > 0: | 
|  | 192         result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | 
|  | 193   for flag in flags: | 
|  | 194     result = {cApiName}(uint64(result) or uint(flag))""" | 
|  | 195         result.add "type" | 
|  | 196     else: | 
|  | 197       if values.len > 0: | 
|  | 198         result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | 
|  | 199   for flag in flags: | 
|  | 200     result = {cApiName}(uint(result) or uint(flag))""" | 
|  | 201         result.add "type" | 
|  | 202 | 
| 80 | 203 func serializeStruct(node: XmlNode): seq[string] = | 
| 78 | 204   let name = node.attr("name") | 
|  | 205   var union = "" | 
|  | 206   if node.attr("category") == "union": | 
| 79 | 207     union = "{.union.} " | 
|  | 208   result.add &"  {name}* {union}= object" | 
| 78 | 209   for member in node.findAll("member"): | 
|  | 210     if not member.hasAttr("api") or member.attr("api") == "vulkan": | 
|  | 211       let fieldname = member.child("name")[0].text.strip(chars={'_'}) | 
|  | 212       var fieldtype = member.child("type")[0].text.strip(chars={'_'}) | 
|  | 213       if member[member.len - 2].kind == xnText and member[member.len - 2].text.strip() == "*": | 
|  | 214         fieldtype = &"ptr {mapType(fieldtype)}" | 
|  | 215       fieldtype = mapType(fieldtype) | 
|  | 216       result.add &"    {mapName(fieldname)}*: {fieldtype}" | 
|  | 217 | 
|  | 218 func serializeFunctiontypes(api: XmlNode): seq[string] = | 
|  | 219   for node in api.findAll("type"): | 
|  | 220     if node.attr("category") == "funcpointer": | 
|  | 221       let name = node[1][0] | 
|  | 222       let returntype = mapType(node[0].text[8 .. ^1].split('(', 1)[0]) | 
|  | 223       var params: seq[string] | 
|  | 224       for i in countup(3, node.len - 1, 2): | 
|  | 225         var paramname = node[i + 1].text.split(',', 1)[0].split(')', 1)[0] | 
|  | 226         var paramtype = node[i][0].text | 
|  | 227         if paramname[0] == '*': | 
|  | 228           paramname = paramname.rsplit(" ", 1)[1] | 
|  | 229           paramtype = "ptr " & paramtype | 
|  | 230         paramname = mapName(paramname) | 
|  | 231         params.add &"{paramname}: {mapType(paramtype)}" | 
|  | 232       let paramsstr = params.join(", ") | 
| 79 | 233       result.add(&"  {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}") | 
| 78 | 234 | 
| 79 | 235 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] = | 
| 78 | 236   if node.attrsLen == 0: | 
|  | 237     return | 
|  | 238   if node.attr("requires") == "vk_platform" or node.attr("category") == "include": | 
|  | 239     return | 
|  | 240   result["basetypes"] = @[] | 
|  | 241   result["enums"] = @[] | 
|  | 242 | 
|  | 243   # include-defined types (in platform headers) | 
| 79 | 244   if node.attr("name") in headerTypes: | 
|  | 245     for platform in PLATFORM_HEADER_MAP[node.attr("requires")]: | 
|  | 246       let platformfile = "platform/" & platform | 
|  | 247       if not result.hasKey(platformfile): | 
|  | 248         result[platformfile] = @[] | 
|  | 249       result[platformfile].add "  " & node.attr("name").strip(chars={'_'}) & " {.header: \"" & node.attr("requires") & "\".} = object" | 
| 78 | 250   # generic base types | 
|  | 251   elif node.attr("category") == "basetype": | 
|  | 252     let typechild = node.child("type") | 
|  | 253     let namechild = node.child("name") | 
|  | 254     if typechild != nil and namechild != nil: | 
|  | 255       var typename = typechild[0].text | 
|  | 256       if node[2].kind == xnText and node[2].text.strip() == "*": | 
|  | 257         typename = &"ptr {typename}" | 
|  | 258       result["basetypes"].add &"  {namechild[0].text}* = {mapType(typename)}" | 
|  | 259     elif namechild != nil: | 
|  | 260       result["basetypes"].add &"  {namechild[0].text}* = object" | 
|  | 261   # function pointers need to be handled with structs | 
|  | 262   elif node.attr("category") == "funcpointer": | 
|  | 263     discard | 
|  | 264   # preprocessor defines, ignored | 
|  | 265   elif node.attr("category") == "define": | 
|  | 266     discard | 
|  | 267   # bitmask aliases | 
|  | 268   elif node.attr("category") == "bitmask": | 
|  | 269     if node.hasAttr("alias"): | 
|  | 270       let name = node.attr("name") | 
|  | 271       let alias = node.attr("alias") | 
|  | 272       result["enums"].add &"  {name}* = {alias}" | 
|  | 273   # distinct resource ID types aka handles | 
|  | 274   elif node.attr("category") == "handle": | 
|  | 275     if not node.hasAttr("alias"): | 
|  | 276       let name = node.child("name")[0].text | 
|  | 277       var thetype = mapType(node.child("type")[0].text) | 
|  | 278       result["basetypes"].add &"  {name}* = distinct {thetype}" | 
|  | 279   # enum aliases | 
|  | 280   elif node.attr("category") == "enum": | 
|  | 281     if node.hasAttr("alias"): | 
|  | 282       let name = node.attr("name") | 
|  | 283       let alias = node.attr("alias") | 
|  | 284       result["enums"].add &"  {name}* = {alias}" | 
|  | 285   else: | 
|  | 286     discard | 
|  | 287 | 
| 79 | 288 func serializeCommand(node: XmlNode): (string, string) = | 
|  | 289   let | 
|  | 290     proto = node.child("proto") | 
|  | 291     resulttype = mapType(proto.child("type")[0].text) | 
|  | 292     name = proto.child("name")[0].text | 
|  | 293   var params: seq[string] | 
|  | 294   for param in node: | 
|  | 295     if param.tag == "param" and param.attr("api") in ["", "vulkan"]: | 
|  | 296       let fieldname = param.child("name")[0].text.strip(chars={'_'}) | 
|  | 297       var fieldtype = param.child("type")[0].text.strip(chars={'_'}) | 
|  | 298       if param[param.len - 2].kind == xnText and param[param.len - 2].text.strip() == "*": | 
|  | 299         fieldtype = &"ptr {mapType(fieldtype)}" | 
|  | 300       fieldtype = mapType(fieldtype) | 
|  | 301       params.add &"{mapName(fieldname)}: {fieldtype}" | 
|  | 302   let allparams = params.join(", ") | 
|  | 303   return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}") | 
|  | 304 | 
| 78 | 305 | 
|  | 306 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = | 
|  | 307   for k, v in b.pairs: | 
|  | 308     if not a.hasKey(k): | 
|  | 309       a[k] = @[] | 
|  | 310     a[k].add v | 
|  | 311 | 
|  | 312 | 
|  | 313 proc main() = | 
|  | 314   if not os.fileExists("vk.xml"): | 
|  | 315     let client = newHttpClient() | 
|  | 316     let glUrl = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml" | 
|  | 317     client.downloadFile(glUrl, "vk.xml") | 
|  | 318 | 
|  | 319   let api = loadXml("vk.xml") | 
|  | 320 | 
|  | 321   const outdir = "src/vulkan_api/output" | 
|  | 322   removeDir outdir | 
|  | 323   createDir outdir | 
|  | 324   createDir outdir / "platform" | 
|  | 325 | 
|  | 326   # index all names that are only available on certain platforms | 
|  | 327   var platformTypes: Table[string, string] | 
|  | 328   for extension in api.findAll("extension"): | 
|  | 329     if extension.hasAttr("platform"): | 
|  | 330       for thetype in extension.findAll("type"): | 
|  | 331         platformTypes[thetype.attr("name")] = extension.attr("platform") | 
|  | 332       for command in extension.findAll("command"): | 
|  | 333         platformTypes[command.attr("name")] = extension.attr("platform") | 
|  | 334     elif extension.attr("name").startsWith("VK_KHR_video"): | 
|  | 335       for thetype in extension.findAll("type"): | 
| 79 | 336         platformTypes[thetype.attr("name")] = "provisional" | 
| 78 | 337       for command in extension.findAll("command"): | 
| 79 | 338         platformTypes[command.attr("name")] = "provisional" | 
| 78 | 339 | 
|  | 340   var outputFiles = { | 
| 79 | 341     "basetypes": @[ | 
|  | 342       "import std/dynlib", | 
|  | 343       "type", | 
|  | 344       "  VkHandle* = distinct pointer", | 
|  | 345       "  VkNonDispatchableHandle* = distinct pointer", | 
|  | 346       "when defined(linux):", | 
|  | 347       "  let vulkanLib* = loadLib(\"libvulkan.so.1\")", | 
|  | 348       "when defined(windows):", | 
|  | 349       "  let vulkanLib* = loadLib(\"vulkan-1.dll\")", | 
|  | 350       "if vulkanLib == nil:", | 
|  | 351       "  raise newException(Exception, \"Unable to load vulkan library\")", | 
| 80 | 352       "func VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =", | 
|  | 353       "  (variant shl 29) or (major shl 22) or (minor shl 12) or patch", | 
|  | 354       "", | 
| 79 | 355       "type", | 
|  | 356     ], | 
|  | 357     "structs": @["type"], | 
|  | 358     "enums": @["type"], | 
|  | 359     "commands": @[], | 
| 78 | 360   }.toTable | 
|  | 361 | 
|  | 362   # enums | 
|  | 363   for thetype in api.findAll("type"): | 
|  | 364     if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"): | 
|  | 365       let name = thetype.child("name")[0].text | 
|  | 366       outputFiles["enums"].add &"  {name}* = distinct VkFlags" | 
|  | 367   for theenum in api.findAll("enums"): | 
|  | 368     outputFiles["enums"].add serializeEnum(theenum, api) | 
|  | 369 | 
|  | 370   # structs and function types need to be in same "type" block to avoid forward-declarations | 
|  | 371   outputFiles["structs"].add serializeFunctiontypes(api) | 
|  | 372   for thetype in api.findAll("type"): | 
|  | 373     if thetype.attr("category") == "struct" or thetype.attr("category") == "union": | 
|  | 374       var outfile = "structs" | 
|  | 375       if thetype.attr("name") in platformTypes: | 
|  | 376         outfile = "platform/" & platformTypes[thetype.attr("name")] | 
|  | 377       if not (outfile in outputFiles): | 
|  | 378         outputFiles[outfile] = @[] | 
| 80 | 379       outputFiles[outfile].add serializeStruct(thetype) | 
| 78 | 380 | 
|  | 381   # types | 
| 79 | 382   var headerTypes: Table[string, string] | 
|  | 383   for types in api.findAll("types"): | 
|  | 384     for thetype in types.findAll("type"): | 
|  | 385       if thetype.attrsLen == 2 and thetype.hasAttr("requires") and thetype.hasAttr("name") and thetype.attr("requires") != "vk_platform": | 
|  | 386         let name = thetype.attr("name") | 
|  | 387         let incld = thetype.attr("requires") | 
|  | 388         headerTypes[name] = &"{name} {{.header: \"{incld}\".}} = object" | 
|  | 389 | 
| 78 | 390   for typesgroup in api.findAll("types"): | 
|  | 391     for thetype in typesgroup.findAll("type"): | 
| 79 | 392       outputFiles.update serializeType(thetype, headerTypes) | 
|  | 393 | 
|  | 394   # commands aka functions | 
|  | 395   var varDecls: Table[string, string] | 
|  | 396   var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs | 
|  | 397   for commands in api.findAll("commands"): | 
|  | 398     for command in commands.findAll("command"): | 
|  | 399       if command.attr("api") != "vulkansc": | 
|  | 400         if command.hasAttr("alias"): | 
|  | 401           let name = command.attr("name") | 
|  | 402           let alias = command.attr("alias") | 
|  | 403           let thetype = varDecls[alias].split(":", 1)[1].strip() | 
|  | 404           varDecls[name] = &"  {name}*: {thetype}" | 
|  | 405           procLoads[name] = &"  {name} = {alias}" | 
|  | 406         else: | 
|  | 407           let (name, thetype) = serializeCommand(command) | 
|  | 408           varDecls[name] = &"  {name}*: {thetype}" | 
|  | 409           procLoads[name] = &"  {name} = cast[{thetype}](checkedSymAddr(vulkanLib, \"{name}\"))" | 
|  | 410   var declared: seq[string] | 
|  | 411   var featureloads: seq[string] | 
|  | 412   for feature in api.findAll("feature"): | 
|  | 413     if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]: | 
|  | 414       let name = feature.attr("name") | 
|  | 415       outputFiles["commands"].add &"# feature {name}" | 
|  | 416       outputFiles["commands"].add "var" | 
|  | 417       for command in feature.findAll("command"): | 
|  | 418         if not (command.attr("name") in declared): | 
|  | 419           outputFiles["commands"].add varDecls[command.attr("name")] | 
|  | 420           declared.add command.attr("name") | 
|  | 421       featureloads.add &"load{name}" | 
|  | 422       outputFiles["commands"].add &"proc load{name}*() =" | 
|  | 423       for command in feature.findAll("command"): | 
|  | 424         outputFiles["commands"].add procLoads[command.attr("name")] | 
|  | 425     outputFiles["commands"].add "" | 
| 80 | 426   outputFiles["commands"].add ["proc initVulkan*() ="] | 
| 79 | 427   for l in featureloads: | 
|  | 428     outputFiles["commands"].add [&"  {l}()"] | 
|  | 429   outputFiles["commands"].add "" | 
|  | 430 | 
|  | 431   # for promoted extensions, dependants need to call the load-function of the promoted feature/extension | 
|  | 432   # use table to store promotions | 
|  | 433   var promotions: Table[string, string] | 
|  | 434   for extensions in api.findAll("extensions"): | 
|  | 435     for extension in extensions.findAll("extension"): | 
|  | 436       if extension.hasAttr("promotedto"): | 
|  | 437         promotions[extension.attr("name")] = extension.attr("promotedto") | 
|  | 438 | 
|  | 439   var extensionDependencies: Table[string, (seq[string], XmlNode)] | 
|  | 440   var features: seq[string] | 
|  | 441   for feature in api.findAll("feature"): | 
|  | 442     features.add feature.attr("name") | 
|  | 443   for extensions in api.findAll("extensions"): | 
|  | 444     for extension in extensions.findAll("extension"): | 
|  | 445       let name = extension.attr("name") | 
|  | 446       extensionDependencies[name] = (@[], extension) | 
|  | 447       if extension.hasAttr("depends"): | 
|  | 448         extensionDependencies[name] = (extension.attr("depends").split("+"), extension) | 
|  | 449         if extension.attr("depends").startsWith("("): # no need for full tree parser, only single place where we can use a feature | 
|  | 450           let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+") | 
|  | 451           extensionDependencies[name] = (dependencies, extension) | 
|  | 452       if name in SPECIAL_DEPENDENCIES: | 
|  | 453         extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name] | 
|  | 454 | 
|  | 455   var dependencyOrderedExtensions: OrderedTable[string, XmlNode] | 
|  | 456   while extensionDependencies.len > 0: | 
|  | 457     var delkeys: seq[string] | 
|  | 458     for extensionName, (dependencies, extension) in extensionDependencies.pairs: | 
|  | 459       var missingExtension = false | 
|  | 460       for dep in dependencies: | 
|  | 461         let realdep = promotions.getOrDefault(dep, dep) | 
|  | 462         if not (realdep in dependencyOrderedExtensions) and not (realdep in features): | 
|  | 463           missingExtension = true | 
|  | 464           break | 
|  | 465       if not missingExtension: | 
|  | 466         dependencyOrderedExtensions[extensionName] = extension | 
|  | 467         delkeys.add extensionName | 
|  | 468     for key in delkeys: | 
|  | 469       extensionDependencies.del key | 
|  | 470 | 
|  | 471   for extension in dependencyOrderedExtensions.values: | 
|  | 472     if extension.hasAttr("promotedto"): # will be loaded in promoted place | 
|  | 473       continue | 
|  | 474     if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]: | 
|  | 475       var file = "commands" | 
|  | 476       if extension.attr("platform") != "": | 
|  | 477         file = "platform/" & extension.attr("platform") | 
|  | 478       elif extension.attr("name").startsWith("VK_KHR_video"): # hack since we do not include video headers by default | 
|  | 479         file = "platform/provisional" | 
|  | 480       let name = extension.attr("name") | 
|  | 481       if extension.findAll("command").len > 0: | 
|  | 482         outputFiles[file].add &"# extension {name}" | 
|  | 483         outputFiles[file].add "var" | 
|  | 484         for command in extension.findAll("command"): | 
|  | 485           if not (command.attr("name") in declared): | 
|  | 486             outputFiles[file].add varDecls[command.attr("name")] | 
|  | 487             declared.add command.attr("name") | 
|  | 488       outputFiles[file].add &"proc load{name}*() =" | 
|  | 489       var addedFunctionBody = false | 
|  | 490       if extension.hasAttr("depends"): | 
|  | 491         for dependency in extension.attr("depends").split("+"): | 
|  | 492           # need to check since some extensions have no commands and therefore no load-function | 
|  | 493           outputFiles[file].add &"  load{promotions.getOrDefault(dependency, dependency)}()" | 
|  | 494           addedFunctionBody = true | 
|  | 495       for command in extension.findAll("command"): | 
|  | 496         outputFiles[file].add procLoads[command.attr("name")] | 
|  | 497         addedFunctionBody = true | 
|  | 498       if not addedFunctionBody: | 
|  | 499         outputFiles[file].add "  discard" | 
|  | 500       outputFiles[file].add "" | 
|  | 501 | 
|  | 502   var mainout: seq[string] | 
|  | 503   for section in ["basetypes", "enums", "structs", "commands"]: | 
|  | 504     mainout.add outputFiles[section] | 
| 80 | 505   for platform in api.findAll("platform"): | 
|  | 506     mainout.add &"when defined({platform.attr(\"protect\")}):" | 
|  | 507     mainout.add &"  include platform/{platform.attr(\"name\")}" | 
| 79 | 508   writeFile outdir / &"types.nim", mainout.join("\n") | 
|  | 509 | 
| 78 | 510   for filename, filecontent in outputFiles.pairs: | 
| 79 | 511     if filename.startsWith("platform/"): | 
|  | 512       writeFile outdir / &"{filename}.nim", (@[ | 
|  | 513       "type" | 
|  | 514       ] & filecontent).join("\n") | 
| 78 | 515 | 
|  | 516 when isMainModule: | 
|  | 517   main() |