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