123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- #!/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" ) );
- 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 = assert(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" // for IRRLICHT_API
- #include "IContextManager.h"
- #include <KHR/khrplatform.h>
- #ifndef APIENTRY
- #define APIENTRY KHRONOS_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) const
- {
- return extensions.count(ext) > 0;
- }
- ]];
- f:write( pointers:Concat( "\n" ) );
- f:write( "\n\n" );
- f:write( cppConsts:Concat( "\n" ) );
- f:write( "\n\n" );
- f:write[[
- static constexpr const GLenum ZERO = 0;
- static constexpr const GLenum ONE = 1;
- static constexpr const GLenum NONE = 0;
- ]];
- 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 = assert(io.open( sourceTreePath .. "/src/mt_opengl_loader.cpp", "wb" ));
- f:write[[
- // This code was generated by scripts/BindingGenerator.lua
- // Do not modify it, modify and run the generator instead.
- #include "mt_opengl.h"
- #include <string>
- #include <sstream>
- OpenGLProcedures GL = OpenGLProcedures();
- void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr)
- {
- ]];
- f:write( loader:Concat() );
- f:write[[
- // OpenGL 3 way to enumerate extensions
- GLint ext_count = 0;
- GetIntegerv(NUM_EXTENSIONS, &ext_count);
- extensions.reserve(ext_count);
- for (GLint k = 0; k < ext_count; k++) {
- auto tmp = GetStringi(EXTENSIONS, k);
- if (tmp)
- extensions.emplace((char*)tmp);
- }
- if (!extensions.empty())
- return;
- // OpenGL 2 / ES 2 way to enumerate extensions
- auto ext_str = GetString(EXTENSIONS);
- if (!ext_str)
- return;
- // get the extension string, chop it up
- std::stringstream ext_ss((char*)ext_str);
- std::string tmp;
- while (std::getline(ext_ss, tmp, ' '))
- extensions.emplace(tmp);
- }
- ]];
- f:close();
|