Mercurial > games > semicongine
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" |
