]> git.lizzy.rs Git - irrlicht.git/blob - scripts/BindingGenerator.lua
Add back LightManager
[irrlicht.git] / scripts / BindingGenerator.lua
1 #!/usr/bin/lua
2 -- BindingGenerator.lua (c) hecks 2021
3 -- This script is a part of IrrlichtMT, released under the same license.
4
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.
17
18
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;
76                 };
77         } );
78 end
79
80
81 ------------ Header parsing ------------
82
83 -- GL and GLES alike
84 local driverVendors = {
85         "NV", "AMD", "INTEL", "OVR", "QCOM", "IMG", "ANGLE", "APPLE", "MESA"
86 }
87 local vendorSuffixes = {
88         "ARB", "EXT", "KHR", "OES",
89         unpack( driverVendors )
90 };
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;
100 end
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];
109                 end
110         end
111         return str;
112 end
113
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] );
120         end
121         local T = table.concat(chunks, " " );
122         return T:TrimBothEnds();
123 end
124
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] );
132         end
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
138 end
139
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 };
148         end
149         return r;
150 end
151
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] );
162         end
163         return ("%s(%s)")( T, table.concat( r, ", " ) );
164 end
165
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";
169
170 --( T, name, args )
171 local typedefFormat = "\ttypedef %s (APIENTRYP %s) (%s);"
172 -- Generate a PFN...GL style typedef for a procedure
173 --
174 local function GetProcedureTypedef( proc )
175         local args = {};
176         for i=1, #proc.args do
177                 args[i] = ("%s %s")( unpack( proc.args[i] ) )
178         end
179         return typedefFormat( proc.retType, pfnFormat( proc.name:upper() ), table.concat( args, ", " ) );
180 end
181
182 local procedures = List();
183 local nameset = {};
184 local definitions = List();
185 local consts = List();
186
187 --[[
188         Structured procedure representation:
189
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 } };
196         };
197 ]]
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" ), "Could not open " .. path );
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 );
219                                 };
220                         end
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 = "0x"..value };
227                                 end
228                         end
229                         ::skip::
230                 elseif( line:find( "typedef" ) and not line:find( "%(" ) ) then
231                         -- Passthrough non-PFN typedefs
232                         defs:Add( "\t" .. line );
233                 end
234         end
235         defs:Add "";
236         f:close();
237 end
238
239 ------------ Parse the headers ------------
240
241 -- ES/gl2.h is a subset of glcorearb.h and does not need parsing.
242
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;";
250
251 ------------ Sort out constants ------------
252
253 local cppConsts = List();
254 do
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;
260         end
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;
268                 end
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 );
277                         end
278                 end
279         end
280 end
281
282
283 ------------ Sort out procedures ------------
284
285 local procTable = {};
286
287 local coreProcedures = procedures:Where( function(x) return not x.vendor; end );
288 local arbProcedures = procedures:Where( function(x) return x.vendor == "ARB"; end );
289
290 -- Only consider core and ARB functions.
291 local nameList = coreProcedures:Join( arbProcedures ):Select(
292         function(p)
293                 return p.name;
294         end );
295
296 local nameSet = {};
297 local uniqueNames = List();
298
299 for s, k in ipairs( nameList ) do
300         if not nameSet[k] then
301                 nameSet[k] = true;
302                 uniqueNames:Add( k );
303         end
304 end
305
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;
311 end
312
313 local priorityList = List{ "core", "ARB", "OES", "KHR" };
314
315 local typedefs = List();
316 local pointers = List();
317 local loader = List();
318
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;
329                         end
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)
334                         );
335                 end
336         end
337 end
338
339
340 ------------ Write files ------------
341
342 -- Write loader header
343 local f = 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.
347
348 #pragma once
349
350 #include <string>
351 #include <unordered_set>
352 #include "IrrCompileConfig.h" // for IRRLICHT_API
353 #include "irrTypes.h"
354 #include "IContextManager.h"
355 #include <KHR/khrplatform.h>
356
357 #ifndef APIENTRY
358         #define APIENTRY
359 #endif
360 #ifndef APIENTRYP
361         #define APIENTRYP APIENTRY *
362 #endif
363 #ifndef GLAPI
364         #define GLAPI extern
365 #endif
366
367 ]];
368
369 f:write[[
370 class OpenGLProcedures {
371 private:
372 ]];
373 f:write( definitions:Concat( "\n" ) );
374 f:write( "\n" );
375 f:write[[
376         // The script will miss this particular typedef thinking it's a PFN,
377         // so we have to paste it in manually. It's the only such type in OpenGL.
378         typedef void (APIENTRY *GLDEBUGPROC)
379                 (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
380
381 ]]
382 f:write( typedefs:Concat( "\n" ) );
383 f:write( "\n\n" );
384 f:write [[
385         std::unordered_set<std::string> extensions;
386 public:
387         // Call this once after creating the context.
388         void LoadAllProcedures(irr::video::IContextManager *cmgr);
389         // Check if an extension is supported.
390         inline bool IsExtensionPresent(const std::string &ext)
391         {
392                 return extensions.find(ext) != extensions.end();
393         }
394
395 ]];
396 f:write( pointers:Concat( "\n" ) );
397 f:write( "\n\n" );
398 f:write( cppConsts:Concat( "\n" ) );
399 f:write( "\n\n" );
400 f:write[[
401         static constexpr const GLenum ZERO = 0;
402         static constexpr const GLenum ONE = 1;
403         static constexpr const GLenum NONE = 0;
404 ]];
405 f:write( "};\n" );
406 f:write( "\n//Global GL procedures object.\n" );
407 f:write( "IRRLICHT_API extern OpenGLProcedures GL;\n" );
408 f:close();
409
410 -- Write loader implementation
411 f = io.open( sourceTreePath .. "/source/Irrlicht/mt_opengl_loader.cpp", "wb" );
412 f:write[[
413 #include "mt_opengl.h"
414 #include <string>
415 #include <sstream>
416
417 OpenGLProcedures GL = OpenGLProcedures();
418
419 void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr)
420 {
421
422 ]];
423 f:write( loader:Concat() );
424 f:write[[
425
426         // get the extension string, chop it up
427         std::string ext_string = std::string((char*)GetString(EXTENSIONS));
428         std::stringstream ext_ss(ext_string);
429         std::string tmp;
430         while (std::getline(ext_ss, tmp, ' '))
431                 extensions.emplace(tmp);
432
433 }
434 ]];
435 f:close();