Mercurial > games > semicongine
view svk/generate.nim @ 1484:a2af327f19df default tip
add: final raw wrapper
author | sam <sam@basx.dev> |
---|---|
date | Thu, 01 May 2025 00:59:40 +0700 |
parents | 55911f736a5a |
children |
line wrap: on
line source
import std/strtabs import std/syncio import std/xmltree import std/tables import std/options import std/sequtils import std/strutils import std/strformat import std/xmlparser import std/os import std/osproc import std/paths # helpers func smartParseInt(value: string): int = if value.startsWith("0x"): parseHexInt(value) else: parseInt(value) const TYPEMAP = { "void": "void", "char": "char", "float": "float32", "double": "float64", "int8_t": "int8", "uint8_t": "uint8", "int16_t": "int16", "uint16_t": "uint16", "int32_t": "int32", "uint32_t": "uint32", "uint64_t": "uint64", "int64_t": "int64", "size_t": "csize_t", "int": "cint", }.toTable # load xml let xml = (system.currentSourcePath.parentDir() / "vk.xml").loadXml() let platforms = xml.findAll("platforms")[0] let types = xml.findAll("types")[0] let xmlenums = xml.findAll("enums") let commands = xml.findAll("commands")[0] let features = xml.findAll("feature") # features has extends="<ENUM>" let extensions = xml.findAll("extensions")[0] # extensions has extends="<ENUM>" let formats = xml.findAll("formats")[0] # gather all enums type EnumEntry = object name: string value: string EnumDef = object name: string values: seq[EnumEntry] isBitmask: bool ConstantDef = object name: string datatype: string value: string var consts: seq[ConstantDef] var enums: Table[string, EnumDef] func addValue(edef: var EnumDef, n: XmlNode) = if n.attr("deprecated") != "aliased" and n.attr("alias") == "": if n.attr("name") in edef.values.mapIt(it.name): return var value = "" if n.attr("value") != "": value = n.attr("value") elif n.attr("bitpos") != "": value = $(1 shl parseInt(n.attr("bitpos"))) elif n.attr("offset") != "": var enumBase = 1000000000 if n.attr("extnumber") != "": enumBase += (smartParseInt(n.attr("extnumber")) - 1) * 1000 var v = smartParseInt(n.attr("offset")) + enumBase if n.attr("dir") == "-": v = -v value = $(v) if value notin edef.values.mapIt(it.value): edef.values.add EnumEntry(name: n.attr("name"), value: value) func doTypename(typename: string, pointerType: int): string = ## pointerType == 0: no pointer ## pointerType == 1: normal pointer (e.g. char *) ## pointerType == 2: double pointer (e.g. void **) assert pointerType in [0, 1, 2] result = TYPEMAP.getOrDefault(typename.strip(), typename.strip()).strip(chars = {'_'}) if typename == "void": assert pointerType > 0 if pointerType > 0: if typename == "void": result = "pointer" elif typename == "char": result = "cstring" else: result = "ptr " & result if pointerType == 2: result = "ptr " & result func doIdentifier(typename: string): string = if typename in ["type", "object"]: return &"`{typename}`" return typename.strip() func doMember(typename, theType: string, pointerType: int, value: string): string = if value == "": &"{doIdentifier(typename)}: {doTypename(theType, pointerType)}" else: &"{doIdentifier(typename)}: {doTypename(theType, pointerType)} = {value}" func memberDecl(n: XmlNode): string = for i in 0 ..< n.len: if n[i].kind == xnElement and n[i].tag == "comment": n.delete(i) break assert n.tag == "member" if n.len == 2: return doMember(n[1][0].text, n[0][0].text, 0, n.attr("values")) elif n.len == 3: assert "*" notin n[0][0].text.strip() if n[1].kind == xnElement and n[1].tag == "name": # bitfield if n[2].text.strip().startsWith(":"): return &"{doIdentifier(n[1][0].text)} {{.bitsize:{n[2].text.strip()[1 .. ^1]}.}}: {doTypename(n[0][0].text, 0)}" # array definition elif n[2].text.strip().startsWith("["): let arrayDim = n[2].text[1 ..< ^1] if "][" in arrayDim: let dims = arrayDim.split("][", 1) let (dim1, dim2) = (dims[0], dims[1]) return doMember( n[1][0].text, &"array[{dim1}, array[{dim2}, {doTypename(n[0][0].text, 0)}]]", 0, n.attr("values"), ) else: return doMember( n[1][0].text, &"array[{arrayDim}, {doTypename(n[0][0].text, 0)}]", 0, n.attr("values"), ) else: debugecho n.toSeq doAssert false, "This should not happen" else: # pointer definition assert n[1].text.strip() == "*" return doMember(n[2][0].text, n[0][0].text, 1, n.attr("values")) elif n.len == 4: if n[0].text.strip() in ["struct", "const struct"]: return doMember(n[3][0].text, n[1][0].text, 1, n.attr("values")) else: assert n[0].text.strip() == "const" # can be ignored assert n[1].tag == "type" assert n[2].text.strip() in ["*", "* const *", "* const*"] # can be ignored, basically every type is a pointer assert n[3].tag == "name" assert n[1].len == 1 assert n[3].len == 1 return doMember(n[3][0].text, n[1][0].text, 1, n.attr("values")) elif n.len in [5, 6]: # array definition, using const-value for array length # <type>uint8_t</type>,<name>pipelineCacheUUID</name>[<enum>VK_UUID_SIZE</enum>] assert n[2].text.strip() == "[" assert n[4].text.strip() == "]" return doMember( n[1][0].text, &"array[{n[3][0].text}, {doTypename(n[0][0].text, 0)}]", 0, n.attr("values"), ) assert false for e in xmlenums: if e.attr("type") == "constants": for c in e.findAll("enum"): var value = c.attr("value").strip(chars = {'(', ')'}) consts.add ConstantDef( name: c.attr("name"), datatype: TYPEMAP[c.attr("type")], value: value ) elif e.attr("type") == "enum": var edef = EnumDef(name: e.attr("name"), isBitmask: false) for ee in e.findAll("enum"): edef.addValue(ee) enums[edef.name] = edef elif e.attr("type") == "bitmask": var edef = EnumDef(name: e.attr("name"), isBitmask: true) for ee in e.findAll("enum"): edef.addValue(ee) enums[edef.name] = edef for f in features: for extendenum in f.findAll("enum"): if extendenum.attr("extends") != "": enums[extendenum.attr("extends")].addValue(extendenum) for extension in extensions.findAll("extension"): let extNum = extension.attr("number") for extendenum in extension.findAll("enum"): if extendenum.attr("extends") != "": if extendenum.attr("extnumber") == "": extendenum.attrs["extnumber"] = extNum enums[extendenum.attr("extends")].addValue(extendenum) let outPath = (system.currentSourcePath.parentDir() / "vkapi.nim") let outFile = open(outPath, fmWrite) # generate core types =============================================================================== # preamble, much easier to hardcode than to generate from xml outFile.writeLine """ import std/dynlib import ../semicongine/thirdparty/winim/winim/inc/winbase import ../semicongine/thirdparty/winim/winim/inc/windef import ../semicongine/thirdparty/x11/xlib import ../semicongine/thirdparty/x11/x import ../semicongine/thirdparty/x11/xrandr func VK_MAKE_API_VERSION*( variant: uint32, major: uint32, minor: uint32, patch: uint32 ): uint32 {.compileTime.} = (variant shl 29) or (major shl 22) or (minor shl 12) or patch """ outFile.writeLine "type" outFile.writeLine """ # some unused native types # # android ANativeWindow = object AHardwareBuffer = object # apple/metal CAMetalLayer = object MTLSharedEvent_id = object MTLDevice_id = object MTLCommandQueue_id = object MTLBuffer_id = object MTLTexture_id = object IOSurfaceRef = object # wayland wl_display = object wl_surface = object # XCB xcb_connection_t = object xcb_window_t = object xcb_visualid_t = object # directfb IDirectFB = object IDirectFBSurface = object # Zircon zx_handle_t = object # GGP C GgpStreamDescriptor = object GgpFrameToken = object # Screen (nintendo switch?) screen_context = object screen_window = object screen_buffer = object # Nvidia NvSciSyncAttrList = object NvSciSyncObj = object NvSciSyncFence = object NvSciBufAttrList = object NvSciBufObj = object # some base vulkan base types VkSampleMask* = distinct uint32 VkBool32* = distinct uint32 VkFlags* = distinct uint32 VkFlags64* = distinct uint64 VkDeviceSize* = distinct uint64 VkDeviceAddress* = distinct uint64 VkRemoteAddressNV* = pointer """ # generate consts =============================================================================== outFile.writeLine "const" for c in consts: var value = c.value if value.endsWith("U"): value = value[0 ..^ 2] & "'u32" elif value.endsWith("ULL"): value = value[0 ..^ 4] & "'u64" if value[0] == '~': value = "not " & value[1 ..^ 1] outFile.writeLine &" {c.name}*: {c.datatype} = {value}" outFile.writeLine "" # generate enums =============================================================================== const nameCollisions = [ "VK_PIPELINE_CACHE_HEADER_VERSION_ONE", "VK_PIPELINE_CACHE_HEADER_VERSION_SAFETY_CRITICAL_ONE", "VK_DEVICE_FAULT_VENDOR_BINARY_HEADER_VERSION_ONE_EXT", ] outFile.writeLine "type" for edef in enums.values(): if edef.values.len > 0: outFile.writeLine &" {edef.name}* {{.size: 4.}} = enum" for ee in edef.values: # due to the nim identifier-system, there might be collisions between typenames and enum-member names if ee.name in nameCollisions: outFile.writeLine &" {ee.name}_VALUE = {ee.value}" else: outFile.writeLine &" {ee.name} = {ee.value}" outFile.writeLine "" # generate types =============================================================================== for t in types: let category = t.attr("category") let tName = t.attr("name") if tName.startsWith("VkVideo"): # we are not doing the video API, sorry continue if tName.startsWith("VkPhysicalDeviceVideo"): # we are not doing the video API, sorry continue if t.attr("api") == "vulkansc": continue elif t.attr("deprecated") == "true": continue elif category == "include": continue elif category == "define": continue elif t.attr("requires").startsWith("vk_video"): continue elif t.attr("alias") != "": let a = t.attr("alias") outFile.writeLine &" {tName}* = {a}" elif category == "bitmask": if t.len > 0 and t[0].text.startsWith("typedef"): outFile.writeLine &" {t[2][0].text}* = distinct {t[1][0].text}" elif category == "union": outFile.writeLine &" {tName}* {{.union.}} = object" for member in t.findAll("member"): outFile.writeLine &" {member.memberDecl()}" elif category == "handle": outFile.writeLine &" {t[2][0].text} = distinct pointer" elif category == "struct": outFile.writeLine &" {tName}* = object" for member in t.findAll("member"): if member.attr("api") == "vulkansc": continue outFile.writeLine &" {member.memberDecl()}" elif category == "funcpointer": assert t[0].text.startsWith("typedef ") let retName = t[0].text[8 ..< ^13].strip() let funcName = t.findAll("name")[0][0].text outFile.write &" {funcname}* = proc(" let nParams = (t.len - 3) div 2 for i in 0 ..< nParams: assert t[i * 2 + 3].tag == "type" let typename = t[i * 2 + 3][0].text.strip() var identifier = t[i * 2 + 4].text.strip(chars = {' ', ')', ';', ',', '\n'}) var pointerType = if identifier.startsWith("*"): 1 else: 0 if pointerType > 0: identifier = identifier[1 .. ^1].strip(chars = {' ', ')', ';', ',', '\n'}) if identifier.endsWith("const"): identifier = identifier[0 .. ^6].strip(chars = {' ', ')', ';', ',', '\n'}) identifier = identifier.strip(chars = {','}) outFile.write &"{doIdentifier(identifier)}: {doTypename(typename, pointerType)}, " if retName == "void": outFile.writeLine &") {{.cdecl.}}" elif retName == "void*": outFile.writeLine &"): pointer {{.cdecl.}}" else: outFile.writeLine &"): {doTypename(retName, 0)} {{.cdecl.}}" else: doAssert category in ["", "basetype", "enum"], "unknown type category: " & category outFile.writeLine "" outFile.write &"var\n" for command in commands: if command.attr("api") == "vulkansc": continue if command.attr("alias") != "": let funcName = command.attr("name") let funcAlias = command.attr("alias") outFile.write &" {funcName}* = {funcAlias}\n" continue let proto = command.findAll("proto")[0] let retType = proto.findAll("type")[0][0].text.strip() let funcName = proto.findAll("name")[0][0].text.strip() if "Video" in funcName: # Video API not supported at this time continue outFile.write &" {funcName}*: proc(" for param in command: if param.tag != "param": continue if param.attr("api") == "vulkansc": continue assert param.len in [2, 3, 4] let paramType = param.findAll("type")[0][0].text let paramName = param.findAll("name")[0][0].text assert "*" notin paramType, $param if param.len == 4: param.delete(0) var pointerType = 0 if param.len == 3: if param[param.len - 1].kind == xnText: assert param[param.len - 1].text[^1] == ']' else: assert param[0].tag == "type" assert param[param.len - 1].tag == "name" if param[1].text.strip() == "*": pointerType = 1 elif param[1].text.strip() == "**": pointerType = 2 # echo "3: ", param[1].text, " ", paramType, " ", paramName outFile.write &"{doIdentifier(paramName)}: {doTypename(paramType, pointerType)}, " outFile.write &")" if retType != "void": assert "*" notin retType outFile.write &": {doTypename(retType, 0)}" outFile.write " {.stdcall.}\n" outFile.write """ when defined(linux): let vulkanLib = loadLib("libvulkan.so.1") when defined(windows): let vulkanLib = loadLib("vulkan-1.dll") if vulkanLib == nil: raise newException(Exception, "Unable to load vulkan library") vkGetInstanceProcAddr = cast[proc(instance: VkInstance, pName: cstring, ): PFN_vkVoidFunction {.stdcall.}](checkedSymAddr(vulkanLib, "vkGetInstanceProcAddr")) """ outFile.close() assert execCmd("nim c " & outPath) == 0