Mercurial > games > semicongine
comparison tools/vulkan_api_generator/vulkan_api_generator.nim @ 1038:e267983f5edf
did: cleanup
author | sam <sam@basx.dev> |
---|---|
date | Fri, 22 Mar 2024 11:42:27 +0700 |
parents | generators/vulkan_api/vulkan_api_generator.nim@c66503386e8b |
children |
comparison
equal
deleted
inserted
replaced
1037:459fe2761311 | 1038:e267983f5edf |
---|---|
1 import std/os | |
2 import std/sugar | |
3 import std/algorithm | |
4 import std/strformat | |
5 import std/strutils | |
6 import std/sequtils | |
7 import std/streams | |
8 import std/tables | |
9 import httpClient | |
10 import std/xmlparser | |
11 import std/xmltree | |
12 | |
13 const | |
14 TYPEMAP = { | |
15 "void": "void", | |
16 "char": "char", | |
17 "float": "float32", | |
18 "double": "float64", | |
19 "int8_t": "int8", | |
20 "uint8_t": "uint8", | |
21 "int16_t": "int16", | |
22 "uint16_t": "uint16", | |
23 "int32_t": "int32", | |
24 "uint32_t": "uint32", | |
25 "uint64_t": "uint64", | |
26 "int64_t": "int64", | |
27 "size_t": "csize_t", | |
28 "int": "cint", | |
29 "void*": "pointer", | |
30 "char*": "cstring", | |
31 "ptr char": "cstring", | |
32 "ptr void": "pointer", | |
33 "VK_DEFINE_HANDLE": "VkHandle", | |
34 "VK_DEFINE_NON_DISPATCHABLE_HANDLE": "VkNonDispatchableHandle", | |
35 }.toTable | |
36 PLATFORM_HEADER_MAP = { | |
37 "X11/Xlib.h": @["xlib", "xlib_xrandr"], | |
38 "X11/extensions/Xrandr.h": @["xlib_xrandr"], | |
39 "wayland-client.h": @["wayland"], | |
40 "windows.h": @["win32"], | |
41 "xcb/xcb.h": @["xcb"], | |
42 "directfb.h": @["directfb"], | |
43 "zircon/types.h": @["fuchsia"], | |
44 "ggp_c/vulkan_types.h": @["ggp"], | |
45 "screen/screen.h": @["screen"], | |
46 "nvscisync.h": @["sci"], | |
47 "nvscibuf.h": @["sci"], | |
48 "vk_video/vulkan_video_codec_h264std.h": @["provisional"], | |
49 "vk_video/vulkan_video_codec_h264std_decode.h": @["provisional"], | |
50 "vk_video/vulkan_video_codec_h264std_encode.h": @["provisional"], | |
51 "vk_video/vulkan_video_codec_h265std.h": @["provisional"], | |
52 "vk_video/vulkan_video_codec_h265std_decode.h": @["provisional"], | |
53 "vk_video/vulkan_video_codec_h265std_encode.h": @["provisional"], | |
54 }.toTable | |
55 MAP_KEYWORD = { | |
56 "object": "theobject", | |
57 "type": "thetype", | |
58 }.toTable | |
59 SPECIAL_DEPENDENCIES = { | |
60 "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline", | |
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 ] | |
71 | |
72 # helpers | |
73 func mapType(typename: string): auto = | |
74 TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars={'_'}) | |
75 func mapName(thename: string): auto = | |
76 MAP_KEYWORD.getOrDefault(thename.strip(), thename.strip()).strip(chars={'_'}) | |
77 func smartParseInt(value: string): int = | |
78 if value.startsWith("0x"): | |
79 parseHexInt(value) | |
80 else: | |
81 parseInt(value) | |
82 func hasAttr(node: XmlNode, attr: string): bool = node.attr(attr) != "" | |
83 func tableSorted(table: Table[int, string]): seq[(int, string)] = | |
84 result = toSeq(table.pairs) | |
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}]" | |
128 | |
129 # serializers | |
130 # return values and whether this is a bitfield | |
131 func serializeEnum(node: XmlNode, api: XmlNode): (seq[string], string) = | |
132 let name = node.attr("name") | |
133 if name == "": | |
134 return result | |
135 | |
136 var reservedNames: seq[string] | |
137 for t in api.findAll("type"): | |
138 reservedNames.add t.attr("name").replace("_", "").toLower() | |
139 | |
140 # find additional enum defintion in feature definitions | |
141 var values: Table[int, string] | |
142 for feature in api.findAll("feature"): | |
143 for require in feature.findAll("require"): | |
144 for theenum in require.findAll("enum"): | |
145 if theenum.attr("extends") == name: | |
146 if theenum.hasAttr("offset"): | |
147 let enumBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | |
148 var value = smartParseInt(theenum.attr("offset")) + enumBase | |
149 if theenum.attr("dir") == "-": | |
150 value = -value | |
151 values[value] = theenum.attr("name") | |
152 elif theenum.hasAttr("value"): | |
153 var value = smartParseInt(theenum.attr("value")) | |
154 if theenum.attr("dir") == "-": | |
155 value = -value | |
156 values[value] = theenum.attr("name") | |
157 elif theenum.hasAttr("bitpos"): | |
158 var value = smartParseInt(theenum.attr("bitpos")) | |
159 if theenum.attr("dir") == "-": | |
160 value = -value | |
161 values[value] = theenum.attr("name") | |
162 elif theenum.hasAttr("alias"): | |
163 discard | |
164 else: | |
165 raise newException(Exception, &"Unknown extension value: {feature}\nvalue:{theenum}") | |
166 # find additional enum defintion in extension definitions | |
167 for extension in api.findAll("extension"): | |
168 let extensionNumber = parseInt(extension.attr("number")) | |
169 let enumBase = 1000000000 + (extensionNumber - 1) * 1000 | |
170 for require in extension.findAll("require"): | |
171 for theenum in require.findAll("enum"): | |
172 if theenum.attr("extends") == name: | |
173 if theenum.hasAttr("offset"): | |
174 if theenum.hasAttr("extnumber"): | |
175 let otherBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | |
176 var value = smartParseInt(theenum.attr("offset")) + otherBase | |
177 if theenum.attr("dir") == "-": | |
178 value = -value | |
179 values[value] = theenum.attr("name") | |
180 else: | |
181 var value = smartParseInt(theenum.attr("offset")) + enumBase | |
182 if theenum.attr("dir") == "-": | |
183 value = -value | |
184 values[value] = theenum.attr("name") | |
185 elif theenum.hasAttr("value"): | |
186 var value = smartParseInt(theenum.attr("value")) | |
187 if theenum.attr("dir") == "-": | |
188 value = -value | |
189 values[value] = theenum.attr("name") | |
190 elif theenum.hasAttr("bitpos"): | |
191 var value = smartParseInt(theenum.attr("bitpos")) | |
192 if theenum.attr("dir") == "-": | |
193 value = -value | |
194 values[value] = theenum.attr("name") | |
195 elif theenum.hasAttr("alias"): | |
196 discard | |
197 else: | |
198 raise newException(Exception, &"Unknown extension value: {extension}\nvalue:{theenum}") | |
199 | |
200 # generate enums | |
201 if node.attr("type") == "enum": | |
202 for value in node.findAll("enum"): | |
203 if value.hasAttr("alias"): | |
204 continue | |
205 if value.attr("value").startsWith("0x"): | |
206 values[parseHexInt(value.attr("value"))] = value.attr("name") | |
207 else: | |
208 values[smartParseInt(value.attr("value"))] = value.attr("name") | |
209 if values.len > 0: | |
210 result[0].add " " & name & "* {.size: sizeof(cint).} = enum" | |
211 for (value, name) in tableSorted(values): | |
212 var thename = name | |
213 if name.replace("_", "").toLower() in reservedNames: | |
214 thename = thename & "_ENUM" | |
215 let enumEntry = &" {thename} = {value}" | |
216 result[0].add enumEntry | |
217 | |
218 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) | |
219 elif node.attr("type") == "bitmask": | |
220 var predefined_enum_sets: seq[string] | |
221 for value in node.findAll("enum"): | |
222 if value.hasAttr("bitpos"): | |
223 values[smartParseInt(value.attr("bitpos"))] = value.attr("name") | |
224 elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos | |
225 values[smartParseInt(value.attr("value"))] = value.attr("name") | |
226 elif value.hasAttr("value"): # create a const that has multiple bits set | |
227 predefined_enum_sets.add &" {value.attr(\"name\")}* = {value.attr(\"value\")}" | |
228 | |
229 if values.len > 0: | |
230 let cApiName = name.replace("FlagBits", "Flags") | |
231 result[1] = cApiName | |
232 if node.hasAttr("bitwidth"): | |
233 result[0].add " " & name & "* {.size: 8.} = enum" | |
234 else: | |
235 result[0].add " " & name & "* {.size: sizeof(cint).} = enum" | |
236 for (bitpos, enumvalue) in tableSorted(values): | |
237 var value = "00000000000000000000000000000000"# makes the bit mask nicely visible | |
238 if node.hasAttr("bitwidth"): # assumes this is always 64 | |
239 value = value & value | |
240 value[^(bitpos + 1)] = '1' | |
241 let enumEntry = &" {enumvalue} = 0b{value}" | |
242 if not (enumEntry in result[0]): # the specs define duplicate entries for backwards compat | |
243 result[0].add enumEntry | |
244 if node.hasAttr("bitwidth"): # assuming this attribute is always 64 | |
245 if values.len > 0: | |
246 result[0].add &"""func toBits*(flags: openArray[{name}]): {cApiName} = | |
247 for flag in flags: | |
248 result = {cApiName}(uint64(result) or uint64(flag))""" | |
249 result[0].add &"""func toEnums*(number: {cApiName}): seq[{name}] = | |
250 for value in {name}.items: | |
251 if (cast[uint64](value) and uint64(number)) > 0: | |
252 result.add value""" | |
253 result[0].add &"proc `==`*(a, b: {cApiName}): bool = uint64(a) == uint64(b)" | |
254 else: | |
255 if values.len > 0: | |
256 result[0].add &"""func toBits*(flags: openArray[{name}]): {cApiName} = | |
257 for flag in flags: | |
258 result = {cApiName}(uint(result) or uint(flag))""" | |
259 result[0].add &"""func toEnums*(number: {cApiName}): seq[{name}] = | |
260 for value in {name}.items: | |
261 if (value.ord and cint(number)) > 0: | |
262 result.add value""" | |
263 result[0].add &"proc `==`*(a, b: {cApiName}): bool = cint(a) == cint(b)" | |
264 if predefined_enum_sets.len > 0: | |
265 result[0].add "const" | |
266 result[0].add predefined_enum_sets | |
267 result[0].add "type" | |
268 | |
269 | |
270 func serializeStruct(node: XmlNode): seq[string] = | |
271 let name = node.attr("name") | |
272 var union = "" | |
273 if node.attr("category") == "union": | |
274 union = "{.union.} " | |
275 result.add &" {name}* {union}= object" | |
276 for member in node.findAll("member"): | |
277 if not member.hasAttr("api") or member.attr("api") == "vulkan": | |
278 let fieldname = member.child("name")[0].text.strip(chars={'_'}) | |
279 result.add &" {mapName(fieldname)}*: {findType(member)}" | |
280 | |
281 func serializeFunctiontypes(api: XmlNode): seq[string] = | |
282 for node in api.findAll("type"): | |
283 if node.attr("category") == "funcpointer": | |
284 let name = node[1][0] | |
285 let returntype = mapType(node[0].text[8 .. ^1].split('(', 1)[0]) | |
286 var params: seq[string] | |
287 for i in countup(3, node.len - 1, 2): | |
288 var paramname = node[i + 1].text.split(',', 1)[0].split(')', 1)[0] | |
289 var paramtype = node[i][0].text | |
290 if paramname[0] == '*': | |
291 paramname = paramname.rsplit(" ", 1)[1] | |
292 paramtype = "ptr " & paramtype | |
293 paramname = mapName(paramname) | |
294 params.add &"{paramname}: {mapType(paramtype)}" | |
295 let paramsstr = params.join(", ") | |
296 result.add(&" {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}") | |
297 | |
298 func serializeConsts(api: XmlNode): seq[string] = | |
299 result = @["const"] | |
300 for enums in api.findAll("enums"): | |
301 if enums.attr("name") == "API Constants": | |
302 for theenum in enums.findAll("enum"): | |
303 if theenum.hasAttr("alias"): | |
304 result.add &" {theenum.attr(\"name\")}* = {theenum.attr(\"alias\")}" | |
305 else: | |
306 var value = theenum.attr("value").strip(chars={'(', ')'}) | |
307 if value.endsWith("U"): | |
308 value = value[0..^2] & "'u32" | |
309 elif value.endsWith("ULL"): | |
310 value = value[0..^4] & "'u64" | |
311 if value[0] == '~': | |
312 value = "not " & value[1..^1] | |
313 result.add &" {theenum.attr(\"name\")}*: {mapType(theenum.attr(\"type\"))} = {value}" | |
314 | |
315 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] = | |
316 if node.attrsLen == 0: | |
317 return | |
318 if node.attr("requires") == "vk_platform" or node.attr("category") == "include": | |
319 return | |
320 result["basetypes"] = @[] | |
321 result["enums"] = @[] | |
322 | |
323 # include-defined types (in platform headers) | |
324 if node.attr("name") in headerTypes: | |
325 for platform in PLATFORM_HEADER_MAP[node.attr("requires")]: | |
326 let platformfile = "platform/" & platform | |
327 if not result.hasKey(platformfile): | |
328 result[platformfile] = @[] | |
329 result[platformfile].add " " & node.attr("name").strip(chars={'_'}) & " *{.header: \"" & node.attr("requires") & "\".} = object" | |
330 # generic base types | |
331 elif node.attr("category") == "basetype": | |
332 let typechild = node.child("type") | |
333 let namechild = node.child("name") | |
334 if typechild != nil and namechild != nil: | |
335 var typename = typechild[0].text | |
336 if node[2].kind == xnText and node[2].text.strip() == "*": | |
337 typename = &"ptr {typename}" | |
338 result["basetypes"].add &" {namechild[0].text}* = {mapType(typename)}" | |
339 elif namechild != nil: | |
340 result["basetypes"].add &" {namechild[0].text}* = object" | |
341 # function pointers need to be handled with structs | |
342 elif node.attr("category") == "funcpointer": | |
343 discard | |
344 # preprocessor defines, ignored | |
345 elif node.attr("category") == "define": | |
346 discard | |
347 # bitmask aliases | |
348 elif node.attr("category") == "bitmask": | |
349 if node.hasAttr("alias"): | |
350 let name = node.attr("name") | |
351 let alias = node.attr("alias") | |
352 result["enums"].add &" {name}* = {alias}" | |
353 # distinct resource ID types aka handles | |
354 elif node.attr("category") == "handle": | |
355 if not node.hasAttr("alias"): | |
356 let name = node.child("name")[0].text | |
357 var thetype = mapType(node.child("type")[0].text) | |
358 result["basetypes"].add &" {name}* = distinct {thetype}" | |
359 # enum aliases | |
360 elif node.attr("category") == "enum": | |
361 if node.hasAttr("alias"): | |
362 let name = node.attr("name") | |
363 let alias = node.attr("alias") | |
364 result["enums"].add &" {name}* = {alias}" | |
365 else: | |
366 discard | |
367 | |
368 func serializeCommand(node: XmlNode): (string, string) = | |
369 let | |
370 proto = node.child("proto") | |
371 resulttype = mapType(proto.child("type")[0].text) | |
372 name = proto.child("name")[0].text | |
373 var params: seq[string] | |
374 for param in node: | |
375 if param.tag == "param" and param.attr("api") in ["", "vulkan"]: | |
376 let fieldname = param.child("name")[0].text.strip(chars={'_'}) | |
377 params.add &"{mapName(fieldname)}: {findType(param)}" | |
378 let allparams = params.join(", ") | |
379 return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}") | |
380 | |
381 | |
382 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = | |
383 for k, v in b.pairs: | |
384 if not a.hasKey(k): | |
385 a[k] = @[] | |
386 a[k].add v | |
387 | |
388 | |
389 proc main() = | |
390 let file = getTempDir() / "vk.xml" | |
391 if not os.fileExists(file): | |
392 let client = newHttpClient() | |
393 let glUrl = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml" | |
394 client.downloadFile(glUrl, file) | |
395 | |
396 let api = loadXml(file) | |
397 | |
398 const outdir = "src/vulkan_api/output" | |
399 removeDir outdir | |
400 createDir outdir | |
401 createDir outdir / "platform" | |
402 | |
403 # index all names that are only available on certain platforms | |
404 var platformTypes: Table[string, string] | |
405 for extension in api.findAll("extension"): | |
406 if extension.hasAttr("platform"): | |
407 for thetype in extension.findAll("type"): | |
408 platformTypes[thetype.attr("name")] = extension.attr("platform") | |
409 for command in extension.findAll("command"): | |
410 platformTypes[command.attr("name")] = extension.attr("platform") | |
411 elif extension.attr("name").startsWith("VK_KHR_video"): | |
412 for thetype in extension.findAll("type"): | |
413 platformTypes[thetype.attr("name")] = "provisional" | |
414 for command in extension.findAll("command"): | |
415 platformTypes[command.attr("name")] = "provisional" | |
416 | |
417 var outputFiles = { | |
418 "basetypes": @[ | |
419 "import std/dynlib", | |
420 "import std/tables", | |
421 "import std/strutils", | |
422 "import std/logging", | |
423 "import std/typetraits", | |
424 "import std/macros", | |
425 "type", | |
426 " VkHandle* = distinct uint", | |
427 " VkNonDispatchableHandle* = distinct uint", | |
428 "when defined(linux):", | |
429 " let vulkanLib* = loadLib(\"libvulkan.so.1\")", | |
430 "when defined(windows):", | |
431 " let vulkanLib* = loadLib(\"vulkan-1.dll\")", | |
432 "if vulkanLib == nil:", | |
433 " raise newException(Exception, \"Unable to load vulkan library\")", | |
434 "func VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =", | |
435 " (variant shl 29) or (major shl 22) or (minor shl 12) or patch", | |
436 "", | |
437 """template checkVkResult*(call: untyped) = | |
438 when defined(release): | |
439 discard call | |
440 else: | |
441 # yes, a bit cheap, but this is only for nice debug output | |
442 var callstr = astToStr(call).replace("\n", "") | |
443 while callstr.find(" ") >= 0: | |
444 callstr = callstr.replace(" ", " ") | |
445 debug "CALLING vulkan: ", callstr | |
446 let value = call | |
447 if value != VK_SUCCESS: | |
448 error "Vulkan error: ", astToStr(call), " returned ", $value | |
449 raise newException(Exception, "Vulkan error: " & astToStr(call) & | |
450 " returned " & $value)""", | |
451 """ | |
452 # custom enum iteration (for enum values > 2^16) | |
453 macro enumFullRange(a: typed): untyped = | |
454 newNimNode(nnkBracket).add(a.getType[1][1..^1]) | |
455 | |
456 iterator items*[T: HoleyEnum](E: typedesc[T]): T = | |
457 for a in enumFullRange(E): yield a""", | |
458 ], | |
459 "structs": @["type"], | |
460 "enums": @["type"], | |
461 "commands": @[], | |
462 }.toTable | |
463 outputFiles["basetypes"].add serializeConsts(api) | |
464 outputFiles["basetypes"].add "type" | |
465 | |
466 # enums | |
467 for thetype in api.findAll("type"): | |
468 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"): | |
469 let name = thetype.child("name")[0].text | |
470 outputFiles["enums"].add &" {name}* = distinct VkFlags" | |
471 | |
472 var bitfields: Table[string, string] | |
473 outputFiles["enums"].add "let vkGetInstanceProcAddr = cast[proc(instance: VkInstance, name: cstring): pointer {.stdcall.}](checkedSymAddr(vulkanLib, \"vkGetInstanceProcAddr\"))" | |
474 outputFiles["enums"].add "type" | |
475 for theenum in api.findAll("enums"): | |
476 let (enums, bitFieldName) = serializeEnum(theenum, api) | |
477 outputFiles["enums"].add enums | |
478 if bitFieldName != "": | |
479 bitfields[theenum.attr("name")] = bitFieldName | |
480 | |
481 # bitmask-to-string functions | |
482 for thetype in api.findAll("type"): | |
483 if thetype.attr("name") in bitfields: | |
484 let name = bitfields[thetype.attr("name")] | |
485 let stringfunc = &"proc `$`*(bitset: {name}): string = $toEnums(bitset)" | |
486 if not (stringfunc in outputFiles["enums"]): | |
487 outputFiles["enums"].add stringfunc | |
488 outputFiles["enums"].add "type" | |
489 | |
490 # structs and function types need to be in same "type" block to avoid forward-declarations | |
491 outputFiles["structs"].add serializeFunctiontypes(api) | |
492 for thetype in api.findAll("type"): | |
493 if thetype.attr("category") == "struct" or thetype.attr("category") == "union": | |
494 var outfile = "structs" | |
495 if thetype.attr("name") in platformTypes: | |
496 outfile = "platform/" & platformTypes[thetype.attr("name")] | |
497 if not (outfile in outputFiles): | |
498 outputFiles[outfile] = @[] | |
499 outputFiles[outfile].add serializeStruct(thetype) | |
500 | |
501 # types | |
502 var headerTypes: Table[string, string] | |
503 for types in api.findAll("types"): | |
504 for thetype in types.findAll("type"): | |
505 if thetype.attrsLen == 2 and thetype.hasAttr("requires") and thetype.hasAttr("name") and thetype.attr("requires") != "vk_platform": | |
506 let name = thetype.attr("name") | |
507 let incld = thetype.attr("requires") | |
508 headerTypes[name] = &"{name} {{.header: \"{incld}\".}} = object" | |
509 | |
510 for typesgroup in api.findAll("types"): | |
511 for thetype in typesgroup.findAll("type"): | |
512 outputFiles.update serializeType(thetype, headerTypes) | |
513 | |
514 for typesgroup in api.findAll("types"): | |
515 for node in typesgroup.findAll("type"): | |
516 if node.attr("category") == "handle": | |
517 if not node.hasAttr("alias"): | |
518 let name = node.child("name")[0].text | |
519 outputFiles["basetypes"].add &"proc `$`*(handle: {name}): string = \"{name}(\" & $(uint(handle)) & \")\"" | |
520 outputFiles["basetypes"].add &"proc valid*(handle: {name}): bool = uint(handle) != 0" | |
521 outputFiles["basetypes"].add &"proc reset*(handle: var {name}) = handle = {name}(0)" | |
522 outputFiles["basetypes"].add &"proc `==`*(a, b: {name}): bool = uint(a) == uint(b)" | |
523 | |
524 | |
525 # commands aka functions | |
526 var varDecls: Table[string, string] | |
527 var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs | |
528 for commands in api.findAll("commands"): | |
529 for command in commands.findAll("command"): | |
530 if command.attr("api") != "vulkansc" and not (command.attr("name") in IGNORED_COMMANDS): | |
531 if command.hasAttr("alias"): | |
532 let name = command.attr("name") | |
533 let alias = command.attr("alias") | |
534 let thetype = varDecls[alias].split(":", 1)[1].strip() | |
535 varDecls[name] = &" {name}*: {thetype}" | |
536 procLoads[name] = &" {name} = {alias}" | |
537 else: | |
538 let (name, thetype) = serializeCommand(command) | |
539 varDecls[name] = &" {name}*: {thetype}" | |
540 procLoads[name] = &" {name} = cast[{thetype}](vkGetInstanceProcAddr(instance, \"{name}\"))" | |
541 var declared: seq[string] | |
542 var featureloads: seq[string] | |
543 for feature in api.findAll("feature"): | |
544 if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]: | |
545 let name = feature.attr("name") | |
546 outputFiles["commands"].add &"# feature {name}" | |
547 outputFiles["commands"].add "var" | |
548 for command in feature.findAll("command"): | |
549 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS): | |
550 outputFiles["commands"].add varDecls[command.attr("name")] | |
551 declared.add command.attr("name") | |
552 featureloads.add &"load{name}" | |
553 outputFiles["commands"].add &"proc load{name}*(instance: VkInstance) =" | |
554 for command in feature.findAll("command"): | |
555 if not (command.attr("name") in IGNORED_COMMANDS & GLOBAL_COMMANDS): | |
556 outputFiles["commands"].add procLoads[command.attr("name")] | |
557 outputFiles["commands"].add "" | |
558 outputFiles["commands"].add ["proc loadVulkan*(instance: VkInstance) ="] | |
559 for l in featureloads: | |
560 outputFiles["commands"].add [&" {l}(instance)"] | |
561 outputFiles["commands"].add "" | |
562 | |
563 # for promoted extensions, dependants need to call the load-function of the promoted feature/extension | |
564 # use table to store promotions | |
565 var promotions: Table[string, string] | |
566 for extensions in api.findAll("extensions"): | |
567 for extension in extensions.findAll("extension"): | |
568 if extension.hasAttr("promotedto"): | |
569 promotions[extension.attr("name")] = extension.attr("promotedto") | |
570 | |
571 var extensionDependencies: Table[string, (seq[string], XmlNode)] | |
572 var features: seq[string] | |
573 for feature in api.findAll("feature"): | |
574 features.add feature.attr("name") | |
575 for extensions in api.findAll("extensions"): | |
576 for extension in extensions.findAll("extension"): | |
577 let name = extension.attr("name") | |
578 extensionDependencies[name] = (@[], extension) | |
579 if extension.hasAttr("depends"): | |
580 extensionDependencies[name] = (extension.attr("depends").split("+"), extension) | |
581 if extension.attr("depends").startsWith("("): # no need for full tree parser, only single place where we can use a feature | |
582 let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+") | |
583 extensionDependencies[name] = (dependencies, extension) | |
584 if name in SPECIAL_DEPENDENCIES: | |
585 extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name] | |
586 | |
587 # order dependencies to generate them in correct order | |
588 var dependencyOrderedExtensions: OrderedTable[string, XmlNode] | |
589 while extensionDependencies.len > 0: | |
590 var delkeys: seq[string] | |
591 for extensionName, (dependencies, extension) in extensionDependencies.pairs: | |
592 var missingExtension = false | |
593 for dep in dependencies: | |
594 let realdep = promotions.getOrDefault(dep, dep) | |
595 if not (realdep in dependencyOrderedExtensions) and not (realdep in features): | |
596 missingExtension = true | |
597 break | |
598 if not missingExtension: | |
599 dependencyOrderedExtensions[extensionName] = extension | |
600 delkeys.add extensionName | |
601 for key in delkeys: | |
602 extensionDependencies.del key | |
603 | |
604 var extensionLoaderMap: Table[string, Table[string, string]] | |
605 for extension in dependencyOrderedExtensions.values: | |
606 if extension.hasAttr("promotedto"): # will be loaded in promoted place | |
607 continue | |
608 if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]: | |
609 var file = "commands" | |
610 var platform = extension.attr("platform") | |
611 if extension.attr("name").startsWith("VK_KHR_video"): | |
612 platform = "provisional" | |
613 if platform != "": | |
614 file = "platform/" & platform | |
615 let name = extension.attr("name") | |
616 if extension.findAll("command").len > 0: | |
617 outputFiles[file].add &"# extension {name}" | |
618 outputFiles[file].add "var" | |
619 for command in extension.findAll("command"): | |
620 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS): | |
621 outputFiles[file].add varDecls[command.attr("name")] | |
622 declared.add command.attr("name") | |
623 outputFiles[file].add &"proc load{name}*(instance: VkInstance) =" | |
624 if not (platform in extensionLoaderMap): | |
625 extensionLoaderMap[platform] = Table[string, string]() | |
626 extensionLoaderMap[platform][name] = &"load{name}" | |
627 var addedFunctionBody = false | |
628 if extension.hasAttr("depends"): | |
629 for dependency in extension.attr("depends").split("+"): | |
630 # need to check since some extensions have no commands and therefore no load-function | |
631 outputFiles[file].add &" load{promotions.getOrDefault(dependency, dependency)}(instance)" | |
632 addedFunctionBody = true | |
633 for command in extension.findAll("command"): | |
634 outputFiles[file].add procLoads[command.attr("name")] | |
635 addedFunctionBody = true | |
636 if not addedFunctionBody: | |
637 outputFiles[file].add " discard" | |
638 outputFiles[file].add "" | |
639 | |
640 var mainout: seq[string] | |
641 for section in ["basetypes", "enums", "structs", "commands"]: | |
642 mainout.add outputFiles[section] | |
643 mainout.add "var EXTENSION_LOADERS = {" | |
644 for extension, loader in extensionLoaderMap[""].pairs: | |
645 mainout.add &" \"{extension}\": {loader}," | |
646 mainout.add "}.toTable" | |
647 for platform in api.findAll("platform"): | |
648 mainout.add &"when defined({platform.attr(\"protect\")}):" | |
649 mainout.add &" ../vulkan/include platform/{platform.attr(\"name\")}" | |
650 if platform.attr("name") in extensionLoaderMap: | |
651 for extension, loader in extensionLoaderMap[platform.attr("name")].pairs: | |
652 mainout.add &" EXTENSION_LOADERS[\"{extension}\"] = {loader}" | |
653 | |
654 mainout.add "" | |
655 mainout.add "proc loadExtension*(instance: VkInstance, extension: string) = EXTENSION_LOADERS[extension](instance)" | |
656 mainout.add "" | |
657 mainout.add "# load global functions immediately" | |
658 mainout.add "block globalFunctions:" | |
659 mainout.add " let instance = VkInstance(0)" | |
660 for l in GLOBAL_COMMANDS: | |
661 mainout.add procLoads[l] | |
662 mainout.add "" | |
663 # produces error if enable both implicit converters | |
664 # mainout.add "converter VkBool2NimBool*(a: VkBool32): bool = a > 0" | |
665 mainout.add "converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)" | |
666 | |
667 writeFile outdir / &"api.nim", mainout.join("\n") | |
668 | |
669 | |
670 for filename, filecontent in outputFiles.pairs: | |
671 if filename.startsWith("platform/"): | |
672 writeFile outdir / &"{filename}.nim", (@[ | |
673 "type" | |
674 ] & filecontent).join("\n") | |
675 | |
676 when isMainModule: | |
677 main() |