]> git.tdb.fi Git - libs/gl.git/blob - scripts/extgen.py
Enhance the extension generator to support different APIs
[libs/gl.git] / scripts / extgen.py
1 #!/usr/bin/python
2
3 import sys
4 import os
5 import xml.dom
6 import xml.dom.minidom
7 import itertools
8
9 ### Command line processing ###
10
11 if len(sys.argv)<2:
12         print """Usage:
13   extgen.py [api] <extension> [<core_version>] [<secondary> ...]
14   extgen.py [api] <extfile> [<outfile>]
15
16 Reads gl.xml and generates files to use <extension>.  Any promoted functions
17 are exposed with their promoted names.  If <secondary> extensions are given,
18 any promoted functions from those are pulled in as well.  <core_version> can
19 be given to override the version where <extension> was promoted to core.
20
21 In the second form, the parameters are read from <extfile>.  If <outfile> is
22 absent, the extension's lowercased name is used.  Anything after the last dot
23 in <outfile> is removed and replaced with cpp and h."""
24         sys.exit(0)
25
26 target_api = "gl"
27
28 i = 1
29 if sys.argv[i].startswith("gl"):
30         target_api = sys.argv[i]
31         i += 1
32
33 target_ext = sys.argv[i]
34 out_base = None
35 if target_ext.endswith(".glext"):
36         fn = target_ext
37         target_ext = None
38         core_version = None
39         secondary = []
40         for line in open(fn):
41                 parts = line.split()
42                 if parts[0]=="extension":
43                         target_ext = parts[1]
44                 elif parts[0]=="core_version":
45                         if parts[1]==target_api:
46                                 core_version = parts[2]
47                 elif parts[0]=="secondary":
48                         secondary.append(parts[1])
49         if i+1<len(sys.argv):
50                 out_base = os.path.splitext(sys.argv[i+1])[0]
51 else:
52         secondary = sys.argv[i+1:]
53         core_version = None
54         if secondary and secondary[0][0].isdigit():
55                 core_version = secondary.pop(0)
56
57 ext_type = target_ext.split('_')[0]
58 backport_ext = None
59
60 if core_version:
61         core_version = map(int, core_version.split('.'))
62
63 if not out_base:
64         out_base = target_ext.lower()
65
66 ### XML file parsing ###
67
68 class Thing:
69         FUNCTION = 1
70         ENUM = 2
71
72         def __init__(self, name, kind):
73                 self.name = name
74                 self.kind = kind
75                 self.version = None
76                 self.extension = None
77                 self.supported_apis = {}
78                 self.aliases = []
79                 self.sources = []
80
81 class Function(Thing):
82         def __init__(self, name):
83                 Thing.__init__(self, name, Thing.FUNCTION)
84                 self.return_type = "void"
85                 self.params = []
86                 self.typedef = None
87
88 class Enum(Thing):
89         def __init__(self, name):
90                 Thing.__init__(self, name, Thing.ENUM)
91                 self.value = 0
92                 self.bitmask = (name.endswith("_BIT") or "_BIT_" in name)
93
94 class Extension:
95         def __init__(self, name):
96                 self.name = name
97                 self.supported_apis = []
98                 self.ext_type = name[0:name.find('_')]
99
100 extensions = {}
101 things = {}
102
103 def get_nested_elements(elem, path):
104         childElements = [c for c in elem.childNodes if c.nodeType==xml.dom.Node.ELEMENT_NODE]
105         if '/' in path:
106                 head, tail = path.split('/', 1)
107                 result = []
108                 for c in childElements:
109                         if c.tagName==head:
110                                 result += get_nested_elements(c, tail)
111                 return result
112         else:
113                 return [c for c in childElements if c.tagName==path]
114
115 def get_first_child(elem, tag):
116         for c in elem.childNodes:
117                 if c.nodeType==xml.dom.Node.ELEMENT_NODE and c.tagName==tag:
118                         return c
119         return None
120
121 def get_text_contents(node):
122         result = ""
123         for c in node.childNodes:
124                 if c.nodeType==xml.dom.Node.TEXT_NODE or c.nodeType==xml.dom.Node.CDATA_SECTION_NODE:
125                         result += c.data
126                 else:
127                         result += get_text_contents(c)
128         return result
129
130 def parse_command(cmd):
131         proto = get_first_child(cmd, "proto")
132         name = get_text_contents(get_first_child(proto, "name"))
133         func = things.get(name)
134         if not func:
135                 func = Function(name)
136                 things[name] = func
137
138         aliases = get_nested_elements(cmd, "alias")
139         func.aliases = [a.getAttribute("name") for a in aliases]
140
141         ptype = get_first_child(proto, "ptype")
142         if ptype:
143                 func.return_type = get_text_contents(ptype)
144         else:
145                 for c in proto.childNodes:
146                         if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip():
147                                 func.return_type = c.data.strip()
148                                 break
149
150         params = get_nested_elements(cmd, "param")
151         func.params = map(get_text_contents, params)
152
153 def parse_enum(en):
154         name = en.getAttribute("name")
155         enum = things.get(name)
156         if not enum:
157                 enum = Enum(name)
158                 things[name] = enum
159
160         enum.value = int(en.getAttribute("value"), 16)
161
162 def parse_feature(feat):
163         api = feat.getAttribute("api")
164         version = feat.getAttribute("number")
165         if version:
166                 version = map(int, version.split('.'))
167         else:
168                 version = None
169
170         requires = get_nested_elements(feat, "require")
171         for req in requires:
172                 commands = get_nested_elements(req, "command")
173                 enums = get_nested_elements(req, "enum")
174                 for t in itertools.chain(commands, enums):
175                         name = t.getAttribute("name")
176                         thing = things.get(name)
177                         if thing:
178                                 thing.supported_apis.setdefault(api, version)
179
180         if not api or api==target_api:
181                 removes = get_nested_elements(feat, "remove")
182                 for rem in removes:
183                         profile = rem.getAttribute("profile")
184                         commands = get_nested_elements(rem, "command")
185                         enums = get_nested_elements(rem, "enum")
186
187                         for t in itertools.chain(commands, enums):
188                                 name = t.getAttribute("name")
189                                 if profile!="core" and name in things:
190                                         del things[name]
191
192 def parse_extension(ext):
193         ext_name = ext.getAttribute("name")
194         if ext_name.startswith("GL_"):
195                 ext_name = ext_name[3:]
196
197         supported = ext.getAttribute("supported").split('|')
198         if target_api not in supported and ext_name!=target_ext:
199                 return
200
201         extension = extensions.get(ext_name)
202         if not extension:
203                 extension = Extension(ext_name)
204                 extensions[ext_name] = extension
205
206         extension.supported_apis = supported
207
208         requires = get_nested_elements(ext, "require")
209         for req in requires:
210                 api = req.getAttribute("api")
211                 if api:
212                         supported = [api]
213                 else:
214                         supported = extension.supported_apis
215
216                 commands = get_nested_elements(req, "command")
217                 enums = get_nested_elements(req, "enum")
218                 for t in itertools.chain(commands, enums):
219                         name = t.getAttribute("name")
220                         thing = things.get(name)
221                         if thing:
222                                 if thing.extension and extension.name!=target_ext:
223                                         if thing.extension.ext_type=="ARB" or thing.extension.name==target_ext:
224                                                 continue
225                                         if thing.extension.ext_type=="EXT" and extension.ext_type!="ARB":
226                                                 continue
227
228                                 thing.extension = extension
229                                 for a in supported:
230                                         thing.supported_apis.setdefault(a, "ext")
231
232 def parse_file(fn):
233         doc = xml.dom.minidom.parse(fn)
234         root = doc.documentElement
235
236         commands = get_nested_elements(root, "commands/command")
237         for cmd in commands:
238                 parse_command(cmd)
239
240         enums = get_nested_elements(root, "enums/enum")
241         for en in enums:
242                 parse_enum(en)
243
244         features = get_nested_elements(root, "feature")
245         for feat in features:
246                 parse_feature(feat)
247
248         extensions = get_nested_elements(root, "extensions/extension")
249         for ext in extensions:
250                 parse_extension(ext)
251
252 parse_file("gl.xml")
253 parse_file("gl.fixes.xml")
254
255 ### Additional processing ###
256
257 if target_ext in extensions:
258         target_ext = extensions[target_ext]
259 else:
260         print "Extension %s not found"%target_ext
261         sys.exit(1)
262
263 # Find aliases for enums
264 enums = [t for t in things.itervalues() if t.kind==Thing.ENUM]
265 core_enums = [e for e in enums if any(v!="ext" for v in e.supported_apis.itervalues())]
266 core_enums_by_value = dict((e.value, None) for e in core_enums)
267
268 def get_key_api(things):
269         common_apis = set(target_ext.supported_apis)
270         for t in things:
271                 common_apis.intersection_update(t.supported_apis.keys())
272         if common_apis:
273                 return common_apis.pop()
274         else:
275                 return target_api
276
277 for e in enums:
278         if all(v=="ext" for v in e.supported_apis.values()) and e.value in core_enums_by_value:
279                 if core_enums_by_value[e.value] is None:
280                         candidates = [ce for ce in core_enums if ce.value==e.value]
281                         key_api = get_key_api(candidates)
282                         core_enums_by_value[e.value] = list(sorted(candidates, key=(lambda x: x.supported_apis.get(key_api, "ext"))))
283                 for ce in core_enums_by_value[e.value]:
284                         if ce.bitmask==e.bitmask:
285                                 e.aliases.append(ce.name)
286                                 break
287
288 # Create references from core things to their extension counterparts
289 for t in things.itervalues():
290         if t.extension:
291                 for a in t.aliases:
292                         alias = things.get(a)
293                         if alias:
294                                 if target_api in t.supported_apis:
295                                         alias.sources.insert(0, t)
296                                 else:
297                                         alias.sources.append(t)
298
299 # Find the things we want to include in this extension
300 def is_relevant(t):
301         # Unpromoted extension things are relevant
302         if t.extension and t.extension==target_ext and not t.aliases:
303                 return True
304
305         # Core things promoted from the extension are also relevant
306         for s in t.sources:
307                 if s.extension==target_ext or s.extension.name in secondary:
308                         return True
309
310         return False
311
312 funcs = [t for t in things.itervalues() if t.kind==Thing.FUNCTION and is_relevant(t)]
313 funcs.sort(key=(lambda f: f.name))
314 enums = filter(is_relevant, enums)
315 enums.sort(key=(lambda e: e.value))
316
317 # Some final preparations for creating the files
318 for t in itertools.chain(funcs, enums):
319         if target_api in t.supported_apis and t.supported_apis[target_api]!="ext":
320                 t.version = t.supported_apis[target_api]
321         if not core_version:
322                 core_version = t.version
323
324         # Things in backport extensions don't acquire an extension suffix
325         if t.extension and not t.name.endswith(ext_type) and target_api in t.supported_apis:
326                 backport_ext = t.extension
327
328 for f in funcs:
329         f.typedef = "FPtr_%s"%f.name
330
331 if target_api in target_ext.supported_apis:
332         source_ext = target_ext
333 else:
334         candidates = {}
335         for t in itertools.chain(funcs, enums):
336                 for s in t.sources:
337                         if target_api in s.supported_apis:
338                                 candidates[s.extension.name] = candidates.get(s.extension.name, 0)+1
339         if candidates:
340                 source_ext = extensions[max(candidates.iteritems(), key=(lambda x: x[1]))[0]]
341         else:
342                 source_ext = None
343
344 ### Output ###
345
346 out = file(out_base+".h", "w")
347 out.write("#ifndef MSP_GL_%s_\n"%target_ext.name.upper())
348 out.write("#define MSP_GL_%s_\n"%target_ext.name.upper())
349
350 out.write("""
351 #include <msp/gl/extension.h>
352 #include <msp/gl/gl.h>
353
354 namespace Msp {
355 namespace GL {
356
357 """)
358
359 if funcs or enums:
360         if funcs:
361                 for f in funcs:
362                         out.write("typedef %s (*%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
363                 out.write("\n")
364
365         if enums:
366                 api_prefix = "GL"
367                 if target_api=="gles2":
368                         api_prefix = "GL_ES"
369
370                 enums_by_category = {}
371                 for e in enums:
372                         cat = None
373                         if e.version:
374                                 cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version))
375                         elif e.extension:
376                                 cat = "GL_"+e.extension.name
377                         enums_by_category.setdefault(cat, []).append(e)
378
379                 for cat in sorted(enums_by_category.keys()):
380                         if cat:
381                                 out.write("#ifndef %s\n"%cat)
382                         for e in enums_by_category[cat]:
383                                 out.write("#define %s 0x%04X\n"%(e.name, e.value))
384                         if cat:
385                                 out.write("#endif\n")
386                         out.write("\n")
387
388         for f in funcs:
389                 out.write("extern %s %s;\n"%(f.typedef, f.name))
390
391 out.write("extern Extension %s;\n"%target_ext.name)
392
393 out.write("""
394 } // namespace GL
395 } // namespace Msp
396
397 #endif
398 """)
399
400 out = file(out_base+".cpp", "w")
401 out.write("#include \"%s.h\"\n"%target_ext.name.lower())
402
403 if funcs:
404         out.write("""
405 #ifdef __APPLE__
406 #define GET_PROC_ADDRESS(x) ::x
407 #else
408 #define GET_PROC_ADDRESS(x) get_proc_address(#x)
409 #endif
410 """)
411 out.write("""
412 namespace Msp {
413 namespace GL {
414
415 """)
416
417 for f in funcs:
418         out.write("%s %s = 0;\n"%(f.typedef, f.name))
419
420 out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.name.lower())
421 if core_version:
422         out.write("\tif(is_version_at_least(%d, %d)"%tuple(core_version))
423         if backport_ext:
424                 out.write(" || is_supported(\"GL_%s\")"%backport_ext.name)
425         out.write(")\n\t{\n")
426         for f in funcs:
427                 if f.version or target_api in f.supported_apis:
428                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, f.name))
429         out.write("\t\treturn Extension::CORE;\n")
430         out.write("\t}\n")
431 if source_ext and source_ext!=backport_ext:
432         out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(source_ext.name))
433         for f in funcs:
434                 s = f
435                 if f.sources:
436                         s = f.sources[0]
437                 if s.version or target_api in s.supported_apis:
438                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, s.name))
439         out.write("\t\treturn Extension::EXTENSION;\n")
440         out.write("\t}\n")
441 out.write("\treturn Extension::UNSUPPORTED;\n")
442 out.write("}\n")
443
444 out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
445
446 out.write("""
447 } // namespace GL
448 } // namespace Msp
449 """)