Mercurial > games > semicongine
comparison src/vulkan_api/vulkan_api_generator.nim @ 84:8412f433dc46
fix: tons of errors in wrapper generator, can now compile, extension function not loaded yet it seems
| author | Sam <sam@basx.dev> |
|---|---|
| date | Fri, 24 Feb 2023 01:32:45 +0700 |
| parents | 5e19aead2b61 |
| children | e872cf354110 |
comparison
equal
deleted
inserted
replaced
| 83:5e19aead2b61 | 84:8412f433dc46 |
|---|---|
| 81 parseInt(value) | 81 parseInt(value) |
| 82 func hasAttr(node: XmlNode, attr: string): bool = node.attr(attr) != "" | 82 func hasAttr(node: XmlNode, attr: string): bool = node.attr(attr) != "" |
| 83 func tableSorted(table: Table[int, string]): seq[(int, string)] = | 83 func tableSorted(table: Table[int, string]): seq[(int, string)] = |
| 84 result = toSeq(table.pairs) | 84 result = toSeq(table.pairs) |
| 85 result.sort((a, b) => cmp(a[0], b[0])) | 85 result.sort((a, b) => cmp(a[0], b[0])) |
| 86 func findType(declNode: XmlNode): string = | |
| 87 # examples: | |
| 88 # char** -> cstringArray | |
| 89 # void* -> pointer | |
| 90 # char* -> cstring | |
| 91 # | |
| 92 # int* -> ptr int | |
| 93 # void** -> ptr pointer | |
| 94 # int** -> ptr ptr int | |
| 95 var basetype = "" | |
| 96 var apointer = "" | |
| 97 var arraylen = "" | |
| 98 for child in declNode: | |
| 99 if child.kind == xnText: | |
| 100 if "[" in child.text: | |
| 101 if "[" in child.text and "]" in child.text: | |
| 102 arraylen = child.text.strip(chars={'[', ']'}).replace("][", "*") | |
| 103 else: | |
| 104 arraylen = declNode.child("enum")[0].text | |
| 105 else: | |
| 106 for i in 0 ..< child.text.count('*'): | |
| 107 apointer = apointer & "ptr " | |
| 108 elif child.tag == "type": | |
| 109 basetype = mapType(child[0].text) | |
| 110 if basetype == "void": | |
| 111 if apointer.count("ptr ") > 0: | |
| 112 basetype = "pointer" | |
| 113 apointer = apointer[0 ..< ^4] | |
| 114 elif basetype == "char": | |
| 115 if apointer.count("ptr ") == 1: | |
| 116 basetype = "cstring" | |
| 117 apointer = "" | |
| 118 elif apointer.count("ptr ") == 2: | |
| 119 basetype = "cstringArray" | |
| 120 apointer = "" | |
| 121 elif apointer.count("ptr ") > 2: | |
| 122 basetype = "cstringArray" | |
| 123 apointer = apointer[0 ..< ^8] | |
| 124 | |
| 125 result = &"{apointer}{basetype}" | |
| 126 if arraylen != "": | |
| 127 result = &"array[{arraylen}, {result}]" | |
| 86 | 128 |
| 87 # serializers | 129 # serializers |
| 88 func serializeEnum(node: XmlNode, api: XmlNode): seq[string] = | 130 func serializeEnum(node: XmlNode, api: XmlNode): seq[string] = |
| 89 let name = node.attr("name") | 131 let name = node.attr("name") |
| 90 if name == "": | 132 if name == "": |
| 172 let enumEntry = &" {thename} = {value}" | 214 let enumEntry = &" {thename} = {value}" |
| 173 result.add enumEntry | 215 result.add enumEntry |
| 174 | 216 |
| 175 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) | 217 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) |
| 176 elif node.attr("type") == "bitmask": | 218 elif node.attr("type") == "bitmask": |
| 219 var predefined_enum_sets: seq[string] | |
| 177 for value in node.findAll("enum"): | 220 for value in node.findAll("enum"): |
| 178 if value.hasAttr("bitpos"): | 221 if value.hasAttr("bitpos"): |
| 179 values[smartParseInt(value.attr("bitpos"))] = value.attr("name") | 222 values[smartParseInt(value.attr("bitpos"))] = value.attr("name") |
| 180 elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos | 223 elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos |
| 181 values[smartParseInt(value.attr("value"))] = value.attr("name") | 224 values[smartParseInt(value.attr("value"))] = value.attr("name") |
| 225 elif value.hasAttr("value"): # create a const that has multiple bits set | |
| 226 predefined_enum_sets.add &" {value.attr(\"name\")}* = {value.attr(\"value\")}" | |
| 227 | |
| 182 if values.len > 0: | 228 if values.len > 0: |
| 183 if node.hasAttr("bitwidth"): | 229 if node.hasAttr("bitwidth"): |
| 184 result.add " " & name & "* {.size: " & $(smartParseInt(node.attr("bitwidth")) div 8) & ".} = enum" | 230 result.add " " & name & "* {.size: 8.} = enum" |
| 185 else: | 231 else: |
| 186 result.add " " & name & "* {.size: sizeof(cint).} = enum" | 232 result.add " " & name & "* {.size: sizeof(cint).} = enum" |
| 187 for (bitpos, enumvalue) in tableSorted(values): | 233 for (bitpos, enumvalue) in tableSorted(values): |
| 188 var value = "00000000000000000000000000000000"# makes the bit mask nicely visible | 234 var value = "00000000000000000000000000000000"# makes the bit mask nicely visible |
| 189 if node.hasAttr("bitwidth"): # assumes this is always 64 | 235 if node.hasAttr("bitwidth"): # assumes this is always 64 |
| 190 value = value & value | 236 value = value & value |
| 191 value[^(bitpos + 1)] = '1' | 237 value[^(bitpos + 1)] = '1' |
| 192 let enumEntry = &" {enumvalue} = 0b{value}" | 238 let enumEntry = &" {enumvalue} = 0b{value}" |
| 193 if not (enumEntry in result): # the specs define duplicate entries for backwards compat | 239 if not (enumEntry in result): # the specs define duplicate entries for backwards compat |
| 194 result.add enumEntry | 240 result.add enumEntry |
| 195 let cApiName = name.replace("FlagBits", "Flags") | 241 let cApiName = name.replace("FlagBits", "Flags") |
| 196 if node.hasAttr("bitwidth"): # assumes this is always 64 | 242 if node.hasAttr("bitwidth"): # assuming this attribute is always 64 |
| 197 if values.len > 0: | 243 if values.len > 0: |
| 198 result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | 244 result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = |
| 199 for flag in flags: | 245 for flag in flags: |
| 200 result = {cApiName}(uint64(result) or uint(flag))""" | 246 result = {cApiName}(int64(result) or int64(flag))""" |
| 201 result.add "type" | 247 result.add &"""converter NumberToBitset*(number: {cApiName}): seq[{name}] = |
| 202 else: | 248 for value in {name}.items: |
| 203 if values.len > 0: | 249 if (value.ord and int64(number)) > 0: |
| 204 result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | 250 result.add value""" |
| 205 for flag in flags: | 251 else: |
| 206 result = {cApiName}(uint(result) or uint(flag))""" | 252 if values.len > 0: |
| 207 result.add "type" | 253 result.add &"""func toBits*(flags: openArray[{name}]): {cApiName} = |
| 254 for flag in flags: | |
| 255 result = {cApiName}(uint(result) or uint(flag))""" | |
| 256 result.add &"""func toEnums*(number: {cApiName}): seq[{name}] = | |
| 257 for value in {name}.items: | |
| 258 if (value.ord and cint(number)) > 0: | |
| 259 result.add value""" | |
| 260 if predefined_enum_sets.len > 0: | |
| 261 result.add "const" | |
| 262 result.add predefined_enum_sets | |
| 263 result.add "type" | |
| 264 | |
| 208 | 265 |
| 209 func serializeStruct(node: XmlNode): seq[string] = | 266 func serializeStruct(node: XmlNode): seq[string] = |
| 210 let name = node.attr("name") | 267 let name = node.attr("name") |
| 211 var union = "" | 268 var union = "" |
| 212 if node.attr("category") == "union": | 269 if node.attr("category") == "union": |
| 213 union = "{.union.} " | 270 union = "{.union.} " |
| 214 result.add &" {name}* {union}= object" | 271 result.add &" {name}* {union}= object" |
| 215 for member in node.findAll("member"): | 272 for member in node.findAll("member"): |
| 216 if not member.hasAttr("api") or member.attr("api") == "vulkan": | 273 if not member.hasAttr("api") or member.attr("api") == "vulkan": |
| 217 let fieldname = member.child("name")[0].text.strip(chars={'_'}) | 274 let fieldname = member.child("name")[0].text.strip(chars={'_'}) |
| 218 var fieldtype = member.child("type")[0].text.strip(chars={'_'}) | 275 result.add &" {mapName(fieldname)}*: {findType(member)}" |
| 219 # detect pointers | |
| 220 for child in member: | |
| 221 if child.kind == xnText and child.text.strip() == "*": | |
| 222 fieldtype = &"ptr {mapType(fieldtype)}" | |
| 223 elif child.kind == xnText and child.text.strip() == "* const*": | |
| 224 fieldtype = "cstringArray" | |
| 225 fieldtype = mapType(fieldtype) | |
| 226 # detect arrays | |
| 227 for child in member: | |
| 228 if child.kind == xnText and child.text.endsWith("]"): | |
| 229 var thelen = "" | |
| 230 if "[" in child.text: | |
| 231 thelen = child.text.strip(chars={'[', ']'}).replace("][", "*") | |
| 232 else: | |
| 233 thelen = member.child("enum")[0].text | |
| 234 fieldtype = &"array[{thelen}, {fieldtype}]" | |
| 235 result.add &" {mapName(fieldname)}*: {fieldtype}" | |
| 236 | 276 |
| 237 func serializeFunctiontypes(api: XmlNode): seq[string] = | 277 func serializeFunctiontypes(api: XmlNode): seq[string] = |
| 238 for node in api.findAll("type"): | 278 for node in api.findAll("type"): |
| 239 if node.attr("category") == "funcpointer": | 279 if node.attr("category") == "funcpointer": |
| 240 let name = node[1][0] | 280 let name = node[1][0] |
| 328 name = proto.child("name")[0].text | 368 name = proto.child("name")[0].text |
| 329 var params: seq[string] | 369 var params: seq[string] |
| 330 for param in node: | 370 for param in node: |
| 331 if param.tag == "param" and param.attr("api") in ["", "vulkan"]: | 371 if param.tag == "param" and param.attr("api") in ["", "vulkan"]: |
| 332 let fieldname = param.child("name")[0].text.strip(chars={'_'}) | 372 let fieldname = param.child("name")[0].text.strip(chars={'_'}) |
| 333 var fieldtype = param.child("type")[0].text.strip(chars={'_'}) | 373 params.add &"{mapName(fieldname)}: {findType(param)}" |
| 334 if param[param.len - 2].kind == xnText and param[param.len - 2].text.strip() == "*": | |
| 335 fieldtype = &"ptr {mapType(fieldtype)}" | |
| 336 fieldtype = mapType(fieldtype) | |
| 337 params.add &"{mapName(fieldname)}: {fieldtype}" | |
| 338 let allparams = params.join(", ") | 374 let allparams = params.join(", ") |
| 339 return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}") | 375 return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}") |
| 340 | 376 |
| 341 | 377 |
| 342 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = | 378 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = |
| 376 | 412 |
| 377 var outputFiles = { | 413 var outputFiles = { |
| 378 "basetypes": @[ | 414 "basetypes": @[ |
| 379 "import std/dynlib", | 415 "import std/dynlib", |
| 380 "import std/tables", | 416 "import std/tables", |
| 417 "import std/strutils", | |
| 418 "import std/logging", | |
| 419 "import std/macros", | |
| 420 "import std/private/digitsutils", | |
| 421 "from typetraits import HoleyEnum", | |
| 381 "type", | 422 "type", |
| 382 " VkHandle* = distinct uint", | 423 " VkHandle* = distinct uint", |
| 383 " VkNonDispatchableHandle* = distinct uint", | 424 " VkNonDispatchableHandle* = distinct uint", |
| 384 "when defined(linux):", | 425 "when defined(linux):", |
| 385 " let vulkanLib* = loadLib(\"libvulkan.so.1\")", | 426 " let vulkanLib* = loadLib(\"libvulkan.so.1\")", |
| 402 let value = call | 443 let value = call |
| 403 if value != VK_SUCCESS: | 444 if value != VK_SUCCESS: |
| 404 error "Vulkan error: ", astToStr(call), " returned ", $value | 445 error "Vulkan error: ", astToStr(call), " returned ", $value |
| 405 raise newException(Exception, "Vulkan error: " & astToStr(call) & | 446 raise newException(Exception, "Vulkan error: " & astToStr(call) & |
| 406 " returned " & $value)""", | 447 " returned " & $value)""", |
| 448 """ | |
| 449 # custom enum iteration (for enum values > 2^16) | |
| 450 macro enumFullRange(a: typed): untyped = | |
| 451 newNimNode(nnkBracket).add(a.getType[1][1..^1]) | |
| 452 | |
| 453 iterator items[T: HoleyEnum](E: typedesc[T]): T = | |
| 454 for a in enumFullRange(E): yield a""", | |
| 407 ], | 455 ], |
| 408 "structs": @["type"], | 456 "structs": @["type"], |
| 409 "enums": @["type"], | 457 "enums": @["type"], |
| 410 "commands": @[], | 458 "commands": @[], |
| 411 }.toTable | 459 }.toTable |
| 581 mainout.add "# load global functions immediately" | 629 mainout.add "# load global functions immediately" |
| 582 mainout.add "block globalFunctions:" | 630 mainout.add "block globalFunctions:" |
| 583 mainout.add " let instance = VkInstance(0)" | 631 mainout.add " let instance = VkInstance(0)" |
| 584 for l in GLOBAL_COMMANDS: | 632 for l in GLOBAL_COMMANDS: |
| 585 mainout.add procLoads[l] | 633 mainout.add procLoads[l] |
| 586 writeFile outdir / &"api.nim", mainout.join("\n") | |
| 587 | |
| 588 mainout.add "" | 634 mainout.add "" |
| 589 mainout.add "converter VkBool2NimBool*(a: VkBool32): bool = a > 0" | 635 mainout.add "converter VkBool2NimBool*(a: VkBool32): bool = a > 0" |
| 590 mainout.add "converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)" | 636 mainout.add "converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)" |
| 637 mainout.add "proc `$`*(x: uint32): string {.raises: [].} = addInt(result, x)" | |
| 638 | |
| 639 writeFile outdir / &"api.nim", mainout.join("\n") | |
| 640 | |
| 591 | 641 |
| 592 for filename, filecontent in outputFiles.pairs: | 642 for filename, filecontent in outputFiles.pairs: |
| 593 if filename.startsWith("platform/"): | 643 if filename.startsWith("platform/"): |
| 594 writeFile outdir / &"{filename}.nim", (@[ | 644 writeFile outdir / &"{filename}.nim", (@[ |
| 595 "type" | 645 "type" |
