Move SMaterial std::hash impl to its header
[minetest.git] / irr / scripts / BindingGenerator.lua
blob1ca5b41d97ba8de2fa0936643aee02d52523a8f8
1 #!/usr/bin/lua
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:
12 -- "glcorearb.h"
13 -- "gl2ext.h"
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 )
24 local r = {};
25 local pos = 1;
26 local from, to;
27 while pos and pos <= #self do
28 from, to = self:find( pat, pos );
29 if not from then
30 break;
31 end
32 r[#r+1] = self:sub( pos, from - 1 );
33 pos = to + 1;
34 end
35 r[#r+1] = self:sub( pos, #self );
36 return r;
37 end
38 function string:TrimBothEnds()
39 return self:gsub("^%s+",""):gsub("%s+$","");
40 end
41 local List;
42 List = function( t )
43 return setmetatable( t or {}, {
44 __index = {
45 Add = function( t, v )
46 t[#t+1] = v;
47 end;
48 AddFormat = function( t, str, ... )
49 t:Add( str( ... ) );
50 end;
51 Where = function( t, f )
52 local r = {};
53 for i=1, #t do
54 if f(t[i]) then r[#r+1] = t[i]; end
55 end
56 return List( r );
57 end;
58 Select = function( t, f )
59 local r = {};
60 for i=1, #t do
61 r[#r+1] = f( t[i] );
62 end
63 return List( r );
64 end;
65 Join = function( t, n )
66 local r = {};
67 for i=1, #t do
68 r[i] = t[i];
69 end
70 for i=1, #n do
71 r[#r+1] = n[i];
72 end
73 return List( r );
74 end;
75 Concat = table.concat;
77 } );
78 end
81 ------------ Header parsing ------------
83 -- GL and GLES alike
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] );
96 end
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;
104 local n;
105 for i=1, #patterns do
106 str, n = str:gsub( patterns[i], "" );
107 if n > 0 then
108 return str, vendorSuffixes[i];
111 return str;
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+" );
118 for j=1, #chunks do
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+" );
130 for j=1, #chunks do
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();
137 return T, name
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*" );
144 local r = {};
145 for i=1, #args do
146 local T, name = NormalizeArgument( args[i] );
147 r[i] = { T, name };
149 return r;
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*" );
159 local r = {};
160 for i=1, #args do
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";
170 --( T, name, args )
171 local typedefFormat = "\ttypedef %s (APIENTRYP %s) (%s);"
172 -- Generate a PFN...GL style typedef for a procedure
174 local function GetProcedureTypedef( proc )
175 local args = {};
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();
183 local nameset = {};
184 local definitions = List();
185 local consts = List();
187 --[[
188 Structured procedure representation:
190 ProcSpec = {
191 string name; -- Normalized name as it appears in the GL spec
192 string? vendor; -- Uppercase vendor string (ARB, EXT, AMD, NV etc)
193 string signature;
194 string retType;
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 );
205 if T then
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;
212 into:Add{
213 name = name;
214 vendor = vendor;
215 -- pfnType = pfnFormat( procName:upper() );
216 signature = NormalizeFunctionSignature( T, args );
217 retType = T;
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 };
229 ::skip::
230 elseif( line:find( "typedef" ) and not line:find( "%(" ) ) then
231 -- Passthrough non-PFN typedefs
232 defs:Add( "\t" .. line );
235 defs:Add "";
236 f:close();
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 = {};
256 for i=1, #consts do
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;
261 local names = {};
262 for i=1, #consts do
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.
267 k = "_" .. k;
269 if b and not names[k] then
270 names[k] = true;
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;
274 if v then
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(
292 function(p)
293 return p.name;
294 end );
296 local nameSet = {};
297 local uniqueNames = List();
299 for s, k in ipairs( nameList ) do
300 if not nameSet[k] then
301 nameSet[k] = true;
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]
325 if proc then
326 if not typeDefGenerated then
327 typedefs:Add( GetProcedureTypedef( proc ) );
328 typeDefGenerated = true;
330 local vendor = k == "core" and "" or k;
331 loader:AddFormat(
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" ));
344 f:write[[
345 // This code was generated by scripts/BindingGenerator.lua
346 // Do not modify it, modify and run the generator instead.
348 #pragma once
350 #include <string>
351 #include <unordered_set>
352 #include "IrrCompileConfig.h" // for IRRLICHT_API
353 #include "IContextManager.h"
354 #include <KHR/khrplatform.h>
356 #ifndef APIENTRY
357 #define APIENTRY KHRONOS_APIENTRY
358 #endif
359 #ifndef APIENTRYP
360 #define APIENTRYP APIENTRY *
361 #endif
362 // undefine a few names that can easily clash with system headers
363 #ifdef NO_ERROR
364 #undef NO_ERROR
365 #endif
366 #ifdef ZERO
367 #undef ZERO
368 #endif
369 #ifdef ONE
370 #undef ONE
371 #endif
375 f:write[[
376 class OpenGLProcedures final {
377 private:
379 f:write( definitions:Concat( "\n" ) );
380 f:write( "\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.
383 f:write[[
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" ) );
389 f:write( "\n\n" );
390 f:write [[
391 std::unordered_set<std::string> extensions;
393 public:
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" ) );
405 f:write( "\n\n" );
406 f:write( cppConsts:Concat( "\n" ) );
407 f:write( "\n\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.
410 f:write[[
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;
416 f:write( "};\n" );
417 f:write( "\n// Global GL procedures object.\n" );
418 f:write( "IRRLICHT_API extern OpenGLProcedures GL;\n" );
419 f:close();
421 -- Write loader implementation
422 f = assert(io.open( sourceTreePath .. "/src/mt_opengl_loader.cpp", "wb" ));
423 f:write[[
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"
428 #include <string>
429 #include <sstream>
431 OpenGLProcedures GL;
433 void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr)
437 f:write( loader:Concat() );
438 f:write[[
440 /* OpenGL 3 & ES 3 way to enumerate extensions */
441 GLint ext_count = 0;
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);
448 if (tmp)
449 extensions.emplace((char*)tmp);
451 if (!extensions.empty())
452 return;
454 /* OpenGL 2 / ES 2 way to enumerate extensions */
455 auto ext_str = GetString(EXTENSIONS);
456 if (!ext_str)
457 return;
458 // get the extension string, chop it up
459 std::istringstream ext_ss((char*)ext_str);
460 std::string tmp;
461 while (std::getline(ext_ss, tmp, ' '))
462 extensions.emplace(tmp);
465 f:close();