local codegen = require "codegen" local idl = codegen.idl "bgfx.idl" local template = [[ /+ + ┌==============================┐ + │ AUTO GENERATED! DO NOT EDIT! │ + └==============================┘ +/ module bgfx; import bindbc.bgfx.config; import bindbc.common.types: c_int64, c_uint64, va_list; static import bgfx.fakeenum; $version alias ViewID = ushort; enum invalidHandle(T) = T(ushort.max); alias ReleaseFn = void function(void* ptr, void* userData); $types pragma(inline,true) nothrow @nogc pure @safe{ StateBlend_ blendFuncSeparate(StateBlend_ srcRGB, StateBlend_ dstRGB, StateBlend_ srcA, StateBlend_ dstA){ return (srcRGB | ((dstRGB) << 4)) | ((srcA | (dstA << 4)) << 8); } ///Blend equation separate. StateBlendEquation_ blendEquationSeparate(StateBlendEquation_ equationRGB, StateBlendEquation_ equationA){ return equationRGB | (equationA << 3); } ///Blend function. StateBlend_ blendFunc(StateBlend_ src, StateBlend_ dst){ return blendFuncSeparate(src, dst, src, dst); } ///Blend equation. StateBlendEquation_ blendEquation(StateBlendEquation_ equation){ return blendEquationSeparate(equation, equation); } ///Utility predefined blend modes. enum StateBlendFunc: StateBlend_{ ///Additive blending. add = blendFunc(StateBlend.one, StateBlend.one), ///Alpha blend. alpha = blendFunc(StateBlend.srcAlpha, StateBlend.invSrcAlpha), ///Selects darker color of blend. darken = blendFunc(StateBlend.one, StateBlend.one) | blendEquation(StateBlendEquation.min), ///Selects lighter color of blend. lighten = blendFunc(StateBlend.one, StateBlend.one) | blendEquation(StateBlendEquation.max), ///Multiplies colors. multiply = blendFunc(StateBlend.dstColor, StateBlend.zero), ///Opaque pixels will cover the pixels directly below them without any math or algorithm applied to them. normal = blendFunc(StateBlend.one, StateBlend.invSrcAlpha), ///Multiplies the inverse of the blend and base colors. screen = blendFunc(StateBlend.one, StateBlend.invSrcColor), ///Decreases the brightness of the base color based on the value of the blend color. linearBurn = blendFunc(StateBlend.dstColor, StateBlend.invDstColor) | blendEquation(StateBlendEquation.sub), } StateBlend_ blendFuncRTx(StateBlend_ src, StateBlend_ dst){ return cast(uint)(src >> StateBlend.shift) | (cast(uint)(dst >> StateBlend.shift) << 4); } StateBlend_ blendFuncRTxE(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){ return blendFuncRTx(src, dst) | (cast(uint)(equation >> StateBlendEquation.shift) << 8); } StateBlend_ blendFuncRT1(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) << 0; } StateBlend_ blendFuncRT2(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) << 11; } StateBlend_ blendFuncRT3(StateBlend_ src, StateBlend_ dst){ return blendFuncRTx(src, dst) << 22; } StateBlend_ blendFuncRT1E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){ return blendFuncRTxE(src, dst, equation) << 0; } StateBlend_ blendFuncRT2E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){ return blendFuncRTxE(src, dst, equation) << 11; } StateBlend_ blendFuncRT3E(StateBlend_ src, StateBlend_ dst, StateBlendEquation_ equation){ return blendFuncRTxE(src, dst, equation) << 22; } } $structs mixin(joinFnBinds((){ FnBind[] ret = [ $funcs ]; return ret; }())); static if(!staticBinding): import bindbc.loader; debug{ mixin(makeDynloadFns("Bgfx", makeLibPaths(["bgfx-shared-libDebug", "bgfxDebug", "bgfx"]), [__MODULE__])); }else{ mixin(makeDynloadFns("Bgfx", makeLibPaths(["bgfx-shared-libRelease", "bgfxRelease", "bgfx"]), [__MODULE__])); } ]] local dKeywords = {"abstract", "alias", "align", "asm", "assert", "auto", "bool", "break", "byte", "case", "cast", "catch", "char", "class", "const", "continue", "dchar", "debug", "default", "delegate", "deprecated", "do", "double", "else", "enum", "export", "extern", "false", "final", "finally", "float", "for", "foreach", "foreach_reverse", "function", "goto", "if", "immutable", "import", "in", "inout", "int", "interface", "invariant", "is", "lazy", "long", "macro", "mixin", "module", "new", "nothrow", "null", "out", "override", "package", "pragma", "private", "protected", "public", "pure", "real", "ref", "return", "scope", "shared", "short", "static", "struct", "super", "switch", "synchronized", "template", "this", "throw", "true", "try", "typeid", "typeof", "ubyte", "uint", "ulong", "union", "unittest", "ushort", "version", "void", "wchar", "while", "with"} local function contains(table, val) for i=1,#table do if table[i] == val then return true end end return false end local function hasPrefix(str, prefix) return prefix == "" or str:sub(1, #prefix) == prefix end local function hasSuffix(str, suffix) return suffix == "" or str:sub(-#suffix) == suffix end local enumTypes = {} local membersWithFns = "" local capsWords = {"Mip", "Id", "Rw", "Vb", "Ib", "Cb", "Rt", "Pci", "Srgb", "Pt", "Ccw", "2d", "3d", "Msaa"} local capsRepl = { hidpi = "hiDPI", lineaa = "lineAA", maxanisotropy = "maxAnisotropy", notequal = "notEqual", gequal = "gEqual", Lequal = "LEqual", lequal = "lEqual", decrsat = "decrSat", incrsat = "incrSat", revsub = "revSub", linestrip = "lineStrip", tristrip = "triStrip", bstencil = "bStencil", fstencil = "fStencil", } local function abbrevsToUpper(name) if name:len() >= 3 then for _, abbrev in pairs(capsWords) do name = name:gsub(abbrev, function(s0) return s0:upper() end) end for from, to in pairs(capsRepl) do name = name:gsub(from, to) end end return name end local function toCamelCase(name) if name:len() >= 3 then name = name:sub(0, 1) .. name:sub(2, -2):gsub("_", "") .. name:sub(-1) end if name:find("%u%u+%l") then name = name:gsub("(%u-)(%u%l)", function(s0, s1) return s0:lower() .. s1 end) else name = (name:gsub("^([^%l]*)(%l?)", function(s0, s1) if s1 ~= nil then return s0:lower() .. s1 end return s0:lower() end)) end return abbrevsToUpper(name) end -- local function toPascalCase(name) -- return (name:gsub("^%l", string.upper)) -- end local usEnSubs = { color = "colour", Color = "Colour", rasterize = "rasterise", Rasterize = "Rasterise", initialize = "initialise", Initialize = "Initialise", normalize = "normalise", Normalize = "Normalise", normalized = "normalised", Normalized = "Normalised", ccw = "acw", CCW = "ACW", } local function toIntlEn(name) local change = false for us, intl in pairs(usEnSubs) do if name:find(us) then name = (name:gsub(us, intl)) change = true end end if change then return name else return nil end end -- Unconditionally convert local function toIntlEnUncond(name) local newName = toIntlEn(name) if newName ~= nil then return newName end return name end local function hexStr(val, bits) local digits = bits / 4 local str = string.format(string.format("%%0%iX", digits), val) local i = 4 while i < str:len() do str = str:sub(0, i) .. "_" .. str:sub(i+1) i = i + 5 end return "0x" .. str end local function convArray(array) if string.find(array, "::") then return string.gsub(array, "::.*", ".") .. toCamelCase(string.gsub(array, ".-::", "")) else return array end end local typeSubs = { uint32_t = "uint", int32_t = "int", uint16_t = "ushort", int16_t = "short", uint64_t = "c_uint64", int64_t = "c_int64", uint8_t = "ubyte", int8_t = "byte", uintptr_t = "size_t" } local function convSomeType(arg, isFnArg) local type = arg.fulltype if type == "bx::AllocatorI*" or type == "CallbackI*" then type = "void*" else for from, to in pairs(typeSubs) do if type:find(from) then type = type:gsub(from, to) break end end type = type:gsub("::Enum", "") --fix enums type = type:gsub("%s+%*", "*") --remove spacing before `*` if isFnArg then for _, enum in pairs(enumTypes) do --fix C++ linkage errors if type == enum then type = string.format("bgfx.fakeenum.%s.Enum", enum) else type = (type:gsub("(" .. enum .. ")([^A-Za-z0-9_])", function(s0, s1) return string.format("bgfx.fakeenum.%s.Enum", enum) .. s1 end)) end end type = type:gsub("([^&]-)%s?&", "ref %1") --change `&` suffix to `ref` prefix if arg.array ~= nil then type = type .. "*" --append * end else type = type:gsub("([^&]-)%s?&", "%1*") --change `&` to `*` if arg.array ~= nil then type = type .. convArray(arg.array) --append [n] end end type = type:gsub("const%s+([A-Za-z_][A-Za-z0-9_]*)%s*%*", "const(%1)*") --change `const x*` to `const(x)*` type = abbrevsToUpper(type) end return type end local function convType(arg) return convSomeType(arg, false) end local function convFnArgType(arg) return convSomeType(arg, true) end local valSubs = { NULL = "null", UINT8_MAX = "ubyte.max", UINT16_MAX = "ushort.max", UINT32_MAX = "uint.max", BGFX_INVALID_HANDLE = "invalidHandle", BGFX_DISCARD_ALL = "Discard.all", BGFX_BUFFER_NONE = "Buffer.none", BGFX_STENCIL_NONE = "Stencil.none", BGFX_TEXTURE_NONE = "Texture.none", BGFX_SAMPLER_NONE = "Sampler.none", BGFX_RESET_NONE = "Reset.none", BGFX_SAMPLER_U_CLAMP = "SamplerU.clamp", BGFX_SAMPLER_V_CLAMP = "SamplerV.clamp", BGFX_RESOLVE_AUTO_GEN_MIPS = "Resolve.autoGenMIPs", ["ViewMode::Default"] = "ViewMode.default_", } local function convVal(arg, type) local val = string.format("%s", arg) for from, to in pairs(valSubs) do if val:find(from) then if from == "BGFX_INVALID_HANDLE" then val = val:gsub(from, to .. "!" .. type) else val = val:gsub(from, to) end end end if val:find("INT32_MAX") then val = val:gsub("INT32_MAX", "int.max") end val = convArray(val) return val end local function convStructType(arg) return convType(arg) end local function convName(name) name = abbrevsToUpper(name) if contains(dKeywords, name) then return name .. "_" end return name end local function convStructMember(member) return convStructType(member) .. " " .. convName(member.name) end local allStructs = {} local function genVersion() return "enum uint apiVersion = " .. (idl._version or 0) .. ";" end local function genStructMemberFn(func) --NOTE: this does not work on nested structs if func.class ~= nil and func.conly == nil and func.cppinline == nil then local st = allStructs[func.class] local attribs = "" if func.comments ~= nil then if #st.fns > 0 then table.insert(st.fns, "") end table.insert(st.fns, "/**") for _, line in ipairs(func.comments) do local line = line:gsub("@remarks", "Remarks:") line = line:gsub("@remark", "Remarks:") line = line:gsub("@(%l)(%l+)", function(a, b) return a:upper() .. b .. ":" end) table.insert(st.fns, line) end local hasParamsComments = false for _, arg in ipairs(func.args) do if arg.comment ~= nil then hasParamsComments = true break end end if hasParamsComments then table.insert(st.fns, "Params:") end for _, arg in ipairs(func.args) do if arg.comment ~= nil then table.insert(st.fns, "\t" .. toIntlEnUncond(convName(arg.name:sub(2))) .. " = " .. arg.comment[1]) for i, comment in ipairs(arg.comment) do if i > 1 then table.insert(st.fns, comment) end end end end table.insert(st.fns, "*/") end local args = {} for _, arg in ipairs(func.args) do local def = "" if arg.default ~= nil then def = string.format("=%s", convVal(arg.default, convFnArgType(arg))) end if arg.fulltype == "..." then table.insert(args, "..." .. def) else table.insert(args, convFnArgType(arg) .. " " .. toIntlEnUncond(convName(arg.name:sub(2))) .. def) end end if func.const ~= nil then attribs = "const" end if attribs ~= "" then attribs = ", memAttr: q{" .. attribs .. "}" end table.insert(st.fns, string.format("{q{%s}, q{%s}, q{%s}, ext: `C++`%s},", convType(func.ret), func.name, table.concat(args, ", "), attribs)) end end local converter = {} local yield = coroutine.yield local gen = {} gen.fakeEnumFile = [[ /+ + ┌==============================┐ + │ AUTO GENERATED! DO NOT EDIT! │ + └==============================┘ +/ module bgfx.fakeenum; //NOTE: Do NOT use this module! Use the enums with the same names in `bgfx/package.d` instead. package: ]] function gen.gen() local indent = "" local idx = 1; local r = template:gsub("$([a-zA-Z_]+)", function(what) local tmp = {} local ind_end = template:find("$"..what, idx, true) local ind_start = ind_end for j = 1, ind_end-1 do local i = 1+ind_end-1-j local c = string.sub(template, i, i) if c ~= ' ' and c ~= '\t' then ind_start = i break end end indent = string.sub(template, ind_start+1, ind_end-1) if what == "version" then return genVersion() elseif what == "structs" then for _, fn in ipairs(idl.funcs) do genStructMemberFn(fn) end for _, object in ipairs(idl["types"]) do if object.struct ~= nil and object.namespace == nil then local co = coroutine.create(converter[what]) local any while true do local ok, v = coroutine.resume(co, allStructs[object.name], object.name, true, indent:len()) assert(ok, debug.traceback(co, v)) if not v then break end table.insert(tmp, v) any = true end if any and tmp[#tmp] ~= "" then table.insert(tmp, "") end end end elseif what == "membersWithFns" then table.insert(tmp, "\"" .. membersWithFns .. "\"") else for _, object in ipairs(idl[what]) do local co = coroutine.create(converter[what]) local any while true do local ok, v = coroutine.resume(co, object) assert(ok, debug.traceback(co, v)) if not v then break end table.insert(tmp, v) any = true end if any and tmp[#tmp] ~= "" then table.insert(tmp, "") end end end return table.concat(tmp, "\n" .. indent) end) return r end function converter.structs(st, name, topLvl) for _, line in ipairs(st.comments) do yield(line) end if topLvl then yield("extern(C++, \"bgfx\") struct " .. name .. "{") else yield("extern(C++) struct " .. name .. "{") end local subN = 0 for _, subStruct in ipairs(st.subs) do subN = subN + 1 local co = coroutine.create(converter.structs) while true do local ok, v = coroutine.resume(co, subStruct, subStruct.name, false) assert(ok, debug.traceback(co, v)) if not v then break end yield("\t" .. v) end end if subN > 0 then yield("\t") end for _, line in ipairs(st.fields) do yield(line) end if #st.fns > 0 then membersWithFns = membersWithFns .. name .. ", " yield("\textern(D) mixin(joinFnBinds((){") yield("\t\tFnBind[] ret = [") for _, line in ipairs(st.fns) do yield("\t\t\t" .. line) end yield("\t\t];") yield("\t\treturn ret;") yield("\t}()));") end yield("}") end function converter.types(typ) if typ.comments ~= nil and not typ.struct then if #typ.comments == 1 then yield("///" .. typ.comments[1]) else yield("/**") for _, comment in ipairs(typ.comments) do yield(comment) end yield("*/") end end if typ.handle then ---hnadle yield("extern(C++, \"bgfx\") struct " .. typ.name .. "{") yield("\tushort idx;") yield("}") --yield(typ.name .. " invalidHandle(){ return " .. typ.name .. "(ushort.max); }") -- For some reason, this has never worked, so I'm commenting it out just in case it does start working suddenly. :P --[[ elseif typ.funcptr then local args = {} for _, arg in ipairs(typ.args) do if arg.fulltype == "..." then table.insert(args, "..." .. def) else table.insert(args, convFnArgType(arg) .. " " .. convName(arg.name:sub(2)) .. def) end end yield(string.format("alias %s = extern(C++) %s function(%s);", typ.name, convType(typ.ret), table.concat(args, ", "))) --]] elseif typ.enum then local typeName = abbrevsToUpper(typ.name:gsub("::Enum", "")) local otherName = string.format("bgfx.fakeenum.%s.Enum", typ.name:gsub("::Enum", "")) yield("enum " .. typeName .. ": " .. otherName .. "{") table.insert(enumTypes, typeName) local vals = "" for idx, enum in ipairs(typ.enum) do local comments = "" if enum.comment ~= nil then if #enum.comment == 1 then comments = " ///" .. enum.comment[1]; else yield("\t/**") for _, comment in ipairs(enum.comment) do yield("\t" .. comment) end yield("\t*/") end end local name = convName(toCamelCase(enum.name)) yield("\t" .. name .. " = " .. otherName .. "." .. name .. ",") vals = vals .. name .. "," local intlName = toIntlEn(enum.name) if intlName ~= nil then yield("\t" .. convName(toCamelCase(intlName)) .. " = " .. otherName .. "." .. name .. ",") end end gen.fakeEnumFile = gen.fakeEnumFile .. string.format([[ extern(C++, "bgfx") package final abstract class %s{ enum Enum{ %scount } } ]], typeName, vals) yield("\t" .. "count = " .. otherName .. ".count,") yield("}") elseif typ.bits ~= nil then local typeName = convName(typ.name) if typeName == "Caps" then typeName = "CapFlags" end local enumType = "uint" if typ.bits == 64 then enumType = "ulong" elseif typ.bits == 32 then enumType = "uint" elseif typ.bits == 16 then enumType = "ushort" elseif typ.bits == 8 then enumType = "ubyte" end local maxLen = 0 if typ.shift then maxLen = string.len("shift") elseif typ.range then maxLen = string.len("mask") end for _, flag in ipairs(typ.flag) do maxLen = math.max(maxLen, flag.name:len()) end yield("alias " .. typeName .. "_ = " .. enumType .. ";") yield("enum " .. typeName .. ": " .. typeName .. "_{") local function getValOr(name) local t = typeName if typeName == "State" then if hasPrefix(name, "Write") then t = t .. name:sub(1, 5) name = name:sub(6) elseif hasPrefix(name, "DepthTest") then t = t .. name:sub(1, 9) name = name:sub(10) elseif hasPrefix(name, "Cull") then t = t .. name:sub(1, 4) name = name:sub(5) end elseif typeName == "Sampler" then if hasPrefix(name, "Min") then t = t .. name:sub(1, 3) name = name:sub(4) elseif hasPrefix(name, "Mag") then t = t .. name:sub(1, 3) name = name:sub(4) elseif hasPrefix(name, "Mip") then t = t .. name:sub(1, 3) name = name:sub(4) elseif hasPrefix(name, "U") then t = t .. name:sub(1, 1) name = name:sub(2) elseif hasPrefix(name, "V") then t = t .. name:sub(1, 1) name = name:sub(2) elseif hasPrefix(name, "W") then t = t .. name:sub(1, 1) name = name:sub(2) elseif hasPrefix(name, "Compare") then t = t .. name:sub(1, 7) name = name:sub(8) end end return abbrevsToUpper(t) .. "." .. convName(toCamelCase(name)) end for idx, flag in ipairs(typ.flag) do local value = flag.value if value ~= nil then value = hexStr(value, typ.bits) else for _, name in ipairs(flag) do if value ~= nil then value = value .. " | " .. getValOr(name) else value = getValOr(name) end end end local comments = "" if flag.comment ~= nil then if #flag.comment == 1 then comments = " ///" .. flag.comment[1] else yield("\t/**") for _, comment in ipairs(flag.comment) do yield("\t" .. comment) end yield("\t*/") end end local name = convName(toCamelCase(flag.name)) yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments) local intlName = toIntlEn(name) if intlName ~= nil then intlName = intlName yield("\t" .. intlName .. string.rep(" ", maxLen+2 - intlName:len()) .. "= " .. name .. ",") end end if typ.shift then local name = convName("shift") local value = typ.shift local comments = "" if typ.desc then comments = string.format(" ///%s bit shift", typ.desc) end yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments) end if typ.range then local name = convName("mask") local value = hexStr(typ.mask, typ.bits) local comments = "" if typ.desc then comments = string.format(" ///%s bit mask", typ.desc) end yield("\t" .. name .. string.rep(" ", maxLen+2 - name:len()) .. "= " .. value .. "," .. comments) end yield("}") local intlName = toIntlEn(typeName) if intlName ~= nil then yield("alias " .. intlName .. " = " .. typeName .. ";") end if typ.helper then yield(string.format( "%s_ to%s(%s v){ return (v << %s) & %s; }", typeName, typeName, enumType, (typeName .. ".shift"), (typeName .. ".mask"))) if intlName ~= nil then yield("alias to" .. intlName .. " = to" .. typeName .. ";") end end elseif typ.struct ~= nil then local st = {name = typ.name, comments = {}, fields = {}, fns = {}, subs = {}} if typ.comments ~= nil then if #typ.comments == 1 then table.insert(st.comments, "///" .. typ.comments[1]) else table.insert(st.comments, "/**") for _, comment in ipairs(typ.comments) do table.insert(st.comments, comment) end table.insert(st.comments, "*/") end end for _, member in ipairs(typ.struct) do local comments = "" if member.comment ~= nil then if #member.comment == 1 then comments = " ///" .. member.comment[1] else if #st.fields > 0 then table.insert(st.fields, "\t") end table.insert(st.fields, "\t/**") for _, comment in ipairs(member.comment) do table.insert(st.fields, "\t" .. comment) end table.insert(st.fields, "\t*/") end end table.insert(st.fields, "\t" .. convStructMember(member) .. ";" .. comments) end if typ.ctor ~= nil and typ.name ~= "PlatformData" then table.insert(st.fns, "{q{void}, q{this}, q{}, ext: `C++`},") end if typ.namespace ~= nil then --if this is a sub-struct if allStructs[typ.namespace] ~= nil then table.insert(allStructs[typ.namespace].subs, st) else allStructs[typ.namespace] = {subs = {st}} end else --otherwise it's top-level if allStructs[typ.name] ~= nil then st.subs = allStructs[typ.name].subs end allStructs[typ.name] = st end end end function converter.funcs(func) if func.class == nil and func.conly == nil and func.cppinline == nil then local extern = "C++, \"bgfx\"" local attribs = "" if func.cfunc ~= nil and func.name ~= "init" then --what the is "cfunc" even meant to mean? return end if func.comments ~= nil then yield("/**") for _, line in ipairs(func.comments) do local line = line:gsub("@remarks", "Remarks:") line = line:gsub("@remark", "Remarks:") line = line:gsub("@(%l)(%l+)", function(a, b) return a:upper() .. b .. ":" end) yield("* " .. line) end local hasParamsComments = false for _, arg in ipairs(func.args) do if arg.comment ~= nil then hasParamsComments = true break end end if hasParamsComments then yield("Params:") end for _, arg in ipairs(func.args) do if arg.comment ~= nil then yield("\t" .. toIntlEnUncond(convName(arg.name:sub(2))) .. " = " .. arg.comment[1]) for i, comment in ipairs(arg.comment) do if i > 1 then yield(comment) end end end end yield("*/") end local args = {} for _, arg in ipairs(func.args) do local def = "" if arg.default ~= nil then def = string.format("=%s", convVal(arg.default, convFnArgType(arg))) end if arg.fulltype == "..." then table.insert(args, "..." .. def) else table.insert(args, convFnArgType(arg) .. " " .. toIntlEnUncond(convName(arg.name:sub(2))) .. def) end end if attribs ~= "" then attribs = ", memAttr: q{" .. attribs .. "}" end yield(string.format("{q{%s}, q{%s}, q{%s}, ext: `%s`%s},", convType(func.ret), func.name, table.concat(args, ", "), extern, attribs)) end end -- printtable("idl types", idl.types) -- printtable("idl funcs", idl.funcs) function gen.write(codes, outputfile) local out = assert(io.open(outputfile, "wb")) out:write(codes) out:close() print("Generating: " .. outputfile) end if (...) == nil then -- run `lua bindings-d.lua` in command line print(gen.gen(dTemplate)) end return gen