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" |