Mercurial > games > semicongine
annotate 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 |
rev | line source |
---|---|
78 | 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 = { | |
79 | 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"], | |
78 | 54 }.toTable |
55 MAP_KEYWORD = { | |
56 "object": "theobject", | |
57 "type": "thetype", | |
58 }.toTable | |
79 | 59 SPECIAL_DEPENDENCIES = { |
60 "VK_NV_ray_tracing": "VK_KHR_ray_tracing_pipeline", | |
61 }.toTable | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
62 # will be directly loaded at startup |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
63 IGNORED_COMMANDS = @["vkGetInstanceProcAddr"] |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
64 # can be loaded without a vulkan instance |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
65 GLOBAL_COMMANDS = @[ |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
66 "vkEnumerateInstanceVersion", |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
67 "vkEnumerateInstanceExtensionProperties", |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
68 "vkEnumerateInstanceLayerProperties", |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
69 "vkCreateInstance", |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
70 ] |
78 | 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 | |
87 # serializers | |
80 | 88 func serializeEnum(node: XmlNode, api: XmlNode): seq[string] = |
78 | 89 let name = node.attr("name") |
90 if name == "": | |
91 return result | |
92 | |
80 | 93 var reservedNames: seq[string] |
94 for t in api.findAll("type"): | |
95 reservedNames.add t.attr("name").replace("_", "").toLower() | |
96 | |
78 | 97 # find additional enum defintion in feature definitions |
98 var values: Table[int, string] | |
80 | 99 for feature in api.findAll("feature"): |
78 | 100 for require in feature.findAll("require"): |
101 for theenum in require.findAll("enum"): | |
102 if theenum.attr("extends") == name: | |
103 if theenum.hasAttr("offset"): | |
104 let enumBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | |
105 var value = smartParseInt(theenum.attr("offset")) + enumBase | |
106 if theenum.attr("dir") == "-": | |
107 value = -value | |
108 values[value] = theenum.attr("name") | |
109 elif theenum.hasAttr("value"): | |
110 var value = smartParseInt(theenum.attr("value")) | |
111 if theenum.attr("dir") == "-": | |
112 value = -value | |
113 values[value] = theenum.attr("name") | |
114 elif theenum.hasAttr("bitpos"): | |
115 var value = smartParseInt(theenum.attr("bitpos")) | |
116 if theenum.attr("dir") == "-": | |
117 value = -value | |
118 values[value] = theenum.attr("name") | |
119 elif theenum.hasAttr("alias"): | |
120 discard | |
121 else: | |
122 raise newException(Exception, &"Unknown extension value: {feature}\nvalue:{theenum}") | |
123 # find additional enum defintion in extension definitions | |
80 | 124 for extension in api.findAll("extension"): |
78 | 125 let extensionNumber = parseInt(extension.attr("number")) |
126 let enumBase = 1000000000 + (extensionNumber - 1) * 1000 | |
127 for require in extension.findAll("require"): | |
128 for theenum in require.findAll("enum"): | |
129 if theenum.attr("extends") == name: | |
130 if theenum.hasAttr("offset"): | |
131 if theenum.hasAttr("extnumber"): | |
132 let otherBase = 1000000000 + (smartParseInt(theenum.attr("extnumber")) - 1) * 1000 | |
133 var value = smartParseInt(theenum.attr("offset")) + otherBase | |
134 if theenum.attr("dir") == "-": | |
135 value = -value | |
136 values[value] = theenum.attr("name") | |
137 else: | |
138 var value = smartParseInt(theenum.attr("offset")) + enumBase | |
139 if theenum.attr("dir") == "-": | |
140 value = -value | |
141 values[value] = theenum.attr("name") | |
142 elif theenum.hasAttr("value"): | |
143 var value = smartParseInt(theenum.attr("value")) | |
144 if theenum.attr("dir") == "-": | |
145 value = -value | |
146 values[value] = theenum.attr("name") | |
147 elif theenum.hasAttr("bitpos"): | |
148 var value = smartParseInt(theenum.attr("bitpos")) | |
149 if theenum.attr("dir") == "-": | |
150 value = -value | |
151 values[value] = theenum.attr("name") | |
152 elif theenum.hasAttr("alias"): | |
153 discard | |
154 else: | |
155 raise newException(Exception, &"Unknown extension value: {extension}\nvalue:{theenum}") | |
156 | |
157 # generate enums | |
158 if node.attr("type") == "enum": | |
159 for value in node.findAll("enum"): | |
160 if value.hasAttr("alias"): | |
161 continue | |
162 if value.attr("value").startsWith("0x"): | |
163 values[parseHexInt(value.attr("value"))] = value.attr("name") | |
164 else: | |
165 values[smartParseInt(value.attr("value"))] = value.attr("name") | |
166 if values.len > 0: | |
167 result.add " " & name & "* {.size: sizeof(cint).} = enum" | |
168 for (value, name) in tableSorted(values): | |
80 | 169 var thename = name |
170 if name.replace("_", "").toLower() in reservedNames: | |
171 thename = thename & "_ENUM" | |
172 let enumEntry = &" {thename} = {value}" | |
78 | 173 result.add enumEntry |
174 | |
175 # generate bitsets (normal enums in the C API, but bitfield-enums in Nim) | |
176 elif node.attr("type") == "bitmask": | |
177 for value in node.findAll("enum"): | |
79 | 178 if value.hasAttr("bitpos"): |
179 values[smartParseInt(value.attr("bitpos"))] = value.attr("name") | |
180 elif node.attr("name") == "VkVideoEncodeRateControlModeFlagBitsKHR": # special exception, for some reason this has values instead of bitpos | |
181 values[smartParseInt(value.attr("value"))] = value.attr("name") | |
78 | 182 if values.len > 0: |
183 if node.hasAttr("bitwidth"): | |
184 result.add " " & name & "* {.size: " & $(smartParseInt(node.attr("bitwidth")) div 8) & ".} = enum" | |
185 else: | |
186 result.add " " & name & "* {.size: sizeof(cint).} = enum" | |
187 for (bitpos, enumvalue) in tableSorted(values): | |
188 var value = "00000000000000000000000000000000"# makes the bit mask nicely visible | |
189 if node.hasAttr("bitwidth"): # assumes this is always 64 | |
190 value = value & value | |
191 value[^(bitpos + 1)] = '1' | |
192 let enumEntry = &" {enumvalue} = 0b{value}" | |
193 if not (enumEntry in result): # the specs define duplicate entries for backwards compat | |
194 result.add enumEntry | |
195 let cApiName = name.replace("FlagBits", "Flags") | |
196 if node.hasAttr("bitwidth"): # assumes this is always 64 | |
197 if values.len > 0: | |
198 result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | |
199 for flag in flags: | |
200 result = {cApiName}(uint64(result) or uint(flag))""" | |
201 result.add "type" | |
202 else: | |
203 if values.len > 0: | |
204 result.add &"""converter BitsetToNumber*(flags: openArray[{name}]): {cApiName} = | |
205 for flag in flags: | |
206 result = {cApiName}(uint(result) or uint(flag))""" | |
207 result.add "type" | |
208 | |
80 | 209 func serializeStruct(node: XmlNode): seq[string] = |
78 | 210 let name = node.attr("name") |
211 var union = "" | |
212 if node.attr("category") == "union": | |
79 | 213 union = "{.union.} " |
214 result.add &" {name}* {union}= object" | |
78 | 215 for member in node.findAll("member"): |
216 if not member.hasAttr("api") or member.attr("api") == "vulkan": | |
217 let fieldname = member.child("name")[0].text.strip(chars={'_'}) | |
218 var fieldtype = member.child("type")[0].text.strip(chars={'_'}) | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
219 # detect pointers |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
220 for child in member: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
221 if child.kind == xnText and child.text.strip() == "*": |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
222 fieldtype = &"ptr {mapType(fieldtype)}" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
223 elif child.kind == xnText and child.text.strip() == "* const*": |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
224 fieldtype = "cstringArray" |
78 | 225 fieldtype = mapType(fieldtype) |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
226 # detect arrays |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
227 for child in member: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
228 if child.kind == xnText and child.text.endsWith("]"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
229 var thelen = "" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
230 if "[" in child.text: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
231 thelen = child.text.strip(chars={'[', ']'}).replace("][", "*") |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
232 else: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
233 thelen = member.child("enum")[0].text |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
234 fieldtype = &"array[{thelen}, {fieldtype}]" |
78 | 235 result.add &" {mapName(fieldname)}*: {fieldtype}" |
236 | |
237 func serializeFunctiontypes(api: XmlNode): seq[string] = | |
238 for node in api.findAll("type"): | |
239 if node.attr("category") == "funcpointer": | |
240 let name = node[1][0] | |
241 let returntype = mapType(node[0].text[8 .. ^1].split('(', 1)[0]) | |
242 var params: seq[string] | |
243 for i in countup(3, node.len - 1, 2): | |
244 var paramname = node[i + 1].text.split(',', 1)[0].split(')', 1)[0] | |
245 var paramtype = node[i][0].text | |
246 if paramname[0] == '*': | |
247 paramname = paramname.rsplit(" ", 1)[1] | |
248 paramtype = "ptr " & paramtype | |
249 paramname = mapName(paramname) | |
250 params.add &"{paramname}: {mapType(paramtype)}" | |
251 let paramsstr = params.join(", ") | |
79 | 252 result.add(&" {name}* = proc({paramsstr}): {returntype} {{.cdecl.}}") |
78 | 253 |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
254 func serializeConsts(api: XmlNode): seq[string] = |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
255 result = @["const"] |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
256 for enums in api.findAll("enums"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
257 if enums.attr("name") == "API Constants": |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
258 for theenum in enums.findAll("enum"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
259 if theenum.hasAttr("alias"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
260 result.add &" {theenum.attr(\"name\")}* = {theenum.attr(\"alias\")}" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
261 else: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
262 var value = theenum.attr("value").strip(chars={'(', ')'}) |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
263 if value.endsWith("U"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
264 value = value[0..^2] & "'u32" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
265 elif value.endsWith("ULL"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
266 value = value[0..^4] & "'u64" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
267 if value[0] == '~': |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
268 value = "not " & value[1..^1] |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
269 result.add &" {theenum.attr(\"name\")}*: {mapType(theenum.attr(\"type\"))} = {value}" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
270 |
79 | 271 func serializeType(node: XmlNode, headerTypes: Table[string, string]): Table[string, seq[string]] = |
78 | 272 if node.attrsLen == 0: |
273 return | |
274 if node.attr("requires") == "vk_platform" or node.attr("category") == "include": | |
275 return | |
276 result["basetypes"] = @[] | |
277 result["enums"] = @[] | |
278 | |
279 # include-defined types (in platform headers) | |
79 | 280 if node.attr("name") in headerTypes: |
281 for platform in PLATFORM_HEADER_MAP[node.attr("requires")]: | |
282 let platformfile = "platform/" & platform | |
283 if not result.hasKey(platformfile): | |
284 result[platformfile] = @[] | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
285 result[platformfile].add " " & node.attr("name").strip(chars={'_'}) & " *{.header: \"" & node.attr("requires") & "\".} = object" |
78 | 286 # generic base types |
287 elif node.attr("category") == "basetype": | |
288 let typechild = node.child("type") | |
289 let namechild = node.child("name") | |
290 if typechild != nil and namechild != nil: | |
291 var typename = typechild[0].text | |
292 if node[2].kind == xnText and node[2].text.strip() == "*": | |
293 typename = &"ptr {typename}" | |
294 result["basetypes"].add &" {namechild[0].text}* = {mapType(typename)}" | |
295 elif namechild != nil: | |
296 result["basetypes"].add &" {namechild[0].text}* = object" | |
297 # function pointers need to be handled with structs | |
298 elif node.attr("category") == "funcpointer": | |
299 discard | |
300 # preprocessor defines, ignored | |
301 elif node.attr("category") == "define": | |
302 discard | |
303 # bitmask aliases | |
304 elif node.attr("category") == "bitmask": | |
305 if node.hasAttr("alias"): | |
306 let name = node.attr("name") | |
307 let alias = node.attr("alias") | |
308 result["enums"].add &" {name}* = {alias}" | |
309 # distinct resource ID types aka handles | |
310 elif node.attr("category") == "handle": | |
311 if not node.hasAttr("alias"): | |
312 let name = node.child("name")[0].text | |
313 var thetype = mapType(node.child("type")[0].text) | |
314 result["basetypes"].add &" {name}* = distinct {thetype}" | |
315 # enum aliases | |
316 elif node.attr("category") == "enum": | |
317 if node.hasAttr("alias"): | |
318 let name = node.attr("name") | |
319 let alias = node.attr("alias") | |
320 result["enums"].add &" {name}* = {alias}" | |
321 else: | |
322 discard | |
323 | |
79 | 324 func serializeCommand(node: XmlNode): (string, string) = |
325 let | |
326 proto = node.child("proto") | |
327 resulttype = mapType(proto.child("type")[0].text) | |
328 name = proto.child("name")[0].text | |
329 var params: seq[string] | |
330 for param in node: | |
331 if param.tag == "param" and param.attr("api") in ["", "vulkan"]: | |
332 let fieldname = param.child("name")[0].text.strip(chars={'_'}) | |
333 var fieldtype = param.child("type")[0].text.strip(chars={'_'}) | |
334 if param[param.len - 2].kind == xnText and param[param.len - 2].text.strip() == "*": | |
335 fieldtype = &"ptr {mapType(fieldtype)}" | |
336 fieldtype = mapType(fieldtype) | |
337 params.add &"{mapName(fieldname)}: {fieldtype}" | |
338 let allparams = params.join(", ") | |
339 return (name, &"proc({allparams}): {resulttype} {{.stdcall.}}") | |
340 | |
78 | 341 |
342 proc update(a: var Table[string, seq[string]], b: Table[string, seq[string]]) = | |
343 for k, v in b.pairs: | |
344 if not a.hasKey(k): | |
345 a[k] = @[] | |
346 a[k].add v | |
347 | |
348 | |
349 proc main() = | |
81 | 350 let file = getTempDir() / "vk.xml" |
351 if not os.fileExists(file): | |
78 | 352 let client = newHttpClient() |
353 let glUrl = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml" | |
81 | 354 client.downloadFile(glUrl, file) |
78 | 355 |
81 | 356 let api = loadXml(file) |
78 | 357 |
358 const outdir = "src/vulkan_api/output" | |
359 removeDir outdir | |
360 createDir outdir | |
361 createDir outdir / "platform" | |
362 | |
363 # index all names that are only available on certain platforms | |
364 var platformTypes: Table[string, string] | |
365 for extension in api.findAll("extension"): | |
366 if extension.hasAttr("platform"): | |
367 for thetype in extension.findAll("type"): | |
368 platformTypes[thetype.attr("name")] = extension.attr("platform") | |
369 for command in extension.findAll("command"): | |
370 platformTypes[command.attr("name")] = extension.attr("platform") | |
371 elif extension.attr("name").startsWith("VK_KHR_video"): | |
372 for thetype in extension.findAll("type"): | |
79 | 373 platformTypes[thetype.attr("name")] = "provisional" |
78 | 374 for command in extension.findAll("command"): |
79 | 375 platformTypes[command.attr("name")] = "provisional" |
78 | 376 |
377 var outputFiles = { | |
79 | 378 "basetypes": @[ |
379 "import std/dynlib", | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
380 "import std/tables", |
79 | 381 "type", |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
382 " VkHandle* = distinct uint", |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
383 " VkNonDispatchableHandle* = distinct uint", |
79 | 384 "when defined(linux):", |
385 " let vulkanLib* = loadLib(\"libvulkan.so.1\")", | |
386 "when defined(windows):", | |
387 " let vulkanLib* = loadLib(\"vulkan-1.dll\")", | |
388 "if vulkanLib == nil:", | |
389 " raise newException(Exception, \"Unable to load vulkan library\")", | |
80 | 390 "func VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =", |
391 " (variant shl 29) or (major shl 22) or (minor shl 12) or patch", | |
392 "", | |
81 | 393 """template checkVkResult*(call: untyped) = |
394 when defined(release): | |
395 discard call | |
396 else: | |
397 # yes, a bit cheap, but this is only for nice debug output | |
398 var callstr = astToStr(call).replace("\n", "") | |
399 while callstr.find(" ") >= 0: | |
400 callstr = callstr.replace(" ", " ") | |
401 debug "CALLING vulkan: ", callstr | |
402 let value = call | |
403 if value != VK_SUCCESS: | |
404 error "Vulkan error: ", astToStr(call), " returned ", $value | |
405 raise newException(Exception, "Vulkan error: " & astToStr(call) & | |
406 " returned " & $value)""", | |
79 | 407 ], |
408 "structs": @["type"], | |
409 "enums": @["type"], | |
410 "commands": @[], | |
78 | 411 }.toTable |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
412 outputFiles["basetypes"].add serializeConsts(api) |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
413 outputFiles["basetypes"].add "type" |
78 | 414 |
415 # enums | |
416 for thetype in api.findAll("type"): | |
417 if thetype.attr("category") == "bitmask" and not thetype.hasAttr("alias") and (not thetype.hasAttr("api") or thetype.attr("api") == "vulkan"): | |
418 let name = thetype.child("name")[0].text | |
419 outputFiles["enums"].add &" {name}* = distinct VkFlags" | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
420 outputFiles["enums"].add "let vkGetInstanceProcAddr = cast[proc(instance: VkInstance, name: cstring): pointer {.stdcall.}](checkedSymAddr(vulkanLib, \"vkGetInstanceProcAddr\"))" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
421 outputFiles["enums"].add "type" |
78 | 422 for theenum in api.findAll("enums"): |
423 outputFiles["enums"].add serializeEnum(theenum, api) | |
424 | |
425 # structs and function types need to be in same "type" block to avoid forward-declarations | |
426 outputFiles["structs"].add serializeFunctiontypes(api) | |
427 for thetype in api.findAll("type"): | |
428 if thetype.attr("category") == "struct" or thetype.attr("category") == "union": | |
429 var outfile = "structs" | |
430 if thetype.attr("name") in platformTypes: | |
431 outfile = "platform/" & platformTypes[thetype.attr("name")] | |
432 if not (outfile in outputFiles): | |
433 outputFiles[outfile] = @[] | |
80 | 434 outputFiles[outfile].add serializeStruct(thetype) |
78 | 435 |
436 # types | |
79 | 437 var headerTypes: Table[string, string] |
438 for types in api.findAll("types"): | |
439 for thetype in types.findAll("type"): | |
440 if thetype.attrsLen == 2 and thetype.hasAttr("requires") and thetype.hasAttr("name") and thetype.attr("requires") != "vk_platform": | |
441 let name = thetype.attr("name") | |
442 let incld = thetype.attr("requires") | |
443 headerTypes[name] = &"{name} {{.header: \"{incld}\".}} = object" | |
444 | |
78 | 445 for typesgroup in api.findAll("types"): |
446 for thetype in typesgroup.findAll("type"): | |
79 | 447 outputFiles.update serializeType(thetype, headerTypes) |
448 | |
449 # commands aka functions | |
450 var varDecls: Table[string, string] | |
451 var procLoads: Table[string, string] # procloads need to be packed into feature/extension loader procs | |
452 for commands in api.findAll("commands"): | |
453 for command in commands.findAll("command"): | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
454 if command.attr("api") != "vulkansc" and not (command.attr("name") in IGNORED_COMMANDS): |
79 | 455 if command.hasAttr("alias"): |
456 let name = command.attr("name") | |
457 let alias = command.attr("alias") | |
458 let thetype = varDecls[alias].split(":", 1)[1].strip() | |
459 varDecls[name] = &" {name}*: {thetype}" | |
460 procLoads[name] = &" {name} = {alias}" | |
461 else: | |
462 let (name, thetype) = serializeCommand(command) | |
463 varDecls[name] = &" {name}*: {thetype}" | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
464 procLoads[name] = &" {name} = cast[{thetype}](vkGetInstanceProcAddr(instance, \"{name}\"))" |
79 | 465 var declared: seq[string] |
466 var featureloads: seq[string] | |
467 for feature in api.findAll("feature"): | |
468 if feature.attr("api") in ["vulkan", "vulkan,vulkansc"]: | |
469 let name = feature.attr("name") | |
470 outputFiles["commands"].add &"# feature {name}" | |
471 outputFiles["commands"].add "var" | |
472 for command in feature.findAll("command"): | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
473 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS): |
79 | 474 outputFiles["commands"].add varDecls[command.attr("name")] |
475 declared.add command.attr("name") | |
476 featureloads.add &"load{name}" | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
477 outputFiles["commands"].add &"proc load{name}*(instance: VkInstance) =" |
79 | 478 for command in feature.findAll("command"): |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
479 if not (command.attr("name") in IGNORED_COMMANDS & GLOBAL_COMMANDS): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
480 outputFiles["commands"].add procLoads[command.attr("name")] |
79 | 481 outputFiles["commands"].add "" |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
482 outputFiles["commands"].add ["proc loadVulkan*(instance: VkInstance) ="] |
79 | 483 for l in featureloads: |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
484 outputFiles["commands"].add [&" {l}(instance)"] |
79 | 485 outputFiles["commands"].add "" |
486 | |
487 # for promoted extensions, dependants need to call the load-function of the promoted feature/extension | |
488 # use table to store promotions | |
489 var promotions: Table[string, string] | |
490 for extensions in api.findAll("extensions"): | |
491 for extension in extensions.findAll("extension"): | |
492 if extension.hasAttr("promotedto"): | |
493 promotions[extension.attr("name")] = extension.attr("promotedto") | |
494 | |
495 var extensionDependencies: Table[string, (seq[string], XmlNode)] | |
496 var features: seq[string] | |
497 for feature in api.findAll("feature"): | |
498 features.add feature.attr("name") | |
499 for extensions in api.findAll("extensions"): | |
500 for extension in extensions.findAll("extension"): | |
501 let name = extension.attr("name") | |
502 extensionDependencies[name] = (@[], extension) | |
503 if extension.hasAttr("depends"): | |
504 extensionDependencies[name] = (extension.attr("depends").split("+"), extension) | |
505 if extension.attr("depends").startsWith("("): # no need for full tree parser, only single place where we can use a feature | |
506 let dependencies = extension.attr("depends").rsplit({')'}, 1)[1][1 .. ^1].split("+") | |
507 extensionDependencies[name] = (dependencies, extension) | |
508 if name in SPECIAL_DEPENDENCIES: | |
509 extensionDependencies[name][0].add SPECIAL_DEPENDENCIES[name] | |
510 | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
511 # order dependencies to generate them in correct order |
79 | 512 var dependencyOrderedExtensions: OrderedTable[string, XmlNode] |
513 while extensionDependencies.len > 0: | |
514 var delkeys: seq[string] | |
515 for extensionName, (dependencies, extension) in extensionDependencies.pairs: | |
516 var missingExtension = false | |
517 for dep in dependencies: | |
518 let realdep = promotions.getOrDefault(dep, dep) | |
519 if not (realdep in dependencyOrderedExtensions) and not (realdep in features): | |
520 missingExtension = true | |
521 break | |
522 if not missingExtension: | |
523 dependencyOrderedExtensions[extensionName] = extension | |
524 delkeys.add extensionName | |
525 for key in delkeys: | |
526 extensionDependencies.del key | |
527 | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
528 var extensionLoaderMap: Table[string, Table[string, string]] |
79 | 529 for extension in dependencyOrderedExtensions.values: |
530 if extension.hasAttr("promotedto"): # will be loaded in promoted place | |
531 continue | |
532 if extension.attr("supported") in ["", "vulkan", "vulkan,vulkansc"]: | |
533 var file = "commands" | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
534 var platform = extension.attr("platform") |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
535 if extension.attr("name").startsWith("VK_KHR_video"): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
536 platform = "provisional" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
537 if platform != "": |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
538 file = "platform/" & platform |
79 | 539 let name = extension.attr("name") |
540 if extension.findAll("command").len > 0: | |
541 outputFiles[file].add &"# extension {name}" | |
542 outputFiles[file].add "var" | |
543 for command in extension.findAll("command"): | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
544 if not (command.attr("name") in declared) and not (command.attr("name") in IGNORED_COMMANDS): |
79 | 545 outputFiles[file].add varDecls[command.attr("name")] |
546 declared.add command.attr("name") | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
547 outputFiles[file].add &"proc load{name}*(instance: VkInstance) =" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
548 if not (platform in extensionLoaderMap): |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
549 extensionLoaderMap[platform] = Table[string, string]() |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
550 extensionLoaderMap[platform][name] = &"load{name}" |
79 | 551 var addedFunctionBody = false |
552 if extension.hasAttr("depends"): | |
553 for dependency in extension.attr("depends").split("+"): | |
554 # need to check since some extensions have no commands and therefore no load-function | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
555 outputFiles[file].add &" load{promotions.getOrDefault(dependency, dependency)}(instance)" |
79 | 556 addedFunctionBody = true |
557 for command in extension.findAll("command"): | |
558 outputFiles[file].add procLoads[command.attr("name")] | |
559 addedFunctionBody = true | |
560 if not addedFunctionBody: | |
561 outputFiles[file].add " discard" | |
562 outputFiles[file].add "" | |
563 | |
564 var mainout: seq[string] | |
565 for section in ["basetypes", "enums", "structs", "commands"]: | |
566 mainout.add outputFiles[section] | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
567 mainout.add "var EXTENSION_LOADERS = {" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
568 for extension, loader in extensionLoaderMap[""].pairs: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
569 mainout.add &" \"{extension}\": {loader}," |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
570 mainout.add "}.toTable" |
80 | 571 for platform in api.findAll("platform"): |
572 mainout.add &"when defined({platform.attr(\"protect\")}):" | |
573 mainout.add &" include platform/{platform.attr(\"name\")}" | |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
574 if platform.attr("name") in extensionLoaderMap: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
575 for extension, loader in extensionLoaderMap[platform.attr("name")].pairs: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
576 mainout.add &" EXTENSION_LOADERS[\"{extension}\"] = {loader}" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
577 |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
578 mainout.add "" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
579 mainout.add "proc loadExtension*(instance: VkInstance, extension: string) = EXTENSION_LOADERS[extension](instance)" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
580 mainout.add "" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
581 mainout.add "# load global functions immediately" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
582 mainout.add "block globalFunctions:" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
583 mainout.add " let instance = VkInstance(0)" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
584 for l in GLOBAL_COMMANDS: |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
585 mainout.add procLoads[l] |
81 | 586 writeFile outdir / &"api.nim", mainout.join("\n") |
79 | 587 |
83
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
588 mainout.add "" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
589 mainout.add "converter VkBool2NimBool*(a: VkBool32): bool = a > 0" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
590 mainout.add "converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)" |
5e19aead2b61
add: new vulkan api wrapper, not done yet
Sam <sam@basx.dev>
parents:
81
diff
changeset
|
591 |
78 | 592 for filename, filecontent in outputFiles.pairs: |
79 | 593 if filename.startsWith("platform/"): |
594 writeFile outdir / &"{filename}.nim", (@[ | |
595 "type" | |
596 ] & filecontent).join("\n") | |
78 | 597 |
598 when isMainModule: | |
599 main() |