comparison src/vulkan_api/vulkan_api_generator.nim @ 79:031f241de6ca

add: beta version
author Sam <sam@basx.dev>
date Wed, 22 Feb 2023 00:44:03 +0700
parents f67496a189cb
children 637da715b604
comparison
equal deleted inserted replaced
78:f67496a189cb 79:031f241de6ca
35 "ptr void": "pointer", 35 "ptr void": "pointer",
36 "VK_DEFINE_HANDLE": "VkHandle", 36 "VK_DEFINE_HANDLE": "VkHandle",
37 "VK_DEFINE_NON_DISPATCHABLE_HANDLE": "VkNonDispatchableHandle", 37 "VK_DEFINE_NON_DISPATCHABLE_HANDLE": "VkNonDispatchableHandle",
38 }.toTable 38 }.toTable
39 PLATFORM_HEADER_MAP = { 39 PLATFORM_HEADER_MAP = {
40 "X11/Xlib.h": "xlib", 40 "X11/Xlib.h": @["xlib", "xlib_xrandr"],
41 "X11/extensions/Xrandr.h": "xlib_xrandr", 41 "X11/extensions/Xrandr.h": @["xlib_xrandr"],
42 "wayland-client.h": "wayland", 42 "wayland-client.h": @["wayland"],
43 "windows.h": "win32", 43 "windows.h": @["win32"],
44 "xcb/xcb.h": "xcb", 44 "xcb/xcb.h": @["xcb"],
45 "directfb.h": "directfb", 45 "directfb.h": @["directfb"],
46 "zircon/types.h": "fuchsia", 46 "zircon/types.h": @["fuchsia"],
47 "ggp_c/vulkan_types.h": "ggp", 47 "ggp_c/vulkan_types.h": @["ggp"],
48 "screen/screen.h": "screen", 48 "screen/screen.h": @["screen"],
49 "nvscisync.h": "nvidia", 49 "nvscisync.h": @["sci"],
50 "nvscibuf.h": "nvidia", 50 "nvscibuf.h": @["sci"],
51 "vk_video/vulkan_video_codec_h264std.h": "vk_video", 51 "vk_video/vulkan_video_codec_h264std.h": @["provisional"],
52 "vk_video/vulkan_video_codec_h264std_decode.h": "vk_video", 52 "vk_video/vulkan_video_codec_h264std_decode.h": @["provisional"],
53 "vk_video/vulkan_video_codec_h264std_encode.h": "vk_video", 53 "vk_video/vulkan_video_codec_h264std_encode.h": @["provisional"],
54 "vk_video/vulkan_video_codec_h265std.h": "vk_video", 54 "vk_video/vulkan_video_codec_h265std.h": @["provisional"],
55 "vk_video/vulkan_video_codec_h265std_decode.h": "vk_video", 55 "vk_video/vulkan_video_codec_h265std_decode.h": @["provisional"],
56 "vk_video/vulkan_video_codec_h265std_encode.h": "vk_video", 56 "vk_video/vulkan_video_codec_h265std_encode.h": @["provisional"],
57 }.toTable 57 }.toTable
58 MAP_KEYWORD = { 58 MAP_KEYWORD = {
59 "object": "theobject", 59 "object": "theobject",
60 "type": "thetype", 60 "type": "thetype",
61 }.toTable
62 SPECIAL_DEPENDENCIES = {
63 "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline",
61 }.toTable 64 }.toTable
62 65
63 # helpers 66 # helpers
64 func mapType(typename: string): auto = 67 func mapType(typename: string): auto =
65 TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'}) 68 TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'})
157 result.add enumEntry 160 result.add enumEntry
158 161
159 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) 162 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim)
160 elif node.attr("type") == "bitmask": 163 elif node.attr("type") == "bitmask":
161 for value in node.findAll("enum"): 164 for value in node.findAll("enum"):
162 if value.hasAttr("alias") or not value.hasAttr("bitpos"): 165 if value.hasAttr("bitpos"):
163 continue 166 values[smartParseInt(value.attr("bitpos"))] = value.attr("name")
164 values[smartParseInt(value.attr("bitpos"))] = value.attr("name") 167 elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos
168 values[smartParseInt(value.attr("value"))] = value.attr("name")
165 if values.len > 0: 169 if values.len > 0:
166 if node.hasAttr("bitwidth"): 170 if node.hasAttr("bitwidth"):
167 result.add " " & name & "* {.size: " & $(smartParseInt(node.attr("bitwidth")) div 8) & ".} = enum" 171 result.add " " & name & "* {.size: " & $(smartParseInt(node.attr("bitwidth")) div 8) & ".} = enum"
168 else: 172 else:
169 result.add " " & name & "* {.size: sizeof(cint).} = enum" 173 result.add " " & name & "* {.size: sizeof(cint).} = enum"
191 195
192 func serializeStruct(node: XmlNode, root: XmlNode): seq[string] = 196 func serializeStruct(node: XmlNode, root: XmlNode): seq[string] =
193 let name = node.attr("name") 197 let name = node.attr("name")
194 var union = "" 198 var union = ""
195 if node.attr("category") == "union": 199 if node.attr("category") == "union":
196 union = "{.union.}" 200 union = "{.union.} "
197 result.add &" {name}* {union} = object" 201 result.add &" {name}* {union}= object"
198 for member in node.findAll("member"): 202 for member in node.findAll("member"):
199 if not member.hasAttr("api") or member.attr("api") == "vulkan": 203 if not member.hasAttr("api") or member.attr("api") == "vulkan":
200 let fieldname = member.child("name")[0].text.strip(chars={'_'}) 204 let fieldname = member.child("name")[0].text.strip(chars={'_'})
201 var fieldtype = member.child("type")[0].text.strip(chars={'_'}) 205 var fieldtype = member.child("type")[0].text.strip(chars={'_'})
202 if member[member.len - 2].kind == xnText and member[member.len - 2].text.strip() == "*": 206 if member[member.len - 2].kind == xnText and member[member.len - 2].text.strip() == "*":
217 paramname = paramname.rsplit(" ", 1)[1] 221 paramname = paramname.rsplit(" ", 1)[1]
218 paramtype = "ptr " & paramtype 222 paramtype = "ptr " & paramtype
219 paramname = mapName(paramname) 223 paramname = mapName(paramname)
220 params.add &"{paramname}: {mapType(paramtype)}" 224 params.add &"{paramname}: {mapType(paramtype)}"
221 let paramsstr = params.join(", ") 225 let paramsstr = params.join(", ")
222 result.add(&" {name} = proc({paramsstr}): {returntype} {{.cdecl.}}") 226 result.add(&" {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}")
223 227
224 func serializeType(node: XmlNode): Table[string, seq[string]] = 228 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] =
225 if node.attrsLen == 0: 229 if node.attrsLen == 0:
226 return 230 return
227 if node.attr("requires") == "vk_platform" or node.attr("category") == "include": 231 if node.attr("requires") == "vk_platform" or node.attr("category") == "include":
228 return 232 return
229 result["basetypes"] = @[] 233 result["basetypes"] = @[]
230 result["enums"] = @[] 234 result["enums"] = @[]
231 235
232 # include-defined types (in platform headers) 236 # include-defined types (in platform headers)
233 if node.hasAttr("requires") and node.hasAttr("name") and node.attr("category") != "define": 237 if node.attr("name") in headerTypes:
234 let platform = "platform/" & PLATFORM_HEADER_MAP[node.attr("requires")] 238 for platform in PLATFORM_HEADER_MAP[node.attr("requires")]:
235 if not result.hasKey(platform): 239 let platformfile = "platform/" & platform
236 result[platform] = @[] 240 if not result.hasKey(platformfile):
237 result[platform].add "type " & node.attr( 241 result[platformfile] = @[]
238 "name") & " {.header: \"" & node.attr("requires") & "\".} = object" 242 result[platformfile].add " " & node.attr("name").strip(chars={'_'}) & " {.header: \"" & node.attr("requires") & "\".} = object"
239 # generic base types 243 # generic base types
240 elif node.attr("category") == "basetype": 244 elif node.attr("category") == "basetype":
241 let typechild = node.child("type") 245 let typechild = node.child("type")
242 let namechild = node.child("name") 246 let namechild = node.child("name")
243 if typechild != nil and namechild != nil: 247 if typechild != nil and namechild != nil:
272 let alias = node.attr("alias") 276 let alias = node.attr("alias")
273 result["enums"].add &" {name}* = {alias}" 277 result["enums"].add &" {name}* = {alias}"
274 else: 278 else:
275 discard 279 discard
276 280
281 func serializeCommand(node: XmlNode): (string, string) =
282 let
283 proto = node.child("proto")
284 resulttype = mapType(proto.child("type")[0].text)
285 name = proto.child("name")[0].text
286 var params: seq[string]
287 for param in node:
288 if param.tag == "param" and param.attr("api") in ["", "vulkan"]:
289 let fieldname = param.child("name")[0].text.strip(chars={'_'})
290 var fieldtype = param.child("type")[0].text.strip(chars={'_'})
291 if param[param.len - 2].kind == xnText and param[param.len - 2].text.strip() == "*":
292 fieldtype = &"ptr {mapType(fieldtype)}"
293 fieldtype = mapType(fieldtype)
294 params.add &"{mapName(fieldname)}: {fieldtype}"
295 let allparams = params.join(", ")
296 return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}")
297
277 298
278 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = 299 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) =
279 for k, v in b.pairs: 300 for k, v in b.pairs:
280 if not a.hasKey(k): 301 if not a.hasKey(k):
281 a[k] = @[] 302 a[k] = @[]
303 platformTypes[thetype.attr("name")] = extension.attr("platform") 324 platformTypes[thetype.attr("name")] = extension.attr("platform")
304 for command in extension.findAll("command"): 325 for command in extension.findAll("command"):
305 platformTypes[command.attr("name")] = extension.attr("platform") 326 platformTypes[command.attr("name")] = extension.attr("platform")
306 elif extension.attr("name").startsWith("VK_KHR_video"): 327 elif extension.attr("name").startsWith("VK_KHR_video"):
307 for thetype in extension.findAll("type"): 328 for thetype in extension.findAll("type"):
308 platformTypes[thetype.attr("name")] = "vk_video" 329 platformTypes[thetype.attr("name")] = "provisional"
309 for command in extension.findAll("command"): 330 for command in extension.findAll("command"):
310 platformTypes[command.attr("name")] = "vk_video" 331 platformTypes[command.attr("name")] = "provisional"
311 332
312 var outputFiles = { 333 var outputFiles = {
313 "basetypes": @["type", " VkHandle* = distinct pointer", " VkNonDispatchableHandle* = distinct pointer"], 334 "basetypes": @[
314 "structs": @["import ./enums", "import ./basetypes", "type"], 335 "import std/dynlib",
315 "enums": @["import ./basetypes", "type"], 336 "type",
337 " VkHandle* = distinct pointer",
338 " VkNonDispatchableHandle* = distinct pointer",
339 "when defined(linux):",
340 " let vulkanLib* = loadLib(\"libvulkan.so.1\")",
341 "when defined(windows):",
342 " let vulkanLib* = loadLib(\"vulkan-1.dll\")",
343 "if vulkanLib == nil:",
344 " raise newException(Exception, \"Unable to load vulkan library\")",
345 "type",
346 ],
347 "structs": @["type"],
348 "enums": @["type"],
349 "commands": @[],
316 }.toTable 350 }.toTable
317 351
318 # enums 352 # enums
319 for thetype in api.findAll("type"): 353 for thetype in api.findAll("type"):
320 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"): 354 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"):
330 var outfile = "structs" 364 var outfile = "structs"
331 if thetype.attr("name") in platformTypes: 365 if thetype.attr("name") in platformTypes:
332 outfile = "platform/" & platformTypes[thetype.attr("name")] 366 outfile = "platform/" & platformTypes[thetype.attr("name")]
333 if not (outfile in outputFiles): 367 if not (outfile in outputFiles):
334 outputFiles[outfile] = @[] 368 outputFiles[outfile] = @[]
335 outputFiles[outfile].add "type"
336 outputFiles[outfile].add serializeStruct(thetype, api) 369 outputFiles[outfile].add serializeStruct(thetype, api)
337 370
338 # types 371 # types
372 var headerTypes: Table[string, string]
373 for types in api.findAll("types"):
374 for thetype in types.findAll("type"):
375 if thetype.attrsLen == 2 and thetype.hasAttr("requires") and thetype.hasAttr("name") and thetype.attr("requires") != "vk_platform":
376 let name = thetype.attr("name")
377 let incld = thetype.attr("requires")
378 headerTypes[name] = &"{name} {{.header: \"{incld}\".}} = object"
379
339 for typesgroup in api.findAll("types"): 380 for typesgroup in api.findAll("types"):
340 for thetype in typesgroup.findAll("type"): 381 for thetype in typesgroup.findAll("type"):
341 outputFiles.update serializeType(thetype) 382 outputFiles.update serializeType(thetype, headerTypes)
383
384 # commands aka functions
385 var varDecls: Table[string, string]
386 var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs
387 for commands in api.findAll("commands"):
388 for command in commands.findAll("command"):
389 if command.attr("api") != "vulkansc":
390 if command.hasAttr("alias"):
391 let name = command.attr("name")
392 let alias = command.attr("alias")
393 let thetype = varDecls[alias].split(":", 1)[1].strip()
394 varDecls[name] = &" {name}*: {thetype}"
395 procLoads[name] = &" {name} = {alias}"
396 else:
397 let (name, thetype) = serializeCommand(command)
398 varDecls[name] = &" {name}*: {thetype}"
399 procLoads[name] = &" {name} = cast[{thetype}](checkedSymAddr(vulkanLib, \"{name}\"))"
400 var declared: seq[string]
401 var featureloads: seq[string]
402 for feature in api.findAll("feature"):
403 if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]:
404 let name = feature.attr("name")
405 outputFiles["commands"].add &"# feature {name}"
406 outputFiles["commands"].add "var"
407 for command in feature.findAll("command"):
408 if not (command.attr("name") in declared):
409 outputFiles["commands"].add varDecls[command.attr("name")]
410 declared.add command.attr("name")
411 featureloads.add &"load{name}"
412 outputFiles["commands"].add &"proc load{name}*() ="
413 for command in feature.findAll("command"):
414 outputFiles["commands"].add procLoads[command.attr("name")]
415 outputFiles["commands"].add ""
416 outputFiles["commands"].add ["proc loadAll*() ="]
417 for l in featureloads:
418 outputFiles["commands"].add [&" {l}()"]
419 outputFiles["commands"].add ""
420
421 # for promoted extensions, dependants need to call the load-function of the promoted feature/extension
422 # use table to store promotions
423 var promotions: Table[string, string]
424 for extensions in api.findAll("extensions"):
425 for extension in extensions.findAll("extension"):
426 if extension.hasAttr("promotedto"):
427 promotions[extension.attr("name")] = extension.attr("promotedto")
428
429 var extensionDependencies: Table[string, (seq[string], XmlNode)]
430 var features: seq[string]
431 for feature in api.findAll("feature"):
432 features.add feature.attr("name")
433 for extensions in api.findAll("extensions"):
434 for extension in extensions.findAll("extension"):
435 let name = extension.attr("name")
436 extensionDependencies[name] = (@[], extension)
437 if extension.hasAttr("depends"):
438 extensionDependencies[name] = (extension.attr("depends").split("+"), extension)
439 if extension.attr("depends").startsWith("("): # no need for full tree parser, only single place where we can use a feature
440 let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+")
441 extensionDependencies[name] = (dependencies, extension)
442 if name in SPECIAL_DEPENDENCIES:
443 extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name]
444
445 var dependencyOrderedExtensions: OrderedTable[string, XmlNode]
446 while extensionDependencies.len > 0:
447 var delkeys: seq[string]
448 for extensionName, (dependencies, extension) in extensionDependencies.pairs:
449 var missingExtension = false
450 for dep in dependencies:
451 let realdep = promotions.getOrDefault(dep, dep)
452 if not (realdep in dependencyOrderedExtensions) and not (realdep in features):
453 missingExtension = true
454 break
455 if not missingExtension:
456 dependencyOrderedExtensions[extensionName] = extension
457 delkeys.add extensionName
458 for key in delkeys:
459 extensionDependencies.del key
460
461 for extension in dependencyOrderedExtensions.values:
462 if extension.hasAttr("promotedto"): # will be loaded in promoted place
463 continue
464 if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]:
465 var file = "commands"
466 if extension.attr("platform") != "":
467 file = "platform/" & extension.attr("platform")
468 elif extension.attr("name").startsWith("VK_KHR_video"): # hack since we do not include video headers by default
469 file = "platform/provisional"
470 let name = extension.attr("name")
471 if extension.findAll("command").len > 0:
472 outputFiles[file].add &"# extension {name}"
473 outputFiles[file].add "var"
474 for command in extension.findAll("command"):
475 if not (command.attr("name") in declared):
476 outputFiles[file].add varDecls[command.attr("name")]
477 declared.add command.attr("name")
478 outputFiles[file].add &"proc load{name}*() ="
479 var addedFunctionBody = false
480 if extension.hasAttr("depends"):
481 for dependency in extension.attr("depends").split("+"):
482 # need to check since some extensions have no commands and therefore no load-function
483 outputFiles[file].add &" load{promotions.getOrDefault(dependency, dependency)}()"
484 addedFunctionBody = true
485 for command in extension.findAll("command"):
486 outputFiles[file].add procLoads[command.attr("name")]
487 addedFunctionBody = true
488 if not addedFunctionBody:
489 outputFiles[file].add " discard"
490 outputFiles[file].add ""
491
492 var mainout: seq[string]
493 for section in ["basetypes", "enums", "structs", "commands"]:
494 mainout.add outputFiles[section]
495 writeFile outdir / &"types.nim", mainout.join("\n")
496
342 for filename, filecontent in outputFiles.pairs: 497 for filename, filecontent in outputFiles.pairs:
343 writeFile outdir / &"{filename}.nim", filecontent.join("\n") 498 if filename.startsWith("platform/"):
499 writeFile outdir / &"{filename}.nim", (@[
500 "import std/dynlib",
501 "import ../types",
502 "type"
503 ] & filecontent).join("\n")
344 504
345 when isMainModule: 505 when isMainModule:
346 main() 506 main()