2 -- BindingGenerator.lua (c) hecks 2021
3 -- This script is a part of IrrlichtMT, released under the same license.
5 -- By default we assume you're running this from /scripts/
6 -- and that you have the necessary headers there (gitignored for your convenience)
7 local sourceTreePath
= os
.getenv( "IRRMTREPO" ) or "..";
8 -- Otherwise run this from wherever you want and set the above env variable.
9 local glHeaderPath
= os
.getenv( "GLHEADERPATH" ) or ".";
10 -- GL headers will be looked for in the current directory or GLHEADERPATH.
11 -- At the moment we require:
14 -- Files other than glcorearb.h are only parsed for vendor specific defines
15 -- and aliases. Otherwise we only use what exists in glcorearb.h, further
16 -- restricted to procedures that are either core or ARB.
19 -- Emulate a portion of the libraries that this was written against.
20 getmetatable( "" ).__index
= string;
21 getmetatable( "" ).__len
= string.len
;
22 getmetatable( "" ).__call
= string.format;
23 function string:Split( pat
)
27 while pos
and pos
<= #self
do
28 from
, to
= self
:find( pat
, pos
);
32 r
[#r
+1] = self
:sub( pos
, from
- 1 );
35 r
[#r
+1] = self
:sub( pos
, #self
);
38 function string:TrimBothEnds()
39 return self
:gsub("^%s+",""):gsub("%s+$","");
43 return setmetatable( t
or {}, {
45 Add
= function( t
, v
)
48 AddFormat
= function( t
, str
, ... )
51 Where
= function( t
, f
)
54 if f(t
[i
]) then r
[#r
+1] = t
[i
]; end
58 Select
= function( t
, f
)
65 Join
= function( t
, n
)
75 Concat
= table.concat
;
81 ------------ Header parsing ------------
84 local driverVendors
= {
85 "NV", "AMD", "INTEL", "OVR", "QCOM", "IMG", "ANGLE", "APPLE", "MESA"
87 local vendorSuffixes
= {
88 "ARB", "EXT", "KHR", "OES",
89 unpack( driverVendors
)
91 local vendorSuffixPattern
= {};
92 local constSuffixPattern
= {};
93 for i
=1, #vendorSuffixes
do
94 vendorSuffixPattern
[i
] = vendorSuffixes
[i
] .. "$";
95 constSuffixPattern
[i
] = ("_%s$")( vendorSuffixes
[i
] );
97 local constBanned
= {};
98 for i
=1, #driverVendors
do
99 constBanned
[driverVendors
[i]]
= true;
101 -- Strip the uppercase extension vendor suffix from a name.
102 local function StripVendorSuffix( str
, const
)
103 local patterns
= const
and constSuffixPattern
or vendorSuffixPattern
;
105 for i
=1, #patterns
do
106 str
, n
= str
:gsub( patterns
[i
], "" );
108 return str
, vendorSuffixes
[i
];
114 -- Normalize the type of an argument or return, also stripping any suffix
115 -- and normalizing all whitespace regions to single spaces.
116 local function NormalizeType( str
)
117 local chunks
= str
:Split( "%s+" );
119 chunks
[j
] = StripVendorSuffix( chunks
[j
] );
121 local T
= table.concat(chunks
, " " );
122 return T
:TrimBothEnds();
125 -- Normalize an argument, returning the normalized type and the name separately,
126 -- always sticking the * of a pointer to the type rather than the name.
127 -- We need this to generate a normalized arg list and function signature (below)
128 local function NormalizeArgument( str
)
129 local chunks
= str
:Split( "%s+" );
131 chunks
[j
] = StripVendorSuffix( chunks
[j
] );
133 local last
= chunks
[#chunks
];
134 local name
= last
:match( "[%w_]+$" );
135 chunks
[#chunks
] = #name
~= #last
and last
:sub( 1, #last
-#name
) or nil;
136 local T
= table.concat(chunks
, " " ):TrimBothEnds();
140 -- Normalize an argument list so that two matching prototypes
141 -- will produce the same table if fed to this function.
142 local function NormalizeArgList( str
)
143 local args
= str
:Split( ",%s*" );
146 local T
, name
= NormalizeArgument( args
[i
] );
152 -- Normalize a function signature into a unique string for keying
153 -- in such a way that if two different GL procedures may be assigned
154 -- to the same function pointer, this will produce an identical string for both.
155 -- This makes it possible to detect function aliases that may work as a fallback.
156 -- You still have to check for the appropriate extension.
157 local function NormalizeFunctionSignature( T
, str
)
158 local args
= str
:Split( ",%s*" );
161 r
[i
] = NormalizeArgument( args
[i
] );
163 return ("%s(%s)")( T
, table.concat( r
, ", " ) );
166 -- Mangle the PFN name so that we don't collide with a
167 -- typedef from any of the GL headers.
168 local pfnFormat
= "PFNGL%sPROC_MT";
171 local typedefFormat
= "\ttypedef %s (APIENTRYP %s) (%s);"
172 -- Generate a PFN...GL style typedef for a procedure
174 local function GetProcedureTypedef( proc
)
176 for i
=1, #proc
.args
do
177 args
[i
] = ("%s %s")( unpack( proc
.args
[i
] ) )
179 return typedefFormat( proc
.retType
, pfnFormat( proc
.name
:upper() ), table.concat( args
, ", " ) );
182 local procedures
= List();
184 local definitions
= List();
185 local consts
= List();
188 Structured procedure representation:
191 string name; -- Normalized name as it appears in the GL spec
192 string? vendor; -- Uppercase vendor string (ARB, EXT, AMD, NV etc)
195 args = { { type, name } };
198 -- Parse a whole header, extracting the data.
199 local function ParseHeader( path
, into
, apiRegex
, defs
, consts
, nameSet
, noNewNames
)
200 defs
:AddFormat( "\t// %s", path
);
201 local f
= assert( io
.open( path
, "r" ) );
202 for line
in f
:lines() do
203 -- Do not parse PFN typedefs; they're easily reconstructible.
204 local T
, rawName
, args
= line
:match( apiRegex
);
206 T
= NormalizeType( T
);
207 -- Strip the 'gl' namespace prefix.
208 local procName
= rawName
:sub(3,-1);
209 local name
, vendor
= StripVendorSuffix( procName
);
210 if not (noNewNames
and nameSet
[name
]) then
211 nameSet
[name
] = true;
215 -- pfnType = pfnFormat( procName:upper() );
216 signature
= NormalizeFunctionSignature( T
, args
);
218 args
= NormalizeArgList( args
);
221 elseif ( line
:find( "#" ) and not line
:find( "#include" ) ) then
222 local rawName
, value
= line
:match( "#define%s+GL_([_%w]+)%s+(0x%w+)" );
223 if rawName
and value
then
224 local name
, vendor
= StripVendorSuffix( rawName
, true );
225 if not constBanned
[vendor
] then
226 consts
:Add
{ name
= name
, vendor
= vendor
, value
= value
};
230 elseif( line
:find( "typedef" ) and not line
:find( "%(" ) ) then
231 -- Passthrough non-PFN typedefs
232 defs
:Add( "\t" .. line
);
239 ------------ Parse the headers ------------
241 -- ES/gl2.h is a subset of glcorearb.h and does not need parsing.
243 local funcRegex
= "GLAPI%s+(.+)APIENTRY%s+(%w+)%s*%((.*)%)";
244 local funcRegexES
= "GL_APICALL%s+(.+)GL_APIENTRY%s+(%w+)%s*%((.*)%)";
245 ParseHeader( glHeaderPath
.. "/glcorearb.h", procedures
, funcRegex
, definitions
, consts
, nameset
);
246 ParseHeader( glHeaderPath
.. "/gl2ext.h", procedures
, funcRegexES
, List(), consts
, nameset
, true );
247 -- Typedefs are redirected to a dummy list here on purpose.
248 -- The only unique typedef from gl2ext is this:
249 definitions
:Add
"\ttypedef void *GLeglClientBufferEXT;";
251 ------------ Sort out constants ------------
253 local cppConsts
= List();
255 local constBuckets
= {};
257 local vendor
= consts
[i
].vendor
or "core";
258 constBuckets
[consts
[i
].name
] = constBuckets
[consts
[i
].name
] or {};
259 constBuckets
[consts
[i
].name
][vendor
] = consts
[i
].value
;
263 local k
= consts
[i
].name
;
264 local b
= constBuckets
[k
];
265 if k
== "WAIT_FAILED" or k
== "DIFFERENCE" then
266 -- This is why using #define as const is evil.
269 if b
and not names
[k
] then
271 -- I have empirically tested that constants in GL with the same name do not differ,
272 -- at least for these suffixes.
273 local v
= b
.core
or b
.KHR
or b
.ARB
or b
.OES
or b
.EXT
;
275 local T
= v
:find( "ull" ) and "GLuint64" or "GLenum";
276 cppConsts
:AddFormat( "\tstatic constexpr const %s %s = %s;", T
, k
, v
);
283 ------------ Sort out procedures ------------
285 local procTable
= {};
287 local coreProcedures
= procedures
:Where( function(x
) return not x
.vendor
; end );
288 local arbProcedures
= procedures
:Where( function(x
) return x
.vendor
== "ARB"; end );
290 -- Only consider core and ARB functions.
291 local nameList
= coreProcedures
:Join( arbProcedures
):Select(
297 local uniqueNames
= List();
299 for s
, k
in ipairs( nameList
) do
300 if not nameSet
[k
] then
302 uniqueNames
:Add( k
);
306 for i
=1, #procedures
do
307 local p
= procedures
[i
];
308 procTable
[p
.name
] = procTable
[p
.name
] or {};
309 local key
= p
.vendor
or "core";
310 procTable
[p
.name
][key
] = p
;
313 local priorityList
= List
{ "core", "ARB", "OES", "KHR" };
315 local typedefs
= List();
316 local pointers
= List();
317 local loader
= List();
319 for s
, str
in ipairs( uniqueNames
) do
320 pointers
:Add( ("\t%s %s = NULL;")( pfnFormat( str
:upper() ), str
) );
321 local typeDefGenerated
= false;
322 for i
=1, #priorityList
do
323 local k
= priorityList
[i
];
324 local proc
= procTable
[str
][k
]
326 if not typeDefGenerated
then
327 typedefs
:Add( GetProcedureTypedef( proc
) );
328 typeDefGenerated
= true;
330 local vendor
= k
== "core" and "" or k
;
332 '\tif (!%s) %s = (%s)cmgr->getProcAddress("%s");\n',
333 str
, str
, pfnFormat( proc
.name
:upper() ), ("gl%s%s")(str
,vendor
)
340 ------------ Write files ------------
342 -- Write loader header
343 local f
= assert(io
.open( sourceTreePath
.. "/include/mt_opengl.h", "wb" ));
345 // This code was generated by scripts/BindingGenerator.lua
346 // Do not modify it, modify and run the generator instead.
351 #include <unordered_set>
352 #include "IrrCompileConfig.h" // for IRRLICHT_API
353 #include "IContextManager.h"
354 #include <KHR/khrplatform.h>
357 #define APIENTRY KHRONOS_APIENTRY
360 #define APIENTRYP APIENTRY *
362 // undefine a few names that can easily clash with system headers
376 class OpenGLProcedures final {
379 f
:write( definitions
:Concat( "\n" ) );
381 -- The script will miss this particular typedef thinking it's a PFN,
382 -- so we have to paste it in manually. It's the only such type in OpenGL.
384 typedef void (APIENTRY *GLDEBUGPROC)
385 (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
388 f
:write( typedefs
:Concat( "\n" ) );
391 std::unordered_set<std::string> extensions;
394 // Call this once after creating the context.
395 void LoadAllProcedures(irr::video::IContextManager *cmgr);
396 /// Check if an extension is supported.
397 /// @param ext full extension name e.g. "GL_KHR_no_error"
398 inline bool IsExtensionPresent(const std::string &ext) const
400 return extensions.count(ext) > 0;
404 f
:write( pointers
:Concat( "\n" ) );
406 f
:write( cppConsts
:Concat( "\n" ) );
408 -- We filter constants not in hex format to avoid the VERSION_X_X and extension
409 -- defines, but that means we miss these.
411 static constexpr const GLenum NO_ERROR = 0;
412 static constexpr const GLenum ZERO = 0;
413 static constexpr const GLenum ONE = 1;
414 static constexpr const GLenum NONE = 0;
417 f
:write( "\n// Global GL procedures object.\n" );
418 f
:write( "IRRLICHT_API extern OpenGLProcedures GL;\n" );
421 -- Write loader implementation
422 f
= assert(io
.open( sourceTreePath
.. "/src/mt_opengl_loader.cpp", "wb" ));
424 // This code was generated by scripts/BindingGenerator.lua
425 // Do not modify it, modify and run the generator instead.
427 #include "mt_opengl.h"
433 void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr)
437 f
:write( loader
:Concat() );
440 /* OpenGL 3 & ES 3 way to enumerate extensions */
442 GetIntegerv(NUM_EXTENSIONS, &ext_count);
443 // clear error which is raised if unsupported
444 while (GetError() != GL.NO_ERROR) {}
445 extensions.reserve(ext_count);
446 for (GLint k = 0; k < ext_count; k++) {
447 auto tmp = GetStringi(EXTENSIONS, k);
449 extensions.emplace((char*)tmp);
451 if (!extensions.empty())
454 /* OpenGL 2 / ES 2 way to enumerate extensions */
455 auto ext_str = GetString(EXTENSIONS);
458 // get the extension string, chop it up
459 std::istringstream ext_ss((char*)ext_str);
461 while (std::getline(ext_ss, tmp, ' '))
462 extensions.emplace(tmp);