Mercurial > games > semicongine
annotate svk/generate.nim @ 1483:55911f736a5a default tip
add: more shit
author | sam <sam@basx.dev> |
---|---|
date | Tue, 29 Apr 2025 00:17:33 +0700 |
parents | bca8f65ed4ed |
children |
rev | line source |
---|---|
1479 | 1 import std/strtabs |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
2 import std/syncio |
1479 | 3 import std/xmltree |
4 import std/tables | |
5 import std/options | |
6 import std/sequtils | |
7 import std/strutils | |
8 import std/strformat | |
9 import std/xmlparser | |
10 import std/os | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
11 import std/osproc |
1479 | 12 import std/paths |
13 | |
14 # helpers | |
15 func smartParseInt(value: string): int = | |
16 if value.startsWith("0x"): | |
17 parseHexInt(value) | |
18 else: | |
19 parseInt(value) | |
20 | |
21 const TYPEMAP = { | |
22 "void": "void", | |
23 "char": "char", | |
24 "float": "float32", | |
25 "double": "float64", | |
26 "int8_t": "int8", | |
27 "uint8_t": "uint8", | |
28 "int16_t": "int16", | |
29 "uint16_t": "uint16", | |
30 "int32_t": "int32", | |
31 "uint32_t": "uint32", | |
32 "uint64_t": "uint64", | |
33 "int64_t": "int64", | |
34 "size_t": "csize_t", | |
35 "int": "cint", | |
36 }.toTable | |
37 # load xml | |
38 let xml = (system.currentSourcePath.parentDir() / "vk.xml").loadXml() | |
39 let platforms = xml.findAll("platforms")[0] | |
40 let types = xml.findAll("types")[0] | |
41 let xmlenums = xml.findAll("enums") | |
42 let commands = xml.findAll("commands")[0] | |
43 let features = xml.findAll("feature") # features has extends="<ENUM>" | |
44 let extensions = xml.findAll("extensions")[0] # extensions has extends="<ENUM>" | |
45 let formats = xml.findAll("formats")[0] | |
46 | |
47 # gather all enums | |
48 | |
49 type | |
50 EnumEntry = object | |
51 name: string | |
52 value: string | |
53 | |
54 EnumDef = object | |
55 name: string | |
56 values: seq[EnumEntry] | |
57 isBitmask: bool | |
58 | |
59 ConstantDef = object | |
60 name: string | |
61 datatype: string | |
62 value: string | |
63 | |
64 var consts: seq[ConstantDef] | |
65 var enums: Table[string, EnumDef] | |
66 | |
67 func addValue(edef: var EnumDef, n: XmlNode) = | |
68 if n.attr("deprecated") != "aliased" and n.attr("alias") == "": | |
69 if n.attr("name") in edef.values.mapIt(it.name): | |
70 return | |
71 | |
72 var value = "" | |
73 if n.attr("value") != "": | |
74 value = n.attr("value") | |
75 elif n.attr("bitpos") != "": | |
76 value = $(1 shl parseInt(n.attr("bitpos"))) | |
77 elif n.attr("offset") != "": | |
78 var enumBase = 1000000000 | |
79 if n.attr("extnumber") != "": | |
80 enumBase += (smartParseInt(n.attr("extnumber")) - 1) * 1000 | |
81 var v = smartParseInt(n.attr("offset")) + enumBase | |
82 if n.attr("dir") == "-": | |
83 v = -v | |
84 value = $(v) | |
85 | |
1482 | 86 if value notin edef.values.mapIt(it.value): |
87 edef.values.add EnumEntry(name: n.attr("name"), value: value) | |
1479 | 88 |
1481 | 89 func doTypename(typename: string, isPointer: bool): string = |
90 result = TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars = {'_'}) | |
91 | |
92 if typename == "void": | |
93 assert isPointer | |
94 | |
95 if isPointer: | |
96 if typename == "void": | |
97 result = "pointer" | |
98 elif typename == "char": | |
99 result = "cstring" | |
100 else: | |
101 result = "ptr " & result | |
102 | |
103 func doIdentifier(typename: string): string = | |
104 if typename in ["type", "object"]: | |
105 return &"`{typename}`" | |
106 return typename.strip() | |
107 | |
108 func doMember(typename, theType: string, isPointer: bool, value: string): string = | |
109 if value == "": | |
110 &"{doIdentifier(typename)}: {doTypename(theType, isPointer)}" | |
111 else: | |
112 &"{doIdentifier(typename)}: {doTypename(theType, isPointer)} = {value}" | |
113 | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
114 func memberDecl(n: XmlNode): string = |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
115 for i in 0 ..< n.len: |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
116 if n[i].kind == xnElement and n[i].tag == "comment": |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
117 n.delete(i) |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
118 break |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
119 assert n.tag == "member" |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
120 if n.len == 2: |
1481 | 121 return doMember(n[1][0].text, n[0][0].text, false, n.attr("values")) |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
122 elif n.len == 3: |
1481 | 123 assert "*" notin n[0][0].text.strip() |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
124 if n[1].kind == xnElement and n[1].tag == "name": |
1481 | 125 # bitfield |
126 if n[2].text.strip().startsWith(":"): | |
127 return | |
128 &"{doIdentifier(n[1][0].text)} {{.bitsize:{n[2].text.strip()[1 .. ^1]}.}}: {doTypename(n[0][0].text, false)}" | |
129 # array definition | |
130 elif n[2].text.strip().startsWith("["): | |
131 let arrayDim = n[2].text[1 ..< ^1] | |
132 if "][" in arrayDim: | |
133 let dims = arrayDim.split("][", 1) | |
134 let (dim1, dim2) = (dims[0], dims[1]) | |
135 return doMember( | |
136 n[1][0].text, | |
137 &"array[{dim1}, array[{dim2}, {doTypename(n[0][0].text, false)}]]", | |
138 false, | |
139 n.attr("values"), | |
140 ) | |
141 else: | |
142 return doMember( | |
143 n[1][0].text, | |
144 &"array[{arrayDim}, {doTypename(n[0][0].text, false)}]", | |
145 false, | |
146 n.attr("values"), | |
147 ) | |
148 else: | |
149 debugecho n.toSeq | |
150 doAssert false, "This should not happen" | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
151 else: |
1481 | 152 # pointer definition |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
153 assert n[1].text.strip() == "*" |
1481 | 154 return doMember(n[2][0].text, n[0][0].text, true, n.attr("values")) |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
155 elif n.len == 4: |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
156 if n[0].text.strip() in ["struct", "const struct"]: |
1481 | 157 return doMember(n[3][0].text, n[1][0].text, true, n.attr("values")) |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
158 else: |
1481 | 159 assert n[0].text.strip() == "const" # can be ignored |
160 assert n[1].tag == "type" | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
161 assert n[2].text.strip() in ["*", "* const *", "* const*"] |
1481 | 162 # can be ignored, basically every type is a pointer |
163 assert n[3].tag == "name" | |
164 assert n[1].len == 1 | |
165 assert n[3].len == 1 | |
166 return doMember(n[3][0].text, n[1][0].text, true, n.attr("values")) | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
167 elif n.len in [5, 6]: |
1481 | 168 # array definition, using const-value for array length |
169 # <type>uint8_t</type>,<name>pipelineCacheUUID</name>[<enum>VK_UUID_SIZE</enum>] | |
170 assert n[2].text.strip() == "[" | |
171 assert n[4].text.strip() == "]" | |
172 return doMember( | |
173 n[1][0].text, | |
174 &"array[{n[3][0].text}, {doTypename(n[0][0].text, false)}]", | |
175 false, | |
176 n.attr("values"), | |
177 ) | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
178 assert false |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
179 |
1479 | 180 for e in xmlenums: |
181 if e.attr("type") == "constants": | |
182 for c in e.findAll("enum"): | |
183 var value = c.attr("value").strip(chars = {'(', ')'}) | |
184 consts.add ConstantDef( | |
185 name: c.attr("name"), datatype: TYPEMAP[c.attr("type")], value: value | |
186 ) | |
187 elif e.attr("type") == "enum": | |
188 var edef = EnumDef(name: e.attr("name"), isBitmask: false) | |
189 for ee in e.findAll("enum"): | |
190 edef.addValue(ee) | |
191 enums[edef.name] = edef | |
192 elif e.attr("type") == "bitmask": | |
193 var edef = EnumDef(name: e.attr("name"), isBitmask: true) | |
194 for ee in e.findAll("enum"): | |
195 edef.addValue(ee) | |
196 enums[edef.name] = edef | |
197 | |
198 for f in features: | |
199 for extendenum in f.findAll("enum"): | |
200 if extendenum.attr("extends") != "": | |
201 enums[extendenum.attr("extends")].addValue(extendenum) | |
202 | |
203 for extension in extensions.findAll("extension"): | |
204 let extNum = extension.attr("number") | |
205 for extendenum in extension.findAll("enum"): | |
206 if extendenum.attr("extends") != "": | |
207 if extendenum.attr("extnumber") == "": | |
208 extendenum.attrs["extnumber"] = extNum | |
209 enums[extendenum.attr("extends")].addValue(extendenum) | |
210 | |
1481 | 211 let outPath = (system.currentSourcePath.parentDir() / "vkapi.nim") |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
212 let outFile = open(outPath, fmWrite) |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
213 |
1479 | 214 # generate core types =============================================================================== |
215 # preamble, much easier to hardcode than to generate from xml | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
216 outFile.writeLine """ |
1481 | 217 |
218 import ../semicongine/thirdparty/winim/winim/inc/winbase | |
219 import ../semicongine/thirdparty/winim/winim/inc/windef | |
220 import ../semicongine/thirdparty/x11/xlib | |
221 import ../semicongine/thirdparty/x11/x | |
222 import ../semicongine/thirdparty/x11/xrandr | |
223 | |
1479 | 224 func VK_MAKE_API_VERSION*( |
225 variant: uint32, major: uint32, minor: uint32, patch: uint32 | |
226 ): uint32 {.compileTime.} = | |
227 (variant shl 29) or (major shl 22) or (minor shl 12) or patch | |
228 """ | |
229 | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
230 outFile.writeLine "type" |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
231 outFile.writeLine """ |
1481 | 232 # some unused native types |
233 # | |
234 # android | |
235 ANativeWindow = object | |
236 AHardwareBuffer = object | |
237 | |
1483 | 238 # apple/metal |
1481 | 239 CAMetalLayer = object |
240 MTLSharedEvent_id = object | |
1483 | 241 MTLDevice_id = object |
242 MTLCommandQueue_id = object | |
243 MTLBuffer_id = object | |
244 MTLTexture_id = object | |
245 IOSurfaceRef = object | |
1481 | 246 |
247 # wayland | |
248 wl_display = object | |
249 wl_surface = object | |
250 | |
251 # XCB | |
252 xcb_connection_t = object | |
253 xcb_window_t = object | |
254 xcb_visualid_t = object | |
255 | |
256 # directfb | |
257 IDirectFB = object | |
258 IDirectFBSurface = object | |
259 | |
260 # Zircon | |
261 zx_handle_t = object | |
262 | |
263 # GGP C | |
264 GgpStreamDescriptor = object | |
265 GgpFrameToken = object | |
266 | |
267 # Screen (nintendo switch?) | |
268 screen_context = object | |
269 screen_window = object | |
270 screen_buffer = object | |
271 | |
272 # Nvidia | |
273 NvSciSyncAttrList = object | |
274 NvSciSyncObj = object | |
275 NvSciSyncFence = object | |
276 NvSciBufAttrList = object | |
277 NvSciBufObj = object | |
278 | |
279 # some base vulkan base types | |
1483 | 280 VkSampleMask* = distinct uint32 |
281 VkBool32* = distinct uint32 | |
282 VkFlags* = distinct uint32 | |
283 VkFlags64* = distinct uint64 | |
284 VkDeviceSize* = distinct uint64 | |
285 VkDeviceAddress* = distinct uint64 | |
286 VkRemoteAddressNV* = pointer | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
287 """ |
1479 | 288 |
289 # generate consts =============================================================================== | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
290 outFile.writeLine "const" |
1479 | 291 for c in consts: |
292 var value = c.value | |
293 if value.endsWith("U"): | |
294 value = value[0 ..^ 2] & "'u32" | |
295 elif value.endsWith("ULL"): | |
296 value = value[0 ..^ 4] & "'u64" | |
297 if value[0] == '~': | |
298 value = "not " & value[1 ..^ 1] | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
299 outFile.writeLine &" {c.name}*: {c.datatype} = {value}" |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
300 outFile.writeLine "" |
1479 | 301 |
302 # generate enums =============================================================================== | |
1481 | 303 const nameCollisions = [ |
304 "VK_PIPELINE_CACHE_HEADER_VERSION_ONE", | |
305 "VK_PIPELINE_CACHE_HEADER_VERSION_SAFETY_CRITICAL_ONE", | |
306 "VK_DEVICE_FAULT_VENDOR_BINARY_HEADER_VERSION_ONE_EXT", | |
307 ] | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
308 outFile.writeLine "type" |
1482 | 309 |
1479 | 310 for edef in enums.values(): |
311 if edef.values.len > 0: | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
312 outFile.writeLine &" {edef.name}* {{.size: 4.}} = enum" |
1479 | 313 for ee in edef.values: |
1481 | 314 # due to the nim identifier-system, there might be collisions between typenames and enum-member names |
315 if ee.name in nameCollisions: | |
316 outFile.writeLine &" {ee.name}_VALUE = {ee.value}" | |
317 else: | |
318 outFile.writeLine &" {ee.name} = {ee.value}" | |
319 | |
320 outFile.writeLine "" | |
321 | |
322 # generate types =============================================================================== | |
323 for t in types: | |
324 let category = t.attr("category") | |
1483 | 325 let tName = t.attr("name") |
326 if tName.startsWith("VkVideo"): # we are not doing the video API, sorry | |
327 continue | |
328 if tName.startsWith("VkPhysicalDeviceVideo"): # we are not doing the video API, sorry | |
329 continue | |
1481 | 330 if t.attr("api") == "vulkansc": |
331 continue | |
332 elif t.attr("deprecated") == "true": | |
333 continue | |
334 elif category == "include": | |
335 continue | |
336 elif category == "define": | |
337 continue | |
338 elif t.attr("requires").startsWith("vk_video"): | |
339 continue | |
340 elif t.attr("alias") != "": | |
341 let a = t.attr("alias") | |
1483 | 342 outFile.writeLine &" {tName}* = {a}" |
1481 | 343 elif category == "bitmask": |
344 if t.len > 0 and t[0].text.startsWith("typedef"): | |
1483 | 345 outFile.writeLine &" {t[2][0].text}* = distinct {t[1][0].text}" |
1481 | 346 elif category == "union": |
1483 | 347 outFile.writeLine &" {tName}* {{.union.}} = object" |
1481 | 348 for member in t.findAll("member"): |
349 outFile.writeLine &" {member.memberDecl()}" | |
350 elif category == "handle": | |
1483 | 351 outFile.writeLine &" {t[2][0].text}* = distinct pointer" |
1481 | 352 elif category == "struct": |
1483 | 353 outFile.writeLine &" {tName}* = object" |
1481 | 354 for member in t.findAll("member"): |
355 if member.attr("api") == "vulkansc": | |
356 continue | |
357 outFile.writeLine &" {member.memberDecl()}" | |
358 elif category == "funcpointer": | |
359 #[ | |
360 <type category="funcpointer">typedef void* (VKAPI_PTR *<name>PFN_vkAllocationFunction</name>)( | |
361 <type>void</type>* pUserData, | |
362 <type>size_t</type> size, | |
363 <type>size_t</type> alignment, | |
364 <type>VkSystemAllocationScope</type> allocationScope); | |
365 </type> | |
366 ]# | |
367 assert t[0].text.startsWith("typedef ") | |
1483 | 368 let retName = t[0].text[8 ..< ^13].strip() |
369 let funcName = t.findAll("name")[0][0].text | |
370 | |
371 outFile.write &" {funcname}* = proc(" | |
372 for i in 3 ..< t.len: | |
373 # TODO: params | |
374 | |
375 if retName == "void" | |
376 outFile.writeLine &") {{.cdecl.}}" | |
377 elif retName == "void*" | |
378 outFile.writeLine &"): pointer {{.cdecl.}}" | |
379 else: | |
380 outFile.writeLine &"): {doTypename(retName, false)} {{.cdecl.}}" | |
381 | |
1481 | 382 else: |
383 doAssert category in ["", "basetype", "enum"], "unknown type category: " & category | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
384 outFile.writeLine "" |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
385 |
1483 | 386 for command in commands: |
387 #[ | |
388 <command successcodes="VK_SUCCESS" errorcodes="VK_ERROR_OUT_OF_HOST_MEMORY,VK_ERROR_OUT_OF_DEVICE_MEMORY,VK_ERROR_INITIALIZATION_FAILED,VK_ERROR_LAYER_NOT_PRESENT,VK_ERROR_EXTENSION_NOT_PRESENT,VK_ERROR_INCOMPATIBLE_DRIVER"> | |
389 <proto> | |
390 <type>VkResult</type> | |
391 <name>vkCreateInstance</name> | |
392 </proto> | |
393 <param>const <type>VkInstanceCreateInfo</type>* <name>pCreateInfo</name></param> | |
394 <param optional="true">const <type>VkAllocationCallbacks</type>* <name>pAllocator</name></param> | |
395 <param><type>VkInstance</type>* <name>pInstance</name></param> | |
396 </command> | |
397 ]# | |
398 if command.attr("api") == "vulkansc": | |
399 continue | |
400 if command.attr("alias") != "": | |
401 let funcName = command.attr("name") | |
402 let funcAlias = command.attr("alias") | |
403 outFile.write &"var {funcName}* = {funcAlias}\n" | |
404 continue | |
405 | |
406 let proto = command.findAll("proto")[0] | |
407 let retType = proto.findAll("type")[0][0].text.strip() | |
408 let funcName = proto.findAll("name")[0][0].text.strip() | |
409 | |
410 if "Video" in funcName: # Video API not supported at this time | |
411 continue | |
412 | |
413 outFile.write &"var {funcName}*: proc(" | |
414 for param in command: | |
415 if param.tag != "param": | |
416 continue | |
417 if param.attr("api") == "vulkansc": | |
418 continue | |
419 assert param.len in [2, 3, 4] | |
420 let paramType = param.findAll("type")[0][0].text | |
421 let paramName = param.findAll("name")[0][0].text | |
422 assert "*" notin paramType, $param | |
423 if paramType == "void": | |
424 outFile.write &"{doIdentifier(paramName)}: {doTypename(paramType, true)}, " | |
425 else: | |
426 outFile.write &"{doIdentifier(paramName)}: {doTypename(paramType, false)}, " | |
427 | |
428 outFile.write &")" | |
429 if retType != "void": | |
430 assert "*" notin retType | |
431 outFile.write &": {doTypename(retType, false)}" | |
432 outFile.write " {.stdcall.}\n" | |
1480
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
433 |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
434 outFile.close() |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
435 |
0b302531f5c5
did: continue on vulkan nim api generator
sam <sam@basx.dev>
parents:
1479
diff
changeset
|
436 assert execCmd("nim c " & outPath) == 0 |