aboutsummaryrefslogtreecommitdiff
path: root/scripts/BindingGenerator.lua
diff options
context:
space:
mode:
authorhecks <42101236+hecktest@users.noreply.github.com>2021-08-07 21:56:00 +0200
committerGitHub <noreply@github.com>2021-08-07 21:56:00 +0200
commit5bf68b5731b1817c31959b8e3adf19a4c2307630 (patch)
treebcd0af6f07df105882f0353cd610abf00e26fe2e /scripts/BindingGenerator.lua
parent7709e1e5f8d8429308ae20d366171fdf6e64bee8 (diff)
downloadirrlicht-5bf68b5731b1817c31959b8e3adf19a4c2307630.tar.xz
Add a unified cross platform OpenGL core profile binding (#52)
Diffstat (limited to 'scripts/BindingGenerator.lua')
-rwxr-xr-xscripts/BindingGenerator.lua430
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();