]> git.tdb.fi Git - libs/gl.git/blob - scripts/extgen.py
Implement 2D array textures
[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 backport_ext_candidates = []
329 for t in itertools.chain(funcs, enums):
330         if target_api in t.supported_apis and t.supported_apis[target_api]!="ext":
331                 t.version = t.supported_apis[target_api]
332         if not core_version:
333                 core_version = t.version
334
335         # Things in backport extensions don't acquire an extension suffix
336         if t.extension and not t.name.endswith(ext_type) and target_api in t.supported_apis:
337                 if t.extension not in backport_ext_candidates:
338                         backport_ext_candidates.append(t.extension)
339
340 if backport_ext:
341         if backport_ext=="none":
342                 backport_ext = None
343         else:
344                 backport_ext = extensions[backport_ext]
345
346                 if backport_ext not in backport_ext_candidates:
347                         print "Warning: explicitly specified backport extension %s does not look like a backport extension"
348 elif backport_ext_candidates:
349         if len(backport_ext_candidates)>1:
350                 print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in backport_ext_candidates))
351
352         for e in backport_ext_candidates:
353                 if e.base_name==target_ext.base_name:
354                         backport_ext = e
355
356         if not backport_ext and len(backport_ext_candidates)==1:
357                 print "Warning: potential backport extension has mismatched name: %s"%backport_ext_candidates[0].name
358
359 for f in funcs:
360         f.typedef = "FPtr_%s"%f.name
361
362 if target_api in target_ext.supported_apis:
363         source_ext = target_ext
364 else:
365         candidates = {}
366         for t in itertools.chain(funcs, enums):
367                 for s in t.sources:
368                         if target_api in s.supported_apis:
369                                 candidates[s.extension.name] = candidates.get(s.extension.name, 0)+1
370         if candidates:
371                 source_ext = extensions[max(candidates.iteritems(), key=(lambda x: x[1]))[0]]
372         else:
373                 source_ext = None
374
375 if funcs or enums:
376         any_supported = False
377         all_supported = True
378         for t in itertools.chain(funcs, enums):
379                 if target_api in t.supported_apis:
380                         any_supported = True
381                 else:
382                         all_supported = False
383
384         if not any_supported:
385                 print "Warning: %s is not supported by the target API"%target_ext.name
386         elif not all_supported:
387                 print "Warning: %s is only partially supported by the target API"%target_ext.name
388                 unsupported = ""
389                 label = "Warning: Unsupported tokens: "
390                 for t in itertools.chain(funcs, enums):
391                         if target_api not in t.supported_apis:
392                                 if unsupported and len(label)+len(unsupported)+2+len(t.name)>78:
393                                         print label+unsupported
394                                         label = " "*len(label)
395                                         unsupported = ""
396                                 if unsupported:
397                                         unsupported += ", "
398                                 unsupported += t.name
399                 if unsupported:
400                         print label+unsupported
401
402 ### Output ###
403
404 out = file(out_base+".h", "w")
405 out.write("#ifndef MSP_GL_%s_\n"%target_ext.name.upper())
406 out.write("#define MSP_GL_%s_\n"%target_ext.name.upper())
407
408 out.write("""
409 #include <msp/gl/extension.h>
410 #include <msp/gl/gl.h>
411
412 namespace Msp {
413 namespace GL {
414
415 """)
416
417 if funcs or enums:
418         if funcs:
419                 for f in funcs:
420                         out.write("typedef %s (APIENTRY *%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
421                 out.write("\n")
422
423         if enums:
424                 api_prefix = "GL"
425                 if target_api=="gles2":
426                         api_prefix = "GL_ES"
427
428                 enums_by_category = {}
429                 for e in enums:
430                         cat = None
431                         if e.version:
432                                 cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version))
433                         elif e.extension:
434                                 cat = "GL_"+e.extension.name
435                         enums_by_category.setdefault(cat, []).append(e)
436
437                 for cat in sorted(enums_by_category.keys()):
438                         if cat:
439                                 out.write("#ifndef %s\n"%cat)
440                         for e in enums_by_category[cat]:
441                                 out.write("#define %s 0x%04X\n"%(e.name, e.value))
442                         if cat:
443                                 out.write("#endif\n")
444                         out.write("\n")
445
446         for f in funcs:
447                 out.write("extern %s %s;\n"%(f.typedef, f.name))
448
449 out.write("extern Extension %s;\n"%target_ext.name)
450
451 out.write("""
452 } // namespace GL
453 } // namespace Msp
454
455 #endif
456 """)
457
458 out = file(out_base+".cpp", "w")
459 out.write("#include \"%s.h\"\n"%target_ext.name.lower())
460
461 if funcs:
462         out.write("""
463 #ifdef __APPLE__
464 #define GET_PROC_ADDRESS(x) &::x
465 #else
466 #define GET_PROC_ADDRESS(x) get_proc_address(#x)
467 #endif
468
469 #ifdef WIN32
470 #define GET_PROC_ADDRESS_1_1(x) &::x
471 #else
472 #define GET_PROC_ADDRESS_1_1(x) GET_PROC_ADDRESS(x)
473 #endif
474 """)
475 out.write("""
476 namespace Msp {
477 namespace GL {
478
479 """)
480
481 for f in funcs:
482         out.write("%s %s = 0;\n"%(f.typedef, f.name))
483
484 out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.name.lower())
485 if core_version:
486         out.write("\tif(is_version_at_least(%d, %d)"%tuple(core_version))
487         if backport_ext:
488                 out.write(" || is_supported(\"GL_%s\")"%backport_ext.name)
489         out.write(")\n\t{\n")
490         for f in funcs:
491                 if target_api in f.supported_apis:
492                         gpa_suffix = ""
493                         if f.version is not None and f.version<=[1, 1]:
494                                 gpa_suffix = "_1_1"
495                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS%s(%s));\n"%(f.name, f.typedef, gpa_suffix, f.name))
496         out.write("\t\treturn Extension::CORE;\n")
497         out.write("\t}\n")
498 if source_ext and source_ext!=backport_ext:
499         out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(source_ext.name))
500         for f in funcs:
501                 if f.sources:
502                         src = None
503                         for s in f.sources:
504                                 if s.name.endswith(source_ext.ext_type):
505                                         src = s
506                                         break
507                         if not src:
508                                 src = f.sources[0]
509                 else:
510                         src = f
511
512                 if target_api in src.supported_apis:
513                         if not src.name.endswith(source_ext.ext_type):
514                                 print "Warning: %s does not match extension type %s"%(src.name, source_ext.ext_type)
515                         out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, src.name))
516         out.write("\t\treturn Extension::EXTENSION;\n")
517         out.write("\t}\n")
518 out.write("\treturn Extension::UNSUPPORTED;\n")
519 out.write("}\n")
520
521 out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
522
523 out.write("""
524 } // namespace GL
525 } // namespace Msp
526 """)