BindingGenerator.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #!/usr/bin/lua
  2. -- BindingGenerator.lua (c) hecks 2021
  3. -- This script is a part of IrrlichtMT, released under the same license.
  4. -- By default we assume you're running this from /scripts/
  5. -- and that you have the necessary headers there (gitignored for your convenience)
  6. local sourceTreePath = os.getenv( "IRRMTREPO" ) or "..";
  7. -- Otherwise run this from wherever you want and set the above env variable.
  8. local glHeaderPath = os.getenv( "GLHEADERPATH" ) or ".";
  9. -- GL headers will be looked for in the current directory or GLHEADERPATH.
  10. -- At the moment we require:
  11. -- "glcorearb.h"
  12. -- "gl2ext.h"
  13. -- Files other than glcorearb.h are only parsed for vendor specific defines
  14. -- and aliases. Otherwise we only use what exists in glcorearb.h, further
  15. -- restricted to procedures that are either core or ARB.
  16. -- Emulate a portion of the libraries that this was written against.
  17. getmetatable( "" ).__index = string;
  18. getmetatable( "" ).__len = string.len;
  19. getmetatable( "" ).__call = string.format;
  20. function string:Split( pat )
  21. local r = {};
  22. local pos = 1;
  23. local from, to;
  24. while pos and pos <= #self do
  25. from, to = self:find( pat, pos );
  26. if not from then
  27. break;
  28. end
  29. r[#r+1] = self:sub( pos, from - 1 );
  30. pos = to + 1;
  31. end
  32. r[#r+1] = self:sub( pos, #self );
  33. return r;
  34. end
  35. function string:TrimBothEnds()
  36. return self:gsub("^%s+",""):gsub("%s+$","");
  37. end
  38. local List;
  39. List = function( t )
  40. return setmetatable( t or {}, {
  41. __index = {
  42. Add = function( t, v )
  43. t[#t+1] = v;
  44. end;
  45. AddFormat = function( t, str, ... )
  46. t:Add( str( ... ) );
  47. end;
  48. Where = function( t, f )
  49. local r = {};
  50. for i=1, #t do
  51. if f(t[i]) then r[#r+1] = t[i]; end
  52. end
  53. return List( r );
  54. end;
  55. Select = function( t, f )
  56. local r = {};
  57. for i=1, #t do
  58. r[#r+1] = f( t[i] );
  59. end
  60. return List( r );
  61. end;
  62. Join = function( t, n )
  63. local r = {};
  64. for i=1, #t do
  65. r[i] = t[i];
  66. end
  67. for i=1, #n do
  68. r[#r+1] = n[i];
  69. end
  70. return List( r );
  71. end;
  72. Concat = table.concat;
  73. };
  74. } );
  75. end
  76. ------------ Header parsing ------------
  77. -- GL and GLES alike
  78. local driverVendors = {
  79. "NV", "AMD", "INTEL", "OVR", "QCOM", "IMG", "ANGLE", "APPLE", "MESA"
  80. }
  81. local vendorSuffixes = {
  82. "ARB", "EXT", "KHR", "OES",
  83. unpack( driverVendors )
  84. };
  85. local vendorSuffixPattern = {};
  86. local constSuffixPattern = {};
  87. for i=1, #vendorSuffixes do
  88. vendorSuffixPattern[i] = vendorSuffixes[i] .. "$";
  89. constSuffixPattern[i] = ("_%s$")( vendorSuffixes[i] );
  90. end
  91. local constBanned = {};
  92. for i=1, #driverVendors do
  93. constBanned[driverVendors[i]] = true;
  94. end
  95. -- Strip the uppercase extension vendor suffix from a name.
  96. local function StripVendorSuffix( str, const )
  97. local patterns = const and constSuffixPattern or vendorSuffixPattern;
  98. local n;
  99. for i=1, #patterns do
  100. str, n = str:gsub( patterns[i], "" );
  101. if n > 0 then
  102. return str, vendorSuffixes[i];
  103. end
  104. end
  105. return str;
  106. end
  107. -- Normalize the type of an argument or return, also stripping any suffix
  108. -- and normalizing all whitespace regions to single spaces.
  109. local function NormalizeType( str )
  110. local chunks = str:Split( "%s+" );
  111. for j=1, #chunks do
  112. chunks[j] = StripVendorSuffix( chunks[j] );
  113. end
  114. local T = table.concat(chunks, " " );
  115. return T:TrimBothEnds();
  116. end
  117. -- Normalize an argument, returning the normalized type and the name separately,
  118. -- always sticking the * of a pointer to the type rather than the name.
  119. -- We need this to generate a normalized arg list and function signature (below)
  120. local function NormalizeArgument( str )
  121. local chunks = str:Split( "%s+" );
  122. for j=1, #chunks do
  123. chunks[j] = StripVendorSuffix( chunks[j] );
  124. end
  125. local last = chunks[#chunks];
  126. local name = last:match( "[%w_]+$" );
  127. chunks[#chunks] = #name ~= #last and last:sub( 1, #last-#name) or nil;
  128. local T = table.concat(chunks, " " ):TrimBothEnds();
  129. return T, name
  130. end
  131. -- Normalize an argument list so that two matching prototypes
  132. -- will produce the same table if fed to this function.
  133. local function NormalizeArgList( str )
  134. local args = str:Split( ",%s*" );
  135. local r = {};
  136. for i=1, #args do
  137. local T, name = NormalizeArgument( args[i] );
  138. r[i] = { T, name };
  139. end
  140. return r;
  141. end
  142. -- Normalize a function signature into a unique string for keying
  143. -- in such a way that if two different GL procedures may be assigned
  144. -- to the same function pointer, this will produce an identical string for both.
  145. -- This makes it possible to detect function aliases that may work as a fallback.
  146. -- You still have to check for the appropriate extension.
  147. local function NormalizeFunctionSignature( T, str )
  148. local args = str:Split( ",%s*" );
  149. local r = {};
  150. for i=1, #args do
  151. r[i] = NormalizeArgument( args[i] );
  152. end
  153. return ("%s(%s)")( T, table.concat( r, ", " ) );
  154. end
  155. -- Mangle the PFN name so that we don't collide with a
  156. -- typedef from any of the GL headers.
  157. local pfnFormat = "PFNGL%sPROC_MT";
  158. --( T, name, args )
  159. local typedefFormat = "\ttypedef %s (APIENTRYP %s) (%s);"
  160. -- Generate a PFN...GL style typedef for a procedure
  161. --
  162. local function GetProcedureTypedef( proc )
  163. local args = {};
  164. for i=1, #proc.args do
  165. args[i] = ("%s %s")( unpack( proc.args[i] ) )
  166. end
  167. return typedefFormat( proc.retType, pfnFormat( proc.name:upper() ), table.concat( args, ", " ) );
  168. end
  169. local procedures = List();
  170. local nameset = {};
  171. local definitions = List();
  172. local consts = List();
  173. --[[
  174. Structured procedure representation:
  175. ProcSpec = {
  176. string name; -- Normalized name as it appears in the GL spec
  177. string? vendor; -- Uppercase vendor string (ARB, EXT, AMD, NV etc)
  178. string signature;
  179. string retType;
  180. args = { { type, name } };
  181. };
  182. ]]
  183. -- Parse a whole header, extracting the data.
  184. local function ParseHeader( path, into, apiRegex, defs, consts, nameSet, noNewNames )
  185. defs:AddFormat( "\t// %s", path );
  186. local f = assert( io.open( path, "r" ) );
  187. for line in f:lines() do
  188. -- Do not parse PFN typedefs; they're easily reconstructible.
  189. local T, rawName, args = line:match( apiRegex );
  190. if T then
  191. T = NormalizeType( T );
  192. -- Strip the 'gl' namespace prefix.
  193. local procName = rawName:sub(3,-1);
  194. local name, vendor = StripVendorSuffix( procName );
  195. if not (noNewNames and nameSet[name]) then
  196. nameSet[name] = true;
  197. into:Add{
  198. name = name;
  199. vendor = vendor;
  200. -- pfnType = pfnFormat( procName:upper() );
  201. signature = NormalizeFunctionSignature( T, args );
  202. retType = T;
  203. args = NormalizeArgList( args );
  204. };
  205. end
  206. elseif ( line:find( "#" ) and not line:find( "#include" ) ) then
  207. local rawName, value = line:match( "#define%s+GL_([_%w]+)%s+0x(%w+)" );
  208. if rawName and value then
  209. local name, vendor = StripVendorSuffix( rawName, true );
  210. if not constBanned[vendor] then
  211. consts:Add{ name = name, vendor = vendor, value = "0x"..value };
  212. end
  213. end
  214. ::skip::
  215. elseif( line:find( "typedef" ) and not line:find( "%(" ) ) then
  216. -- Passthrough non-PFN typedefs
  217. defs:Add( "\t" .. line );
  218. end
  219. end
  220. defs:Add "";
  221. f:close();
  222. end
  223. ------------ Parse the headers ------------
  224. -- ES/gl2.h is a subset of glcorearb.h and does not need parsing.
  225. local funcRegex = "GLAPI%s+(.+)APIENTRY%s+(%w+)%s*%((.*)%)";
  226. local funcRegexES = "GL_APICALL%s+(.+)GL_APIENTRY%s+(%w+)%s*%((.*)%)";
  227. ParseHeader( glHeaderPath .. "/glcorearb.h", procedures, funcRegex, definitions, consts, nameset );
  228. ParseHeader( glHeaderPath .. "/gl2ext.h", procedures, funcRegexES, List(), consts, nameset, true );
  229. -- Typedefs are redirected to a dummy list here on purpose.
  230. -- The only unique typedef from gl2ext is this:
  231. definitions:Add "\ttypedef void *GLeglClientBufferEXT;";
  232. ------------ Sort out constants ------------
  233. local cppConsts = List();
  234. do
  235. local constBuckets = {};
  236. for i=1, #consts do
  237. local vendor = consts[i].vendor or "core";
  238. constBuckets[consts[i].name] = constBuckets[consts[i].name] or {};
  239. constBuckets[consts[i].name][vendor] = consts[i].value;
  240. end
  241. local names = {};
  242. for i=1, #consts do
  243. local k = consts[i].name;
  244. local b = constBuckets[k];
  245. if k == "WAIT_FAILED" or k == "DIFFERENCE" then
  246. -- This is why using #define as const is evil.
  247. k = "_" .. k;
  248. end
  249. if b and not names[k] then
  250. names[k] = true;
  251. -- I have empirically tested that constants in GL with the same name do not differ,
  252. -- at least for these suffixes.
  253. local v = b.core or b.KHR or b.ARB or b.OES or b.EXT;
  254. if v then
  255. local T = v:find( "ull" ) and "GLuint64" or "GLenum";
  256. cppConsts:AddFormat( "\tstatic constexpr const %s %s = %s;", T, k, v );
  257. end
  258. end
  259. end
  260. end
  261. ------------ Sort out procedures ------------
  262. local procTable = {};
  263. local coreProcedures = procedures:Where( function(x) return not x.vendor; end );
  264. local arbProcedures = procedures:Where( function(x) return x.vendor == "ARB"; end );
  265. -- Only consider core and ARB functions.
  266. local nameList = coreProcedures:Join( arbProcedures ):Select(
  267. function(p)
  268. return p.name;
  269. end );
  270. local nameSet = {};
  271. local uniqueNames = List();
  272. for s, k in ipairs( nameList ) do
  273. if not nameSet[k] then
  274. nameSet[k] = true;
  275. uniqueNames:Add( k );
  276. end
  277. end
  278. for i=1, #procedures do
  279. local p = procedures[i];
  280. procTable[p.name] = procTable[p.name] or {};
  281. local key = p.vendor or "core";
  282. procTable[p.name][key] = p;
  283. end
  284. local priorityList = List{ "core", "ARB", "OES", "KHR" };
  285. local typedefs = List();
  286. local pointers = List();
  287. local loader = List();
  288. for s, str in ipairs( uniqueNames ) do
  289. pointers:Add( ("\t%s %s = NULL;")( pfnFormat( str:upper() ), str ) );
  290. local typeDefGenerated = false;
  291. for i=1, #priorityList do
  292. local k = priorityList[i];
  293. local proc = procTable[str][k]
  294. if proc then
  295. if not typeDefGenerated then
  296. typedefs:Add( GetProcedureTypedef( proc ) );
  297. typeDefGenerated = true;
  298. end
  299. local vendor = k == "core" and "" or k;
  300. loader:AddFormat(
  301. '\tif (!%s) %s = (%s)cmgr->getProcAddress("%s");\n',
  302. str, str, pfnFormat( proc.name:upper() ), ("gl%s%s")(str,vendor)
  303. );
  304. end
  305. end
  306. end
  307. ------------ Write files ------------
  308. -- Write loader header
  309. local f = assert(io.open( sourceTreePath .. "/include/mt_opengl.h", "wb" ));
  310. f:write[[
  311. // This code was generated by scripts/BindingGenerator.lua
  312. // Do not modify it, modify and run the generator instead.
  313. #pragma once
  314. #include <string>
  315. #include <unordered_set>
  316. #include "IrrCompileConfig.h" // for IRRLICHT_API
  317. #include "IContextManager.h"
  318. #include <KHR/khrplatform.h>
  319. #ifndef APIENTRY
  320. #define APIENTRY KHRONOS_APIENTRY
  321. #endif
  322. #ifndef APIENTRYP
  323. #define APIENTRYP APIENTRY *
  324. #endif
  325. #ifndef GLAPI
  326. #define GLAPI extern
  327. #endif
  328. ]];
  329. f:write[[
  330. class OpenGLProcedures {
  331. private:
  332. ]];
  333. f:write( definitions:Concat( "\n" ) );
  334. f:write( "\n" );
  335. f:write[[
  336. // The script will miss this particular typedef thinking it's a PFN,
  337. // so we have to paste it in manually. It's the only such type in OpenGL.
  338. typedef void (APIENTRY *GLDEBUGPROC)
  339. (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
  340. ]]
  341. f:write( typedefs:Concat( "\n" ) );
  342. f:write( "\n\n" );
  343. f:write [[
  344. std::unordered_set<std::string> extensions;
  345. public:
  346. // Call this once after creating the context.
  347. void LoadAllProcedures(irr::video::IContextManager *cmgr);
  348. // Check if an extension is supported.
  349. inline bool IsExtensionPresent(const std::string &ext) const
  350. {
  351. return extensions.count(ext) > 0;
  352. }
  353. ]];
  354. f:write( pointers:Concat( "\n" ) );
  355. f:write( "\n\n" );
  356. f:write( cppConsts:Concat( "\n" ) );
  357. f:write( "\n\n" );
  358. f:write[[
  359. static constexpr const GLenum ZERO = 0;
  360. static constexpr const GLenum ONE = 1;
  361. static constexpr const GLenum NONE = 0;
  362. ]];
  363. f:write( "};\n" );
  364. f:write( "\n// Global GL procedures object.\n" );
  365. f:write( "IRRLICHT_API extern OpenGLProcedures GL;\n" );
  366. f:close();
  367. -- Write loader implementation
  368. f = assert(io.open( sourceTreePath .. "/src/mt_opengl_loader.cpp", "wb" ));
  369. f:write[[
  370. // This code was generated by scripts/BindingGenerator.lua
  371. // Do not modify it, modify and run the generator instead.
  372. #include "mt_opengl.h"
  373. #include <string>
  374. #include <sstream>
  375. OpenGLProcedures GL = OpenGLProcedures();
  376. void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr)
  377. {
  378. ]];
  379. f:write( loader:Concat() );
  380. f:write[[
  381. // OpenGL 3 way to enumerate extensions
  382. GLint ext_count = 0;
  383. GetIntegerv(NUM_EXTENSIONS, &ext_count);
  384. extensions.reserve(ext_count);
  385. for (GLint k = 0; k < ext_count; k++) {
  386. auto tmp = GetStringi(EXTENSIONS, k);
  387. if (tmp)
  388. extensions.emplace((char*)tmp);
  389. }
  390. if (!extensions.empty())
  391. return;
  392. // OpenGL 2 / ES 2 way to enumerate extensions
  393. auto ext_str = GetString(EXTENSIONS);
  394. if (!ext_str)
  395. return;
  396. // get the extension string, chop it up
  397. std::stringstream ext_ss((char*)ext_str);
  398. std::string tmp;
  399. while (std::getline(ext_ss, tmp, ' '))
  400. extensions.emplace(tmp);
  401. }
  402. ]];
  403. f:close();