comparison src/vulkan_api/vulkan_api_generator.nim @ 83:5e19aead2b61

add: new vulkan api wrapper, not done yet
author Sam <sam@basx.dev>
date Thu, 23 Feb 2023 00:34:38 +0700
parents fa1b6107deae
children 8412f433dc46
comparison
equal deleted inserted replaced
82:9e7937b7bcc8 83:5e19aead2b61
57 "type": "thetype", 57 "type": "thetype",
58 }.toTable 58 }.toTable
59 SPECIAL_DEPENDENCIES = { 59 SPECIAL_DEPENDENCIES = {
60 "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline", 60 "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline",
61 }.toTable 61 }.toTable
62 # will be directly loaded at startup
63 IGNORED_COMMANDS = @["vkGetInstanceProcAddr"]
64 # can be loaded without a vulkan instance
65 GLOBAL_COMMANDS = @[
66 "vkEnumerateInstanceVersion",
67 "vkEnumerateInstanceExtensionProperties",
68 "vkEnumerateInstanceLayerProperties",
69 "vkCreateInstance",
70 ]
62 71
63 # helpers 72 # helpers
64 func mapType(typename: string): auto = 73 func mapType(typename: string): auto =
65 TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'}) 74 TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'})
66 func mapName(thename: string): auto = 75 func mapName(thename: string): auto =
205 result.add &" {name}* {union}= object" 214 result.add &" {name}* {union}= object"
206 for member in node.findAll("member"): 215 for member in node.findAll("member"):
207 if not member.hasAttr("api") or member.attr("api") == "vulkan": 216 if not member.hasAttr("api") or member.attr("api") == "vulkan":
208 let fieldname = member.child("name")[0].text.strip(chars={'_'}) 217 let fieldname = member.child("name")[0].text.strip(chars={'_'})
209 var fieldtype = member.child("type")[0].text.strip(chars={'_'}) 218 var fieldtype = member.child("type")[0].text.strip(chars={'_'})
210 if member[member.len - 2].kind == xnText and member[member.len - 2].text.strip() == "*": 219 # detect pointers
211 fieldtype = &"ptr {mapType(fieldtype)}" 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"
212 fieldtype = mapType(fieldtype) 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}]"
213 result.add &" {mapName(fieldname)}*: {fieldtype}" 235 result.add &" {mapName(fieldname)}*: {fieldtype}"
214 236
215 func serializeFunctiontypes(api: XmlNode): seq[string] = 237 func serializeFunctiontypes(api: XmlNode): seq[string] =
216 for node in api.findAll("type"): 238 for node in api.findAll("type"):
217 if node.attr("category") == "funcpointer": 239 if node.attr("category") == "funcpointer":
227 paramname = mapName(paramname) 249 paramname = mapName(paramname)
228 params.add &"{paramname}: {mapType(paramtype)}" 250 params.add &"{paramname}: {mapType(paramtype)}"
229 let paramsstr = params.join(", ") 251 let paramsstr = params.join(", ")
230 result.add(&" {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}") 252 result.add(&" {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}")
231 253
254 func serializeConsts(api: XmlNode): seq[string] =
255 result = @["const"]
256 for enums in api.findAll("enums"):
257 if enums.attr("name") == "API Constants":
258 for theenum in enums.findAll("enum"):
259 if theenum.hasAttr("alias"):
260 result.add &" {theenum.attr(\"name\")}* = {theenum.attr(\"alias\")}"
261 else:
262 var value = theenum.attr("value").strip(chars={'(', ')'})
263 if value.endsWith("U"):
264 value = value[0..^2] & "'u32"
265 elif value.endsWith("ULL"):
266 value = value[0..^4] & "'u64"
267 if value[0] == '~':
268 value = "not " & value[1..^1]
269 result.add &" {theenum.attr(\"name\")}*: {mapType(theenum.attr(\"type\"))} = {value}"
270
232 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] = 271 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] =
233 if node.attrsLen == 0: 272 if node.attrsLen == 0:
234 return 273 return
235 if node.attr("requires") == "vk_platform" or node.attr("category") == "include": 274 if node.attr("requires") == "vk_platform" or node.attr("category") == "include":
236 return 275 return
241 if node.attr("name") in headerTypes: 280 if node.attr("name") in headerTypes:
242 for platform in PLATFORM_HEADER_MAP[node.attr("requires")]: 281 for platform in PLATFORM_HEADER_MAP[node.attr("requires")]:
243 let platformfile = "platform/" & platform 282 let platformfile = "platform/" & platform
244 if not result.hasKey(platformfile): 283 if not result.hasKey(platformfile):
245 result[platformfile] = @[] 284 result[platformfile] = @[]
246 result[platformfile].add " " & node.attr("name").strip(chars={'_'}) & " {.header: \"" & node.attr("requires") & "\".} = object" 285 result[platformfile].add " " & node.attr("name").strip(chars={'_'}) & " *{.header: \"" & node.attr("requires") & "\".} = object"
247 # generic base types 286 # generic base types
248 elif node.attr("category") == "basetype": 287 elif node.attr("category") == "basetype":
249 let typechild = node.child("type") 288 let typechild = node.child("type")
250 let namechild = node.child("name") 289 let namechild = node.child("name")
251 if typechild != nil and namechild != nil: 290 if typechild != nil and namechild != nil:
336 platformTypes[command.attr("name")] = "provisional" 375 platformTypes[command.attr("name")] = "provisional"
337 376
338 var outputFiles = { 377 var outputFiles = {
339 "basetypes": @[ 378 "basetypes": @[
340 "import std/dynlib", 379 "import std/dynlib",
380 "import std/tables",
341 "type", 381 "type",
342 " VkHandle* = distinct pointer", 382 " VkHandle* = distinct uint",
343 " VkNonDispatchableHandle* = distinct pointer", 383 " VkNonDispatchableHandle* = distinct uint",
344 "when defined(linux):", 384 "when defined(linux):",
345 " let vulkanLib* = loadLib(\"libvulkan.so.1\")", 385 " let vulkanLib* = loadLib(\"libvulkan.so.1\")",
346 "when defined(windows):", 386 "when defined(windows):",
347 " let vulkanLib* = loadLib(\"vulkan-1.dll\")", 387 " let vulkanLib* = loadLib(\"vulkan-1.dll\")",
348 "if vulkanLib == nil:", 388 "if vulkanLib == nil:",
362 let value = call 402 let value = call
363 if value != VK_SUCCESS: 403 if value != VK_SUCCESS:
364 error "Vulkan error: ", astToStr(call), " returned ", $value 404 error "Vulkan error: ", astToStr(call), " returned ", $value
365 raise newException(Exception, "Vulkan error: " & astToStr(call) & 405 raise newException(Exception, "Vulkan error: " & astToStr(call) &
366 " returned " & $value)""", 406 " returned " & $value)""",
367 "type",
368 ], 407 ],
369 "structs": @["type"], 408 "structs": @["type"],
370 "enums": @["type"], 409 "enums": @["type"],
371 "commands": @[], 410 "commands": @[],
372 }.toTable 411 }.toTable
412 outputFiles["basetypes"].add serializeConsts(api)
413 outputFiles["basetypes"].add "type"
373 414
374 # enums 415 # enums
375 for thetype in api.findAll("type"): 416 for thetype in api.findAll("type"):
376 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"): 417 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"):
377 let name = thetype.child("name")[0].text 418 let name = thetype.child("name")[0].text
378 outputFiles["enums"].add &" {name}* = distinct VkFlags" 419 outputFiles["enums"].add &" {name}* = distinct VkFlags"
420 outputFiles["enums"].add "let vkGetInstanceProcAddr = cast[proc(instance: VkInstance, name: cstring): pointer {.stdcall.}](checkedSymAddr(vulkanLib, \"vkGetInstanceProcAddr\"))"
421 outputFiles["enums"].add "type"
379 for theenum in api.findAll("enums"): 422 for theenum in api.findAll("enums"):
380 outputFiles["enums"].add serializeEnum(theenum, api) 423 outputFiles["enums"].add serializeEnum(theenum, api)
381 424
382 # structs and function types need to be in same "type" block to avoid forward-declarations 425 # structs and function types need to be in same "type" block to avoid forward-declarations
383 outputFiles["structs"].add serializeFunctiontypes(api) 426 outputFiles["structs"].add serializeFunctiontypes(api)
406 # commands aka functions 449 # commands aka functions
407 var varDecls: Table[string, string] 450 var varDecls: Table[string, string]
408 var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs 451 var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs
409 for commands in api.findAll("commands"): 452 for commands in api.findAll("commands"):
410 for command in commands.findAll("command"): 453 for command in commands.findAll("command"):
411 if command.attr("api") != "vulkansc": 454 if command.attr("api") != "vulkansc" and not (command.attr("name") in IGNORED_COMMANDS):
412 if command.hasAttr("alias"): 455 if command.hasAttr("alias"):
413 let name = command.attr("name") 456 let name = command.attr("name")
414 let alias = command.attr("alias") 457 let alias = command.attr("alias")
415 let thetype = varDecls[alias].split(":", 1)[1].strip() 458 let thetype = varDecls[alias].split(":", 1)[1].strip()
416 varDecls[name] = &" {name}*: {thetype}" 459 varDecls[name] = &" {name}*: {thetype}"
417 procLoads[name] = &" {name} = {alias}" 460 procLoads[name] = &" {name} = {alias}"
418 else: 461 else:
419 let (name, thetype) = serializeCommand(command) 462 let (name, thetype) = serializeCommand(command)
420 varDecls[name] = &" {name}*: {thetype}" 463 varDecls[name] = &" {name}*: {thetype}"
421 procLoads[name] = &" {name} = cast[{thetype}](checkedSymAddr(vulkanLib, \"{name}\"))" 464 procLoads[name] = &" {name} = cast[{thetype}](vkGetInstanceProcAddr(instance, \"{name}\"))"
422 var declared: seq[string] 465 var declared: seq[string]
423 var featureloads: seq[string] 466 var featureloads: seq[string]
424 for feature in api.findAll("feature"): 467 for feature in api.findAll("feature"):
425 if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]: 468 if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]:
426 let name = feature.attr("name") 469 let name = feature.attr("name")
427 outputFiles["commands"].add &"# feature {name}" 470 outputFiles["commands"].add &"# feature {name}"
428 outputFiles["commands"].add "var" 471 outputFiles["commands"].add "var"
429 for command in feature.findAll("command"): 472 for command in feature.findAll("command"):
430 if not (command.attr("name") in declared): 473 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS):
431 outputFiles["commands"].add varDecls[command.attr("name")] 474 outputFiles["commands"].add varDecls[command.attr("name")]
432 declared.add command.attr("name") 475 declared.add command.attr("name")
433 featureloads.add &"load{name}" 476 featureloads.add &"load{name}"
434 outputFiles["commands"].add &"proc load{name}*() =" 477 outputFiles["commands"].add &"proc load{name}*(instance: VkInstance) ="
435 for command in feature.findAll("command"): 478 for command in feature.findAll("command"):
436 outputFiles["commands"].add procLoads[command.attr("name")] 479 if not (command.attr("name") in IGNORED_COMMANDS & GLOBAL_COMMANDS):
480 outputFiles["commands"].add procLoads[command.attr("name")]
437 outputFiles["commands"].add "" 481 outputFiles["commands"].add ""
438 outputFiles["commands"].add ["proc initVulkan*() ="] 482 outputFiles["commands"].add ["proc loadVulkan*(instance: VkInstance) ="]
439 for l in featureloads: 483 for l in featureloads:
440 outputFiles["commands"].add [&" {l}()"] 484 outputFiles["commands"].add [&" {l}(instance)"]
441 outputFiles["commands"].add "" 485 outputFiles["commands"].add ""
442 486
443 # for promoted extensions, dependants need to call the load-function of the promoted feature/extension 487 # for promoted extensions, dependants need to call the load-function of the promoted feature/extension
444 # use table to store promotions 488 # use table to store promotions
445 var promotions: Table[string, string] 489 var promotions: Table[string, string]
462 let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+") 506 let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+")
463 extensionDependencies[name] = (dependencies, extension) 507 extensionDependencies[name] = (dependencies, extension)
464 if name in SPECIAL_DEPENDENCIES: 508 if name in SPECIAL_DEPENDENCIES:
465 extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name] 509 extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name]
466 510
511 # order dependencies to generate them in correct order
467 var dependencyOrderedExtensions: OrderedTable[string, XmlNode] 512 var dependencyOrderedExtensions: OrderedTable[string, XmlNode]
468 while extensionDependencies.len > 0: 513 while extensionDependencies.len > 0:
469 var delkeys: seq[string] 514 var delkeys: seq[string]
470 for extensionName, (dependencies, extension) in extensionDependencies.pairs: 515 for extensionName, (dependencies, extension) in extensionDependencies.pairs:
471 var missingExtension = false 516 var missingExtension = false
478 dependencyOrderedExtensions[extensionName] = extension 523 dependencyOrderedExtensions[extensionName] = extension
479 delkeys.add extensionName 524 delkeys.add extensionName
480 for key in delkeys: 525 for key in delkeys:
481 extensionDependencies.del key 526 extensionDependencies.del key
482 527
528 var extensionLoaderMap: Table[string, Table[string, string]]
483 for extension in dependencyOrderedExtensions.values: 529 for extension in dependencyOrderedExtensions.values:
484 if extension.hasAttr("promotedto"): # will be loaded in promoted place 530 if extension.hasAttr("promotedto"): # will be loaded in promoted place
485 continue 531 continue
486 if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]: 532 if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]:
487 var file = "commands" 533 var file = "commands"
488 if extension.attr("platform") != "": 534 var platform = extension.attr("platform")
489 file = "platform/" & extension.attr("platform") 535 if extension.attr("name").startsWith("VK_KHR_video"):
490 elif extension.attr("name").startsWith("VK_KHR_video"): # hack since we do not include video headers by default 536 platform = "provisional"
491 file = "platform/provisional" 537 if platform != "":
538 file = "platform/" & platform
492 let name = extension.attr("name") 539 let name = extension.attr("name")
493 if extension.findAll("command").len > 0: 540 if extension.findAll("command").len > 0:
494 outputFiles[file].add &"# extension {name}" 541 outputFiles[file].add &"# extension {name}"
495 outputFiles[file].add "var" 542 outputFiles[file].add "var"
496 for command in extension.findAll("command"): 543 for command in extension.findAll("command"):
497 if not (command.attr("name") in declared): 544 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS):
498 outputFiles[file].add varDecls[command.attr("name")] 545 outputFiles[file].add varDecls[command.attr("name")]
499 declared.add command.attr("name") 546 declared.add command.attr("name")
500 outputFiles[file].add &"proc load{name}*() =" 547 outputFiles[file].add &"proc load{name}*(instance: VkInstance) ="
548 if not (platform in extensionLoaderMap):
549 extensionLoaderMap[platform] = Table[string, string]()
550 extensionLoaderMap[platform][name] = &"load{name}"
501 var addedFunctionBody = false 551 var addedFunctionBody = false
502 if extension.hasAttr("depends"): 552 if extension.hasAttr("depends"):
503 for dependency in extension.attr("depends").split("+"): 553 for dependency in extension.attr("depends").split("+"):
504 # need to check since some extensions have no commands and therefore no load-function 554 # need to check since some extensions have no commands and therefore no load-function
505 outputFiles[file].add &" load{promotions.getOrDefault(dependency, dependency)}()" 555 outputFiles[file].add &" load{promotions.getOrDefault(dependency, dependency)}(instance)"
506 addedFunctionBody = true 556 addedFunctionBody = true
507 for command in extension.findAll("command"): 557 for command in extension.findAll("command"):
508 outputFiles[file].add procLoads[command.attr("name")] 558 outputFiles[file].add procLoads[command.attr("name")]
509 addedFunctionBody = true 559 addedFunctionBody = true
510 if not addedFunctionBody: 560 if not addedFunctionBody:
512 outputFiles[file].add "" 562 outputFiles[file].add ""
513 563
514 var mainout: seq[string] 564 var mainout: seq[string]
515 for section in ["basetypes", "enums", "structs", "commands"]: 565 for section in ["basetypes", "enums", "structs", "commands"]:
516 mainout.add outputFiles[section] 566 mainout.add outputFiles[section]
567 mainout.add "var EXTENSION_LOADERS = {"
568 for extension, loader in extensionLoaderMap[""].pairs:
569 mainout.add &" \"{extension}\": {loader},"
570 mainout.add "}.toTable"
517 for platform in api.findAll("platform"): 571 for platform in api.findAll("platform"):
518 mainout.add &"when defined({platform.attr(\"protect\")}):" 572 mainout.add &"when defined({platform.attr(\"protect\")}):"
519 mainout.add &" include platform/{platform.attr(\"name\")}" 573 mainout.add &" include platform/{platform.attr(\"name\")}"
574 if platform.attr("name") in extensionLoaderMap:
575 for extension, loader in extensionLoaderMap[platform.attr("name")].pairs:
576 mainout.add &" EXTENSION_LOADERS[\"{extension}\"] = {loader}"
577
578 mainout.add ""
579 mainout.add "proc loadExtension*(instance: VkInstance, extension: string) = EXTENSION_LOADERS[extension](instance)"
580 mainout.add ""
581 mainout.add "# load global functions immediately"
582 mainout.add "block globalFunctions:"
583 mainout.add " let instance = VkInstance(0)"
584 for l in GLOBAL_COMMANDS:
585 mainout.add procLoads[l]
520 writeFile outdir / &"api.nim", mainout.join("\n") 586 writeFile outdir / &"api.nim", mainout.join("\n")
587
588 mainout.add ""
589 mainout.add "converter VkBool2NimBool*(a: VkBool32): bool = a > 0"
590 mainout.add "converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)"
521 591
522 for filename, filecontent in outputFiles.pairs: 592 for filename, filecontent in outputFiles.pairs:
523 if filename.startsWith("platform/"): 593 if filename.startsWith("platform/"):
524 writeFile outdir / &"{filename}.nim", (@[ 594 writeFile outdir / &"{filename}.nim", (@[
525 "type" 595 "type"