]> git.tdb.fi Git - libs/gl.git/blob - scripts/extgen.py
Be smarter when detecting the core version of extensions
[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 backport_ext = None
35 out_base = None
36 ignore_things = []
37 if target_ext.endswith(".glext"):
38         fn = target_ext
39         target_ext = None
40         core_version = None
41         secondary = []
42         for line in open(fn):
43                 parts = line.split()
44                 if parts[0]=="extension":
45                         target_ext = parts[1]
46                 elif parts[0]=="core_version":
47                         if parts[1]==target_api:
48                                 core_version = parts[2]
49                 elif parts[0]=="secondary":
50                         secondary.append(parts[1])
51                 elif parts[0]=="backport":
52                         backport_ext = parts[1]
53                 elif parts[0]=="ignore":
54                         ignore_things.append(parts[1])
55         if i+1<len(sys.argv):
56                 out_base = os.path.splitext(sys.argv[i+1])[0]
57 else:
58         secondary = sys.argv[i+1:]
59         core_version = None
60         if secondary and secondary[0][0].isdigit():
61                 core_version = secondary.pop(0)
62
63 ext_type = target_ext.split('_')[0]
64
65 if core_version:
66         core_version = map(int, core_version.split('.'))
67
68 if not out_base:
69         out_base = target_ext.lower()
70
71 ### XML file parsing ###
72
73 class Thing:
74         FUNCTION = 1
75         ENUM = 2
76
77         def __init__(self, name, kind):
78                 self.name = name
79                 self.kind = kind
80                 self.version = None
81                 self.extension = None
82                 self.supported_apis = {}
83                 self.aliases = []
84                 self.sources = []
85
86 class Function(Thing):
87         def __init__(self, name):
88                 Thing.__init__(self, name, Thing.FUNCTION)
89                 self.return_type = "void"
90                 self.params = []
91                 self.typedef = None
92
93 class Enum(Thing):
94         def __init__(self, name):
95                 Thing.__init__(self, name, Thing.ENUM)
96                 self.value = 0
97                 self.bitmask = (name.endswith("_BIT") or "_BIT_" in name)
98
99 class Extension:
100         def __init__(self, name):
101                 self.name = name
102                 self.supported_apis = []
103                 underscore = name.find('_')
104                 self.ext_type = name[0:underscore]
105                 self.base_name = name[underscore+1:]
106
107 extensions = {}
108 things = {}
109
110 def get_nested_elements(elem, path):
111         childElements = [c for c in elem.childNodes if c.nodeType==xml.dom.Node.ELEMENT_NODE]
112         if '/' in path:
113                 head, tail = path.split('/', 1)
114                 result = []
115                 for c in childElements:
116                         if c.tagName==head:
117                                 result += get_nested_elements(c, tail)
118                 return result
119         else:
120                 return [c for c in childElements if c.tagName==path]
121
122 def get_first_child(elem, tag):
123         for c in elem.childNodes:
124                 if c.nodeType==xml.dom.Node.ELEMENT_NODE and c.tagName==tag:
125                         return c
126         return None
127
128 def get_text_contents(node):
129         result = ""
130         for c in node.childNodes:
131                 if c.nodeType==xml.dom.Node.TEXT_NODE or c.nodeType==xml.dom.Node.CDATA_SECTION_NODE:
132                         result += c.data
133                 else:
134                         result += get_text_contents(c)
135         return result
136
137 def parse_command(cmd):
138         proto = get_first_child(cmd, "proto")
139         name = get_text_contents(get_first_child(proto, "name"))
140         func = things.get(name)
141         if not func:
142                 func = Function(name)
143                 things[name] = func
144
145         aliases = get_nested_elements(cmd, "alias")
146         func.aliases = [a.getAttribute("name") for a in aliases]
147
148         ptype = get_first_child(proto, "ptype")
149         if ptype:
150                 func.return_type = get_text_contents(ptype)
151         else:
152                 for c in proto.childNodes:
153                         if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip():
154                                 func.return_type = c.data.strip()
155                                 break
156
157         params = get_nested_elements(cmd, "param")
158         func.params = map(get_text_contents, params)
159
160 def parse_enum(en):
161         name = en.getAttribute("name")
162         enum = things.get(name)
163         if not enum:
164                 enum = Enum(name)
165                 things[name] = enum
166
167         enum.value = int(en.getAttribute("value"), 16)
168
169 def parse_feature(feat):
170         api = feat.getAttribute("api")
171         version = feat.getAttribute("number")
172         if version:
173                 version = map(int, version.split('.'))
174         else:
175                 version = None
176
177         requires = get_nested_elements(feat, "require")
178         for req in requires:
179                 commands = get_nested_elements(req, "command")
180                 enums = get_nested_elements(req, "enum")
181                 for t in itertools.chain(commands, enums):
182                         name = t.getAttribute("name")
183                         thing = things.get(name)
184                         if thing:
185                                 thing.supported_apis.setdefault(api, version)
186
187         if not api or api==target_api:
188                 removes = get_nested_elements(feat, "remove")
189                 for rem in removes:
190                         profile = rem.getAttribute("profile")
191                         commands = get_nested_elements(rem, "command")
192                         enums = get_nested_elements(rem, "enum")
193
194                         for t in itertools.chain(commands, enums):
195                                 name = t.getAttribute("name")
196                                 if profile!="core" and name in things:
197                                         del things[name]
198
199 def parse_extension(ext):
200         ext_name = ext.getAttribute("name")
201         if ext_name.startswith("GL_"):
202                 ext_name = ext_name[3:]
203
204         supported = ext.getAttribute("supported").split('|')
205         if target_api not in supported and ext_name!=target_ext:
206                 return
207
208         extension = extensions.get(ext_name)
209         if not extension:
210                 extension = Extension(ext_name)
211                 extensions[ext_name] = extension
212
213         extension.supported_apis = supported
214
215         requires = get_nested_elements(ext, "require")
216         for req in requires:
217                 api = req.getAttribute("api")
218                 if api:
219                         supported = [api]
220                 else:
221                         supported = extension.supported_apis
222
223                 commands = get_nested_elements(req, "command")
224                 enums = get_nested_elements(req, "enum")
225                 for t in itertools.chain(commands, enums):
226                         name = t.getAttribute("name")
227                         if name in ignore_things:
228                                 continue
229
230                         thing = things.get(name)
231                         if thing:
232                                 if thing.extension and extension.name!=target_ext:
233                                         if thing.extension.ext_type=="ARB" or thing.extension.name==target_ext:
234                                                 continue
235                                         if thing.extension.ext_type=="EXT" and extension.ext_type!="ARB":
236                                                 continue
237
238                                 thing.extension = extension
239                                 for a in supported:
240                                         thing.supported_apis.setdefault(a, "ext")
241
242 def parse_file(fn):
243         doc = xml.dom.minidom.parse(fn)
244         root = doc.documentElement
245
246         commands = get_nested_elements(root, "commands/command")
247         for cmd in commands:
248                 parse_command(cmd)
249
250         enums = get_nested_elements(root, "enums/enum")
251         for en in enums:
252                 parse_enum(en)
253
254         features = get_nested_elements(root, "feature")
255         for feat in features:
256                 parse_feature(feat)
257
258         extensions = get_nested_elements(root, "extensions/extension")
259         for ext in extensions:
260                 parse_extension(ext)
261
262 parse_file("gl.xml")
263 parse_file("gl.fixes.xml")
264
265 ### Additional processing ###
266
267 if target_ext in extensions:
268         target_ext = extensions[target_ext]
269 else:
270         print "Extension %s not found"%target_ext
271         sys.exit(1)
272
273 # Find aliases for enums
274 enums = [t for t in things.itervalues() if t.kind==Thing.ENUM]
275 core_enums = [e for e in enums if any(v!="ext" for v in e.supported_apis.itervalues())]
276 core_enums_by_value = dict((e.value, None) for e in core_enums)
277
278 def get_key_api(things):
279         common_apis = set(target_ext.supported_apis)
280         for t in things:
281                 common_apis.intersection_update(t.supported_apis.keys())
282         if common_apis:
283                 return common_apis.pop()
284         else:
285                 return target_api
286
287 for e in enums:
288         if all(v=="ext" for v in e.supported_apis.values()) and e.value in core_enums_by_value:
289                 if core_enums_by_value[e.value] is None:
290                         candidates = [ce for ce in core_enums if ce.value==e.value]
291                         key_api = get_key_api(candidates)
292                         core_enums_by_value[e.value] = list(sorted(candidates, key=(lambda x: x.supported_apis.get(key_api, "ext"))))
293                 for ce in core_enums_by_value[e.value]:
294                         if ce.bitmask==e.bitmask:
295                                 e.aliases.append(ce.name)
296                                 break
297
298 # Create references from core things to their extension counterparts
299 for t in things.itervalues():
300         if t.extension:
301                 for a in t.aliases:
302                         alias = things.get(a)
303                         if alias:
304                                 if target_api in t.supported_apis:
305                                         alias.sources.insert(0, t)
306                                 else:
307                                         alias.sources.append(t)
308
309 # Find the things we want to include in this extension
310 def is_relevant(t):
311         # Unpromoted extension things are relevant
312         if t.extension and t.extension==target_ext and not t.aliases:
313                 return True
314
315         # Core things promoted from the extension are also relevant
316         for s in t.sources:
317                 if s.extension==target_ext or s.extension.name in secondary:
318                         return True
319
320         return False
321
322 funcs = [t for t in things.itervalues() if t.kind==Thing.FUNCTION and is_relevant(t)]
323 funcs.sort(key=(lambda f: f.name))
324 enums = filter(is_relevant, enums)
325 enums.sort(key=(lambda e: e.value))
326
327 # Some final preparations for creating the files
328 core_version_candidates = {}
329 backport_ext_candidates = []
330 for t in itertools.chain(funcs, enums):
331         if target_api in t.supported_apis and t.supported_apis[target_api]!="ext":
332                 t.version = t.supported_apis[target_api]
333                 if t.version:
334                         ver_tuple = tuple(t.version)
335                         core_version_candidates[ver_tuple] = core_version_candidates.get(ver_tuple, 0)+1
336
337         # Things in backport extensions don't acquire an extension suffix
338         if t.extension and not t.name.endswith(ext_type) and target_api in t.supported_apis:
339                 if t.extension not in backport_ext_candidates:
340                         backport_ext_candidates.append(t.extension)
341
342 if not core_version and core_version_candidates:
343         core_version_candidates = list((v, k) for k, v in core_version_candidates.items())
344         if len(core_version_candidates)>1:
345                 core_version_candidates.sort(reverse=True)
346                 if core_version_candidates[1][0]+1>=core_version_candidates[0][0]:
347                         ver0 = core_version_candidates[0][1]
348                         ver1 = core_version_candidates[1][1]
349                         print "Warning: multiple likely core version candidates: %d.%d %d.%d"%(ver0[0], ver0[1], ver1[0], ver1[1])
350         core_version = core_version_candidates[0][1]
351
352 if backport_ext:
353         if backport_ext=="none":
354                 backport_ext = None
355         else:
356                 backport_ext = extensions[backport_ext]
357
358                 if backport_ext not in backport_ext_candidates:
359                         print "Warning: explicitly specified backport extension %s does not look like a backport extension"
360 elif backport_ext_candidates:
361         if len(backport_ext_candidates)>1:
362                 print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in backport_ext_candidates))
363
364         for e in backport_ext_candidates:
365                 if e.base_name==target_ext.base_name:
366                         backport_ext = e
367
368         if not backport_ext and len(backport_ext_candidates)==1:
369                 print "Warning: potential backport extension has mismatched name: %s"%backport_ext_candidates[0].name
370
371 for f in funcs:
372         f.typedef = "FPtr_%s"%f.name
373
374 if target_api in target_ext.supported_apis:
375         source_ext = target_ext
376 else:
377         candidates = {}
378         for t in itertools.chain(funcs, enums):
379                 for s in t.sources:
380                         if target_api in s.supported_apis:
381                                 candidates[s.extension.name] = candidates.get(s.extension.name, 0)+1
382         if candidates:
383                 source_ext = extensions[max(candidates.iteritems(), key=(lambda x: x[1]))[0]]
384         else:
385                 source_ext = None
386
387 if funcs or enums:
388         any_supported = False
389         all_supported = True
390         for t in itertools.chain(funcs, enums):
391                 if target_api in t.supported_apis:
392                         any_supported = True
393                 else:
394                         all_supported = False
395
396         if not any_supported:
397                 print "Warning: %s is not supported by the target API"%target_ext.name
398         elif not all_supported:
399                 print "Warning: %s is only partially supported by the target API"%target_ext.name
400                 unsupported = ""
401                 label = "Warning: Unsupported tokens: "
402                 for t in itertools.chain(funcs, enums):
403                         if target_api not in t.supported_apis:
404                                 if unsupported and len(label)+len(unsupported)+2+len(t.name)>78:
405                                         print label+unsupported
406                                         label = " "*len(label)
407                                         unsupported = ""
408                                 if unsupported:
409                                         unsupported += ", "
410                                 unsupported += t.name
411                 if unsupported:
412                         print label+unsupported
413
414 ### Output ###
415
416 out = file(out_base+".h", "w")
417 out.write("#ifndef MSP_GL_%s_\n"%target_ext.name.upper())
418 out.write("#define MSP_GL_%s_\n"%target_ext.name.upper())
419
420 out.write("""
421 #include <msp/gl/extension.h>
422 #include <msp/gl/gl.h>
423
424 namespace Msp {
425 namespace GL {
426
427 """)
428
429 if funcs or enums:
430         if funcs:
431                 for f in funcs:
432                         out.write("typedef %s (APIENTRY *%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
433                 out.write("\n")
434
435         if enums:
436                 api_prefix = "GL"
437                 if target_api=="gles2":
438                         api_prefix = "GL_ES"
439
440                 enums_by_category = {}
441                 for e in enums:
442                         cat = None
443                         if e.version:
444                                 cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version))
445                         elif e.extension:
446                                 cat = "GL_"+e.extension.name
447                         enums_by_category.setdefault(cat, []).append(e)
448
449                 for cat in sorted(enums_by_category.keys()):
450                         if cat:
451                                 out.write("#ifndef %s\n"%cat)
452                         for e in enums_by_category[cat]:
453                                 out.write("#define %s 0x%04X\n"%(e.name, e.value))
454                         if cat:
455                                 out.write("#endif\n")
456                         out.write("\n")
457
458         for f in funcs:
459                 out.write("extern %s %s;\n"%(f.typedef, f.name))
460
461 out.write("extern Extension %s;\n"%target_ext.name)
462
463 out.write("""
464 } // namespace GL
465 } // namespace Msp
466
467 #endif
468 """)
469
470 out = file(out_base+".cpp", "w")
471 out.write("#include \"%s.h\"\n"%target_ext.name.lower())
472
473 if funcs:
474         out.write("""
475 #ifdef __APPLE__
476 #define GET_PROC_ADDRESS(x) &::x
477 #else
478 #define GET_PROC_ADDRESS(x) get_proc_address(#x)
479 #endif
480
481 #ifdef WIN32
482 #define GET_PROC_ADDRESS_1_1(x) &::x
483 #else
484 #define GET_PROC_ADDRESS_1_1(x) GET_PROC_ADDRESS(x)
485 #endif
486 """)
487 out.write("""
488 namespace Msp {
489 namespace GL {
490
491 """)
492
493 for f in funcs:
494         out.write("%s %s = 0;\n"%(f.typedef, f.name))
495
496 out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.name.lower())
497 if core_version:
498         out.write("\tif(is_version_at_least(%d, %d)"%tuple(core_version))
499         if backport_ext:
500                 out.write(" || is_supported(\"GL_%s\")"%backport_ext.name)
501         out.write(")\n\t{\n")
502         for f in funcs:
503                 if target_api in f.supported_apis:
504                         gpa_suffix = ""
505                         if f.version is not None and f.version<=[1, 1]:
506                                 gpa_suffix = "_1_1"
507                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS%s(%s));\n"%(f.name, f.typedef, gpa_suffix, f.name))
508         out.write("\t\treturn Extension::CORE;\n")
509         out.write("\t}\n")
510 if source_ext and source_ext!=backport_ext:
511         out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(source_ext.name))
512         for f in funcs:
513                 if f.sources:
514                         src = None
515                         for s in f.sources:
516                                 if s.name.endswith(source_ext.ext_type):
517                                         src = s
518                                         break
519                         if not src:
520                                 src = f.sources[0]
521                 else:
522                         src = f
523
524                 if target_api in src.supported_apis:
525                         if not src.name.endswith(source_ext.ext_type):
526                                 print "Warning: %s does not match extension type %s"%(src.name, source_ext.ext_type)
527                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, src.name))
528         out.write("\t\treturn Extension::EXTENSION;\n")
529         out.write("\t}\n")
530 out.write("\treturn Extension::UNSUPPORTED;\n")
531 out.write("}\n")
532
533 out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
534
535 out.write("""
536 } // namespace GL
537 } // namespace Msp
538 """)