diff options
author | hecks <42101236+hecktest@users.noreply.github.com> | 2021-08-07 21:56:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-07 21:56:00 +0200 |
commit | 5bf68b5731b1817c31959b8e3adf19a4c2307630 (patch) | |
tree | bcd0af6f07df105882f0353cd610abf00e26fe2e /scripts/BindingGenerator.lua | |
parent | 7709e1e5f8d8429308ae20d366171fdf6e64bee8 (diff) | |
download | irrlicht-5bf68b5731b1817c31959b8e3adf19a4c2307630.tar.xz |
Add a unified cross platform OpenGL core profile binding (#52)
Diffstat (limited to 'scripts/BindingGenerator.lua')
-rwxr-xr-x | scripts/BindingGenerator.lua | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/scripts/BindingGenerator.lua b/scripts/BindingGenerator.lua new file mode 100755 index 0000000..a1f2f2c --- /dev/null +++ b/scripts/BindingGenerator.lua @@ -0,0 +1,430 @@ +#!/usr/bin/lua +-- BindingGenerator.lua (c) hecks 2021 +-- This script is a part of IrrlichtMT, released under the same license. + +-- By default we assume you're running this from /scripts/ +-- and that you have the necessary headers there (gitignored for your convenience) +local sourceTreePath = os.getenv( "IRRMTREPO" ) or ".."; +-- Otherwise run this from wherever you want and set the above env variable. +local glHeaderPath = os.getenv( "GLHEADERPATH" ) or "."; +-- GL headers will be looked for in the current directory or GLHEADERPATH. +-- At the moment we require: +-- "glcorearb.h" +-- "gl2ext.h" +-- Files other than glcorearb.h are only parsed for vendor specific defines +-- and aliases. Otherwise we only use what exists in glcorearb.h, further +-- restricted to procedures that are either core or ARB. + + +-- Emulate a portion of the libraries that this was written against. +getmetatable( "" ).__index = string; +getmetatable( "" ).__len = string.len; +getmetatable( "" ).__call = string.format; +function string:Split( pat ) + local r = {}; + local pos = 1; + local from, to; + while pos and pos <= #self do + from, to = self:find( pat, pos ); + if not from then + break; + end + r[#r+1] = self:sub( pos, from - 1 ); + pos = to + 1; + end + r[#r+1] = self:sub( pos, #self ); + return r; +end +function string:TrimBothEnds() + return self:gsub("^%s+",""):gsub("%s+$",""); +end +local List; +List = function( t ) + return setmetatable( t or {}, { + __index = { + Add = function( t, v ) + t[#t+1] = v; + end; + AddFormat = function( t, str, ... ) + t:Add( str( ... ) ); + end; + Where = function( t, f ) + local r = {}; + for i=1, #t do + if f(t[i]) then r[#r+1] = t[i]; end + end + return List( r ); + end; + Select = function( t, f ) + local r = {}; + for i=1, #t do + r[#r+1] = f( t[i] ); + end + return List( r ); + end; + Join = function( t, n ) + local r = {}; + for i=1, #t do + r[i] = t[i]; + end + for i=1, #n do + r[#r+1] = n[i]; + end + return List( r ); + end; + Concat = table.concat; + }; + } ); +end + + +------------ Header parsing ------------ + +-- GL and GLES alike +local driverVendors = { + "NV", "AMD", "INTEL", "OVR", "QCOM", "IMG", "ANGLE", "APPLE", "MESA" +} +local vendorSuffixes = { + "ARB", "EXT", "KHR", "OES", + unpack( driverVendors ) +}; +local vendorSuffixPattern = {}; +local constSuffixPattern = {}; +for i=1, #vendorSuffixes do + vendorSuffixPattern[i] = vendorSuffixes[i] .. "$"; + constSuffixPattern[i] = ("_%s$")( vendorSuffixes[i] ); +end +local constBanned = {}; +for i=1, #driverVendors do + constBanned[driverVendors[i]] = true; +end +-- Strip the uppercase extension vendor suffix from a name. +local function StripVendorSuffix( str, const ) + local patterns = const and constSuffixPattern or vendorSuffixPattern; + local n; + for i=1, #patterns do + str, n = str:gsub( patterns[i], "" ); + if n > 0 then + return str, vendorSuffixes[i]; + end + end + return str; +end + +-- Normalize the type of an argument or return, also stripping any suffix +-- and normalizing all whitespace regions to single spaces. +local function NormalizeType( str ) + local chunks = str:Split( "%s+" ); + for j=1, #chunks do + chunks[j] = StripVendorSuffix( chunks[j] ); + end + local T = table.concat(chunks, " " ); + return T:TrimBothEnds(); +end + +-- Normalize an argument, returning the normalized type and the name separately, +-- always sticking the * of a pointer to the type rather than the name. +-- We need this to generate a normalized arg list and function signature (below) +local function NormalizeArgument( str ) + local chunks = str:Split( "%s+" ); + for j=1, #chunks do + chunks[j] = StripVendorSuffix( chunks[j] ); + end + local last = chunks[#chunks]; + local name = last:match( "[%w_]+$" ); + chunks[#chunks] = #name ~= #last and last:sub( 1, #last-#name) or nil; + local T = table.concat(chunks, " " ):TrimBothEnds(); + return T, name +end + +-- Normalize an argument list so that two matching prototypes +-- will produce the same table if fed to this function. +local function NormalizeArgList( str ) + local args = str:Split( ",%s*" ); + local r = {}; + for i=1, #args do + local T, name = NormalizeArgument( args[i] ); + r[i] = { T, name }; + end + return r; +end + +-- Normalize a function signature into a unique string for keying +-- in such a way that if two different GL procedures may be assigned +-- to the same function pointer, this will produce an identical string for both. +-- This makes it possible to detect function aliases that may work as a fallback. +-- You still have to check for the appropriate extension. +local function NormalizeFunctionSignature( T, str ) + local args = str:Split( ",%s*" ); + local r = {}; + for i=1, #args do + r[i] = NormalizeArgument( args[i] ); + end + return ("%s(%s)")( T, table.concat( r, ", " ) ); +end + +-- Mangle the PFN name so that we don't collide with a +-- typedef from any of the GL headers. +local pfnFormat = "PFNGL%sPROC_MT"; + +--( T, name, args ) +local typedefFormat = "\ttypedef %s (APIENTRYP %s) (%s);" +-- Generate a PFN...GL style typedef for a procedure +-- +local function GetProcedureTypedef( proc ) + local args = {}; + for i=1, #proc.args do + args[i] = ("%s %s")( unpack( proc.args[i] ) ) + end + return typedefFormat( proc.retType, pfnFormat( proc.name:upper() ), table.concat( args, ", " ) ); +end + +local procedures = List(); +local nameset = {}; +local definitions = List(); +local consts = List(); + +--[[ + Structured procedure representation: + + ProcSpec = { + string name; -- Normalized name as it appears in the GL spec + string? vendor; -- Uppercase vendor string (ARB, EXT, AMD, NV etc) + string signature; + string retType; + args = { { type, name } }; + }; +]] +-- Parse a whole header, extracting the data. +local function ParseHeader( path, into, apiRegex, defs, consts, nameSet, noNewNames ) + defs:AddFormat( "\t// %s", path ); + local f = assert( io.open( path, "r" ), "Could not open " .. path ); + for line in f:lines() do + -- Do not parse PFN typedefs; they're easily reconstructible. + local T, rawName, args = line:match( apiRegex ); + if T then + T = NormalizeType( T ); + -- Strip the 'gl' namespace prefix. + local procName = rawName:sub(3,-1); + local name, vendor = StripVendorSuffix( procName ); + if not (noNewNames and nameSet[name]) then + nameSet[name] = true; + into:Add{ + name = name; + vendor = vendor; + -- pfnType = pfnFormat( procName:upper() ); + signature = NormalizeFunctionSignature( T, args ); + retType = T; + args = NormalizeArgList( args ); + }; + end + elseif ( line:find( "#" ) and not line:find( "#include" ) ) then + local rawName, value = line:match( "#define%s+GL_([_%w]+)%s+0x(%w+)" ); + if rawName and value then + local name, vendor = StripVendorSuffix( rawName, true ); + if not constBanned[vendor] then + consts:Add{ name = name, vendor = vendor, value = "0x"..value }; + end + end + ::skip:: + elseif( line:find( "typedef" ) and not line:find( "%(" ) ) then + -- Passthrough non-PFN typedefs + defs:Add( "\t" .. line ); + end + end + defs:Add ""; + f:close(); +end + +------------ Parse the headers ------------ + +-- ES/gl2.h is a subset of glcorearb.h and does not need parsing. + +local funcRegex = "GLAPI%s+(.+)APIENTRY%s+(%w+)%s*%((.*)%)"; +local funcRegexES = "GL_APICALL%s+(.+)GL_APIENTRY%s+(%w+)%s*%((.*)%)"; +ParseHeader( glHeaderPath .. "/glcorearb.h", procedures, funcRegex, definitions, consts, nameset ); +ParseHeader( glHeaderPath .. "/gl2ext.h", procedures, funcRegexES, List(), consts, nameset, true ); +-- Typedefs are redirected to a dummy list here on purpose. +-- The only unique typedef from gl2ext is this: +definitions:Add "\ttypedef void *GLeglClientBufferEXT;"; + +------------ Sort out constants ------------ + +local cppConsts = List(); +do + local constBuckets = {}; + for i=1, #consts do + local vendor = consts[i].vendor or "core"; + constBuckets[consts[i].name] = constBuckets[consts[i].name] or {}; + constBuckets[consts[i].name][vendor] = consts[i].value; + end + local names = {}; + for i=1, #consts do + local k = consts[i].name; + local b = constBuckets[k]; + if k == "WAIT_FAILED" or k == "DIFFERENCE" then + -- This is why using #define as const is evil. + k = "_" .. k; + end + if b and not names[k] then + names[k] = true; + -- I have empirically tested that constants in GL with the same name do not differ, + -- at least for these suffixes. + local v = b.core or b.KHR or b.ARB or b.OES or b.EXT; + if v then + local T = v:find( "ull" ) and "GLuint64" or "GLenum"; + cppConsts:AddFormat( "\tstatic constexpr const %s %s = %s;", T, k, v ); + end + end + end +end + + +------------ Sort out procedures ------------ + +local procTable = {}; + +local coreProcedures = procedures:Where( function(x) return not x.vendor; end ); +local arbProcedures = procedures:Where( function(x) return x.vendor == "ARB"; end ); + +-- Only consider core and ARB functions. +local nameList = coreProcedures:Join( arbProcedures ):Select( + function(p) + return p.name; + end ); + +local nameSet = {}; +local uniqueNames = List(); + +for s, k in ipairs( nameList ) do + if not nameSet[k] then + nameSet[k] = true; + uniqueNames:Add( k ); + end +end + +for i=1, #procedures do + local p = procedures[i]; + procTable[p.name] = procTable[p.name] or {}; + local key = p.vendor or "core"; + procTable[p.name][key] = p; +end + +local priorityList = List{ "core", "ARB", "OES", "KHR" }; + +local typedefs = List(); +local pointers = List(); +local loader = List(); + +for s, str in ipairs( uniqueNames ) do + pointers:Add( ("\t%s %s = NULL;")( pfnFormat( str:upper() ), str ) ); + local typeDefGenerated = false; + for i=1, #priorityList do + local k = priorityList[i]; + local proc = procTable[str][k] + if proc then + if not typeDefGenerated then + typedefs:Add( GetProcedureTypedef( proc ) ); + typeDefGenerated = true; + end + local vendor = k == "core" and "" or k; + loader:AddFormat( + '\tif (!%s) %s = (%s)cmgr->getProcAddress("%s");\n', + str, str, pfnFormat( proc.name:upper() ), ("gl%s%s")(str,vendor) + ); + end + end +end + + +------------ Write files ------------ + +-- Write loader header +local f = io.open( sourceTreePath .. "/include/mt_opengl.h", "wb" ); +f:write[[ +// This code was generated by scripts/BindingGenerator.lua +// Do not modify it, modify and run the generator instead. + +#pragma once + +#include <string> +#include <unordered_set> +#include "IrrCompileConfig.h" +#include "irrTypes.h" +#include "IContextManager.h" +#include <KHR/khrplatform.h> + +#ifndef APIENTRY + #define APIENTRY +#endif +#ifndef APIENTRYP + #define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI + #define GLAPI extern +#endif + +]]; + +f:write[[ +class OpenGLProcedures { +private: +]]; +f:write( definitions:Concat( "\n" ) ); +f:write( "\n" ); +f:write[[ + // The script will miss this particular typedef thinking it's a PFN, + // so we have to paste it in manually. It's the only such type in OpenGL. + typedef void (APIENTRY *GLDEBUGPROC) + (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); + +]] +f:write( typedefs:Concat( "\n" ) ); +f:write( "\n\n" ); +f:write [[ + std::unordered_set<std::string> extensions; +public: + // Call this once after creating the context. + void LoadAllProcedures(irr::video::IContextManager *cmgr); + // Check if an extension is supported. + inline bool IsExtensionPresent(const std::string &ext) + { + return extensions.find(ext) != extensions.end(); + } + +]]; +f:write( pointers:Concat( "\n" ) ); +f:write( "\n\n" ); +f:write( cppConsts:Concat( "\n" ) ); +f:write( "\n\n" ); +f:write( "};\n" ); +f:write( "\n//Global GL procedures object.\n" ); +f:write( "IRRLICHT_API extern OpenGLProcedures GL;\n" ); +f:close(); + +-- Write loader implementation +f = io.open( sourceTreePath .. "/source/Irrlicht/mt_opengl_loader.cpp", "wb" ); +f:write[[ +#include "mt_opengl.h" +#include <string> +#include <sstream> + +OpenGLProcedures GL = OpenGLProcedures(); + +void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) +{ + +]]; +f:write( loader:Concat() ); +f:write[[ + + // get the extension string, chop it up + std::string ext_string = std::string((char*)GetString(EXTENSIONS)); + std::stringstream ext_ss(ext_string); + std::string tmp; + while (std::getline(ext_ss, tmp, ' ')) + extensions.emplace(tmp); + +} +]]; +f:close(); |