]> git.tdb.fi Git - libs/gl.git/commitdiff
Refactor the structure of extgen.py
authorMikko Rasa <tdb@tdb.fi>
Fri, 17 Nov 2017 21:54:43 +0000 (23:54 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sun, 19 Nov 2017 17:34:35 +0000 (19:34 +0200)
Functionality should be more or less preserved at this stage, modulo new
or fixed bugs.  Some extensions are detected differently on OpenGL ES but
those were largely incorrect anyway and will be fixed soon.

scripts/extgen.py

index c7c06b2c07c115a11bf6986f8f52d2761ef87999..f5df83f21588ab0cbb71f94d8a94a8d05023af7a 100755 (executable)
@@ -5,116 +5,109 @@ import os
 import xml.dom
 import xml.dom.minidom
 import itertools
+import re
+
+class Version:
+       def __init__(self, *args):
+               if len(args)==0:
+                       self.major = 0
+                       self.minor = 0
+               elif len(args)==2:
+                       self.major = args[0]
+                       self.minor = args[1]
+               else:
+                       raise TypeError, "__init__() takes zero or two arguments (%d given)"%len(args)
 
-### Command line processing ###
+       def __str__(self):
+               return "%d.%d"%(self.major, self.minor)
 
-if len(sys.argv)<2:
-       print """Usage:
-  extgen.py [api] <extension> [<core_version>] [<secondary> ...]
-  extgen.py [api] <extfile> [<outfile>]
+       def __repr__(self):
+               return "Version(%d, %d)"%(self.major, self.minor)
 
-Reads gl.xml and generates files to use <extension>.  Any promoted functions
-are exposed with their promoted names.  If <secondary> extensions are given,
-any promoted functions from those are pulled in as well.  <core_version> can
-be given to override the version where <extension> was promoted to core.
+       def as_define(self):
+               return "VERSION_%d_%d"%(self.major, self.minor)
 
-In the second form, the parameters are read from <extfile>.  If <outfile> is
-absent, the extension's lowercased name is used.  Anything after the last dot
-in <outfile> is removed and replaced with cpp and h."""
-       sys.exit(0)
+       def __lt__(self, other):
+               if other is None:
+                       return False
 
-target_api = "gl"
+               if self.major!=other.major:
+                       return self.major<other.major
+               return self.minor<other.minor
 
-i = 1
-if sys.argv[i].startswith("gl"):
-       target_api = sys.argv[i]
-       i += 1
+       def __gt__(self, other):
+               if other is None:
+                       return True
+
+               if self.major!=other.major:
+                       return self.major>other.major
+               return self.minor>other.minor
 
-target_ext = sys.argv[i]
-backport_ext = None
-deprecated_version = None
-out_base = None
-ignore_things = []
-if target_ext.endswith(".glext"):
-       fn = target_ext
-       target_ext = None
-       core_version = None
-       secondary = []
-       for line in open(fn):
-               parts = line.split()
-               if parts[0]=="extension":
-                       target_ext = parts[1]
-               elif parts[0]=="core_version":
-                       if parts[1]==target_api:
-                               core_version = parts[2]
-               elif parts[0]=="deprecated":
-                       if parts[1]==target_api:
-                               deprecated_version = parts[2]
-               elif parts[0]=="secondary":
-                       secondary.append(parts[1])
-               elif parts[0]=="backport":
-                       backport_ext = parts[1]
-               elif parts[0]=="ignore":
-                       ignore_things.append(parts[1])
-       if i+1<len(sys.argv):
-               out_base = os.path.splitext(sys.argv[i+1])[0]
-else:
-       secondary = sys.argv[i+1:]
-       core_version = None
-       if secondary and secondary[0][0].isdigit():
-               core_version = secondary.pop(0)
-
-ext_type = target_ext.split('_')[0]
-
-if core_version:
-       core_version = map(int, core_version.split('.'))
-
-if deprecated_version:
-       deprecated_version = map(int, deprecated_version.split('.'))
-
-if not out_base:
-       out_base = target_ext.lower()
-
-### XML file parsing ###
 
 class Thing:
        FUNCTION = 1
        ENUM = 2
 
+       class ApiSupport:
+               def __init__(self):
+                       self.core_version = None
+                       self.deprecated_version = None
+                       self.extensions = []
+                       self.sources = []
+
        def __init__(self, name, kind):
                self.name = name
                self.kind = kind
-               self.version = None
-               self.deprecated_version = None
-               self.extension = None
-               self.supported_apis = {}
-               self.deprecated = {}
                self.aliases = []
-               self.sources = []
+               self.api_support = {}
+
+       def get_or_create_api_support(self, api):
+               supp = self.api_support.get(api)
+               if not supp:
+                       supp = Thing.ApiSupport()
+                       self.api_support[api] = supp
+               return supp
+
 
 class Function(Thing):
        def __init__(self, name):
                Thing.__init__(self, name, Thing.FUNCTION)
                self.return_type = "void"
                self.params = []
-               self.typedef = None
+
+
+r_bitmask = re.compile("_BIT[0-9]*(_|$)")
 
 class Enum(Thing):
        def __init__(self, name):
                Thing.__init__(self, name, Thing.ENUM)
                self.value = 0
-               self.bitmask = (name.endswith("_BIT") or "_BIT_" in name)
+               self.bitmask = bool(r_bitmask.search(self.name))
+
 
 class Extension:
-       def __init__(self, name):
+       def __init__(self, name, api):
                self.name = name
-               self.supported_apis = []
                underscore = name.find('_')
                self.ext_type = name[0:underscore]
                self.base_name = name[underscore+1:]
+               self.api = api
+               self.things = {}
+               self.preference = 0
+               if self.ext_type=="EXT":
+                       self.preference = 1
+               elif self.ext_type=="ARB" or self.ext_type=="OES":
+                       self.preference = 2
+               self.backport = False
+
+
+class Api:
+       def __init__(self, name):
+               self.name = name
+               self.latest_version = None
+               self.core_things = {}
+               self.extensions = {}
 
-extensions = {}
-things = {}
 
 def get_nested_elements(elem, path):
        childElements = [c for c in elem.childNodes if c.nodeType==xml.dom.Node.ELEMENT_NODE]
@@ -143,304 +136,311 @@ def get_text_contents(node):
                        result += get_text_contents(c)
        return result
 
-def parse_command(cmd):
-       proto = get_first_child(cmd, "proto")
-       name = get_text_contents(get_first_child(proto, "name"))
-       func = things.get(name)
-       if not func:
-               func = Function(name)
-               things[name] = func
+def get_or_create(map, name, type, *args):
+       obj = map.get(name)
+       if not obj:
+               obj = type(name, *args)
+               map[name] = obj
+       return obj
 
-       aliases = get_nested_elements(cmd, "alias")
-       func.aliases = [a.getAttribute("name") for a in aliases]
 
-       ptype = get_first_child(proto, "ptype")
-       if ptype:
-               func.return_type = get_text_contents(ptype)
-       else:
-               for c in proto.childNodes:
-                       if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip():
-                               func.return_type = c.data.strip()
-                               break
-
-       params = get_nested_elements(cmd, "param")
-       func.params = map(get_text_contents, params)
-
-def parse_enum(en):
-       name = en.getAttribute("name")
-       enum = things.get(name)
-       if not enum:
-               enum = Enum(name)
-               things[name] = enum
-
-       enum.value = int(en.getAttribute("value"), 16)
-
-def parse_feature(feat):
-       api = feat.getAttribute("api")
-       version = feat.getAttribute("number")
-       if version:
-               version = map(int, version.split('.'))
-       else:
-               version = None
-
-       requires = get_nested_elements(feat, "require")
-       for req in requires:
-               commands = get_nested_elements(req, "command")
-               enums = get_nested_elements(req, "enum")
-               for t in itertools.chain(commands, enums):
-                       name = t.getAttribute("name")
-                       thing = things.get(name)
-                       if thing:
-                               thing.supported_apis.setdefault(api, version)
-
-       if not api or api==target_api:
-               removes = get_nested_elements(feat, "remove")
-               for rem in removes:
-                       profile = rem.getAttribute("profile")
-                       commands = get_nested_elements(rem, "command")
-                       enums = get_nested_elements(rem, "enum")
+class GlXmlParser:
+       def __init__(self, host_api_name, target_ext_name):
+               self.host_api_name = host_api_name
+               self.target_ext_name = target_ext_name
+               self.apis = {}
+               self.things = {}
 
-                       for t in itertools.chain(commands, enums):
-                               name = t.getAttribute("name")
-                               if name in things:
-                                       if profile!="core":
-                                               del things[name]
-                                       else:
-                                               things[name].deprecated.setdefault(api, version)
-
-def parse_extension(ext):
-       ext_name = ext.getAttribute("name")
-       if ext_name.startswith("GL_"):
-               ext_name = ext_name[3:]
-
-       supported = ext.getAttribute("supported").split('|')
-       if target_api not in supported and ext_name!=target_ext:
-               return
-
-       extension = extensions.get(ext_name)
-       if not extension:
-               extension = Extension(ext_name)
-               extensions[ext_name] = extension
+       def parse_command(self, cmd):
+               proto = get_first_child(cmd, "proto")
+               name = get_text_contents(get_first_child(proto, "name"))
+               func = get_or_create(self.things, name, Function)
 
-       extension.supported_apis = supported
+               aliases = get_nested_elements(cmd, "alias")
+               func.aliases = [a.getAttribute("name") for a in aliases]
 
-       requires = get_nested_elements(ext, "require")
-       for req in requires:
-               api = req.getAttribute("api")
-               if api:
-                       supported = [api]
+               ptype = get_first_child(proto, "ptype")
+               if ptype:
+                       func.return_type = get_text_contents(ptype)
                else:
-                       supported = extension.supported_apis
+                       for c in proto.childNodes:
+                               if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip():
+                                       func.return_type = c.data.strip()
+                                       break
 
-               commands = get_nested_elements(req, "command")
-               enums = get_nested_elements(req, "enum")
-               for t in itertools.chain(commands, enums):
-                       name = t.getAttribute("name")
-                       if name in ignore_things:
-                               continue
+               params = get_nested_elements(cmd, "param")
+               func.params = map(get_text_contents, params)
 
-                       thing = things.get(name)
-                       if thing:
-                               if thing.extension and extension.name!=target_ext:
-                                       if thing.extension.ext_type=="ARB" or thing.extension.name==target_ext:
-                                               continue
-                                       if thing.extension.ext_type=="EXT" and extension.ext_type!="ARB":
-                                               continue
+       def parse_enum(self, en):
+               name = en.getAttribute("name")
+               enum = get_or_create(self.things, name, Enum)
 
-                               thing.extension = extension
-                               for a in supported:
-                                       thing.supported_apis.setdefault(a, "ext")
+               enum.value = int(en.getAttribute("value"), 16)
 
-def parse_file(fn):
-       doc = xml.dom.minidom.parse(fn)
-       root = doc.documentElement
+       def parse_feature(self, feat):
+               api_name = feat.getAttribute("api")
+               if not api_name:
+                       api_name = self.host_api_name
+               api = get_or_create(self.apis, api_name, Api)
 
-       commands = get_nested_elements(root, "commands/command")
-       for cmd in commands:
-               parse_command(cmd)
+               version = feat.getAttribute("number")
+               if version:
+                       version = Version(*map(int, version.split('.')))
+               else:
+                       version = None
 
-       enums = get_nested_elements(root, "enums/enum")
-       for en in enums:
-               parse_enum(en)
+               requires = get_nested_elements(feat, "require")
+               for req in requires:
+                       commands = get_nested_elements(req, "command")
+                       enums = get_nested_elements(req, "enum")
+                       for t in itertools.chain(commands, enums):
+                               name = t.getAttribute("name")
+                               thing = self.things.get(name)
+                               if thing:
+                                       supp = thing.get_or_create_api_support(api.name)
+                                       if not supp.core_version or version<supp.core_version:
+                                               supp.core_version = version
+                                       api.core_things[thing.name] = thing
 
-       features = get_nested_elements(root, "feature")
-       for feat in features:
-               parse_feature(feat)
+               removes = get_nested_elements(feat, "remove")
+               for rem in removes:
+                       profile = rem.getAttribute("profile")
+                       commands = get_nested_elements(rem, "command")
+                       enums = get_nested_elements(rem, "enum")
 
-       extensions = get_nested_elements(root, "extensions/extension")
-       for ext in extensions:
-               parse_extension(ext)
+                       for t in itertools.chain(commands, enums):
+                               name = t.getAttribute("name")
+                               thing = self.things.get(name)
+                               if thing:
+                                       if profile!="core":
+                                               if thing.name in api.core_things:
+                                                       del api.core_things[thing.name]
+                                               for s in thing.api_support.itervalues():
+                                                       for e in s.extensions:
+                                                               del e.things[thing.name]
+                                       else:
+                                               supp = thing.get_or_create_api_support(api.name)
+                                               supp.deprecated_version = version
+
+       def parse_extension(self, ext):
+               ext_things_by_api = {}
+               requires = get_nested_elements(ext, "require")
+               for req in requires:
+                       api = req.getAttribute("api")
+                       ext_things = ext_things_by_api.setdefault(api, [])
+
+                       commands = get_nested_elements(req, "command")
+                       enums = get_nested_elements(req, "enum")
+                       for t in itertools.chain(commands, enums):
+                               name = t.getAttribute("name")
+                               thing = self.things.get(name)
+                               if thing:
+                                       ext_things.append(thing)
+
+               ext_name = ext.getAttribute("name")
+               if ext_name.startswith("GL_"):
+                       ext_name = ext_name[3:]
+
+               common_things = ext_things_by_api.get("", [])
+               supported = ext.getAttribute("supported").split('|')
+               for s in supported:
+                       api = self.apis.get(s)
+                       if not api:
+                               continue
 
-parse_file("gl.xml")
-parse_file("gl.fixes.xml")
+                       ext = get_or_create(api.extensions, ext_name, Extension, api)
+                       api_things = ext_things_by_api.get(s, [])
+                       for t in itertools.chain(common_things, api_things):
+                               ext.things[t.name] = t
+                               t.get_or_create_api_support(api.name).extensions.append(ext)
+
+       def parse_file(self, fn):
+               doc = xml.dom.minidom.parse(fn)
+               root = doc.documentElement
+
+               commands = get_nested_elements(root, "commands/command")
+               for cmd in commands:
+                       self.parse_command(cmd)
+
+               enums = get_nested_elements(root, "enums/enum")
+               for en in enums:
+                       self.parse_enum(en)
+
+               features = get_nested_elements(root, "feature")
+               for feat in features:
+                       self.parse_feature(feat)
+
+               extensions = get_nested_elements(root, "extensions/extension")
+               for ext in extensions:
+                       self.parse_extension(ext)
+
+       def check_backport_extensions(self, api):
+               for e in api.extensions.itervalues():
+                       e.backport = True
+                       for t in e.things.itervalues():
+                               if t.name.endswith(e.ext_type):
+                                       e.backport = False
+                                       break
 
-### Additional processing ###
+       def resolve_enum_aliases(self, api):
+               core_enums = filter((lambda t: t.kind==Thing.ENUM), api.core_things.itervalues())
+               core_enums_by_value = dict((e.value, None) for e in core_enums)
+
+               for e in api.extensions.itervalues():
+                       ext_enums = filter((lambda t: t.kind==Thing.ENUM), e.things.itervalues())
+                       enum_suffix = "_"+e.ext_type
+                       for n in ext_enums:
+                               if n.api_support[api.name].core_version:
+                                       continue
+
+                               name = n.name
+                               if name.endswith(enum_suffix):
+                                       name = name[:-len(enum_suffix)]
+                               ce = api.core_things.get(name)
+                               if not ce and n.value in core_enums_by_value:
+                                       if core_enums_by_value[n.value] is None:
+                                               core_enums_by_value[n.value] = filter((lambda e: e.value==n.value), core_enums)
+                                       for c in core_enums_by_value[n.value]:
+                                               if c.bitmask==n.bitmask:
+                                                       ce = c
+                                                       break
+                               if ce and ce.value==n.value and ce.name not in n.aliases:
+                                       n.aliases.append(ce.name)
+
+       def resolve_sources(self, api):
+               for e in api.extensions.itervalues():
+                       for t in e.things.itervalues():
+                               for a in t.aliases:
+                                       # There are a few cases where a vendor function is aliased to
+                                       # an EXT or ARB function but those are rare and not relevant for
+                                       # our use
+                                       alias = api.core_things.get(a)
+                                       if alias:
+                                               sources = alias.api_support[api.name].sources
+                                               if t not in sources:
+                                                       sources.append(t)
+
+       def sort_extensions(self):
+               for a in self.apis.itervalues():
+                       e = a.extensions.get(self.target_ext_name)
+                       if e:
+                               e.preference = 3
+               for t in self.things.itervalues():
+                       for s in t.api_support.itervalues():
+                               s.extensions.sort(key=(lambda e: e.preference), reverse=True)
+
+       def finalize(self):
+               for a in self.apis.itervalues():
+                       self.check_backport_extensions(a)
+                       self.resolve_enum_aliases(a)
+                       self.resolve_sources(a)
+               self.sort_extensions()
+
+
+def detect_core_version(host_api, things):
+       candidates = {}
+       for t in things:
+               supp = t.api_support.get(host_api.name)
+               if supp and supp.core_version:
+                       candidates[supp.core_version] = candidates.get(supp.core_version, 0)+1
 
-if target_ext in extensions:
-       target_ext = extensions[target_ext]
-else:
-       print "Extension %s not found"%target_ext
-       sys.exit(1)
+       if candidates:
+               candidates = list((v, k) for k, v in candidates.items())
+               if len(candidates)>1:
+                       candidates.sort(reverse=True)
+                       if candidates[1][0]+1>=candidates[0][0]:
+                               print "Warning: multiple likely core version candidates: %s %s"%(candidates[0][1], candidates[1][1])
+               return candidates[0][1]
+
+def detect_deprecated_version(host_api, things):
+       min_version = None
+       for t in things:
+               supp = t.api_support.get(host_api.name)
+               if supp and supp.deprecated_version:
+                       if min_version is None:
+                               min_version = supp.deprecated_version
+                       else:
+                               min_version = min(min_version, supp.deprecated_version)
+               else:
+                       return None
 
-# Find aliases for enums
-enums = [t for t in things.itervalues() if t.kind==Thing.ENUM]
-core_enums = [e for e in enums if any(v!="ext" for v in e.supported_apis.itervalues())]
-core_enums_by_value = dict((e.value, None) for e in core_enums)
+       return min_version
 
-def get_key_api(things):
-       common_apis = set(target_ext.supported_apis)
+def detect_backport_extension(host_api, target_ext, things):
+       candidates = []
        for t in things:
-               common_apis.intersection_update(t.supported_apis.keys())
-       if common_apis:
-               return common_apis.pop()
-       else:
-               return target_api
-
-for e in enums:
-       if all(v=="ext" for v in e.supported_apis.values()) and e.value in core_enums_by_value:
-               if core_enums_by_value[e.value] is None:
-                       candidates = [ce for ce in core_enums if ce.value==e.value]
-                       key_api = get_key_api(candidates)
-                       core_enums_by_value[e.value] = list(sorted(candidates, key=(lambda x: x.supported_apis.get(key_api, "ext"))))
-               for ce in core_enums_by_value[e.value]:
-                       if ce.bitmask==e.bitmask:
-                               e.aliases.append(ce.name)
-                               break
-
-# Create references from core things to their extension counterparts
-for t in things.itervalues():
-       if t.extension:
-               for a in t.aliases:
-                       alias = things.get(a)
-                       if alias:
-                               if target_api in t.supported_apis:
-                                       alias.sources.insert(0, t)
-                               else:
-                                       alias.sources.append(t)
+               supp = t.api_support.get(host_api.name)
+               if supp and supp.core_version:
+                       for e in supp.extensions:
+                               if e.backport and e not in candidates:
+                                       candidates.append(e)
 
-# Find the things we want to include in this extension
-def is_relevant(t):
-       # Unpromoted extension things are relevant
-       if t.extension and t.extension==target_ext and not t.aliases:
-               return True
+       if len(candidates)>1:
+               print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in candidates))
 
-       # Core things promoted from the extension are also relevant
-       for s in t.sources:
-               if s.extension==target_ext or s.extension.name in secondary:
-                       return True
+       for e in candidates:
+               if e.base_name==target_ext.base_name:
+                       return e
 
-       return False
-
-funcs = [t for t in things.itervalues() if t.kind==Thing.FUNCTION and is_relevant(t)]
-funcs.sort(key=(lambda f: f.name))
-enums = filter(is_relevant, enums)
-enums.sort(key=(lambda e: e.value))
-
-# Some final preparations for creating the files
-core_version_candidates = {}
-min_deprecated_version = [999, 0]
-backport_ext_candidates = []
-for t in itertools.chain(funcs, enums):
-       if target_api in t.supported_apis and t.supported_apis[target_api]!="ext":
-               t.version = t.supported_apis[target_api]
-               if t.version:
-                       ver_tuple = tuple(t.version)
-                       core_version_candidates[ver_tuple] = core_version_candidates.get(ver_tuple, 0)+1
-
-       if target_api in t.deprecated:
-               t.deprecated_version = t.deprecated[target_api]
-               min_deprecated_version = min(min_deprecated_version, t.deprecated_version)
-       else:
-               min_deprecated_version = None
-
-       # Things in backport extensions don't acquire an extension suffix
-       if t.extension and not t.name.endswith(ext_type) and target_api in t.supported_apis:
-               if t.extension not in backport_ext_candidates:
-                       backport_ext_candidates.append(t.extension)
-
-if not core_version and core_version_candidates:
-       core_version_candidates = list((v, k) for k, v in core_version_candidates.items())
-       if len(core_version_candidates)>1:
-               core_version_candidates.sort(reverse=True)
-               if core_version_candidates[1][0]+1>=core_version_candidates[0][0]:
-                       ver0 = core_version_candidates[0][1]
-                       ver1 = core_version_candidates[1][1]
-                       print "Warning: multiple likely core version candidates: %d.%d %d.%d"%(ver0[0], ver0[1], ver1[0], ver1[1])
-       core_version = core_version_candidates[0][1]
-
-if not deprecated_version:
-       deprecated_version = min_deprecated_version
-
-if backport_ext:
-       if backport_ext=="none":
-               backport_ext = None
-       else:
-               bpe_name = backport_ext
-               backport_ext = extensions.get(backport_ext)
+       if len(candidates)==1:
+               print "Warning: potential backport extension has mismatched name: %s"%candidates[0].name
 
-               if backport_ext not in backport_ext_candidates:
-                       print "Warning: explicitly specified backport extension %s does not look like a backport extension"%bpe_name
-elif backport_ext_candidates:
-       if len(backport_ext_candidates)>1:
-               print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in backport_ext_candidates))
+def collect_extensions(thing, api, exts):
+       supp = thing.api_support.get(api)
+       if not supp:
+               return
 
-       for e in backport_ext_candidates:
-               if e.base_name==target_ext.base_name:
-                       backport_ext = e
+       for e in supp.extensions:
+               if not e.backport and e not in exts:
+                       exts.append(e)
 
-       if not backport_ext and len(backport_ext_candidates)==1:
-               print "Warning: potential backport extension has mismatched name: %s"%backport_ext_candidates[0].name
+       for s in supp.sources:
+               collect_extensions(s, api, exts)
 
-for f in funcs:
-       f.typedef = "FPtr_%s"%f.name
+def detect_source_extension(host_api, target_ext, things):
+       if target_ext.name in host_api.extensions:
+               return target_ext
 
-if target_api in target_ext.supported_apis:
-       source_ext = target_ext
-else:
-       candidates = {}
-       for t in itertools.chain(funcs, enums):
-               for s in t.sources:
-                       if target_api in s.supported_apis:
-                               candidates[s.extension.name] = candidates.get(s.extension.name, 0)+1
-       if candidates:
-               source_ext = extensions[max(candidates.iteritems(), key=(lambda x: x[1]))[0]]
-       else:
-               source_ext = None
-
-if funcs or enums:
-       any_supported = False
-       all_supported = True
-       for t in itertools.chain(funcs, enums):
-               if target_api in t.supported_apis:
-                       any_supported = True
-               else:
-                       all_supported = False
-
-       if not any_supported:
-               print "Warning: %s is not supported by the target API"%target_ext.name
-       elif not all_supported:
-               print "Warning: %s is only partially supported by the target API"%target_ext.name
-               unsupported = ""
-               label = "Warning: Unsupported tokens: "
-               for t in itertools.chain(funcs, enums):
-                       if target_api not in t.supported_apis:
-                               if unsupported and len(label)+len(unsupported)+2+len(t.name)>78:
-                                       print label+unsupported
-                                       label = " "*len(label)
-                                       unsupported = ""
-                               if unsupported:
-                                       unsupported += ", "
-                               unsupported += t.name
-               if unsupported:
-                       print label+unsupported
-
-### Output ###
-
-out = file(out_base+".h", "w")
-out.write("#ifndef MSP_GL_%s_\n"%target_ext.name.upper())
-out.write("#define MSP_GL_%s_\n"%target_ext.name.upper())
-
-out.write("""
+       things_by_ext = {}
+       for t in things:
+               exts = []
+               collect_extensions(t, host_api.name, exts)
+               for e in exts:
+                       things_by_ext.setdefault(e, []).append(t)
+
+       largest_ext = None
+       largest_count = 0
+       for e, t in things_by_ext.iteritems():
+               count = len(t)
+               if count>largest_count:
+                       largest_ext = e
+                       largest_count = count
+
+       return largest_ext
+
+
+class SourceGenerator:
+       def __init__(self, host_api, target_ext, things):
+               self.host_api = host_api
+               self.api_prefix = "GL"
+               if self.host_api.name=="gles2":
+                       self.api_prefix = "GL_ES"
+               self.target_ext = target_ext
+               self.funcs = filter((lambda t: t.kind==Thing.FUNCTION), things)
+               self.funcs.sort(key=(lambda f: f.name))
+               self.func_typedefs = dict((f.name, "FPtr_"+f.name) for f in self.funcs)
+               self.enums = filter((lambda t: t.kind==Thing.ENUM), things)
+               self.enums.sort(key=(lambda e: e.value))
+               self.core_version = detect_core_version(host_api, things)
+               self.deprecated_version = detect_deprecated_version(host_api, things)
+               self.backport_ext = detect_backport_extension(host_api, target_ext, things);
+               self.source_ext = detect_source_extension(host_api, target_ext, things)
+
+       def write_header_intro(self, out):
+               out.write("#ifndef MSP_GL_%s_\n"%self.target_ext.name.upper())
+               out.write("#define MSP_GL_%s_\n"%self.target_ext.name.upper())
+
+               out.write("""
 #include <msp/gl/extension.h>
 #include <msp/gl/gl.h>
 
@@ -449,24 +449,16 @@ namespace GL {
 
 """)
 
-if funcs or enums:
-       if funcs:
-               for f in funcs:
-                       out.write("typedef %s (APIENTRY *%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
-               out.write("\n")
-
-       if enums:
-               api_prefix = "GL"
-               if target_api=="gles2":
-                       api_prefix = "GL_ES"
-
+       def write_enum_definitions(self, out):
                enums_by_category = {}
-               for e in enums:
+               for e in self.enums:
                        cat = None
-                       if e.version:
-                               cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version))
-                       elif e.extension:
-                               cat = "GL_"+e.extension.name
+                       supp = e.api_support.get(self.host_api.name)
+                       if supp:
+                               if supp.core_version:
+                                       cat = "%s_%s"%(self.api_prefix, supp.core_version.as_define())
+                               elif supp.extensions:
+                                       cat = "GL_"+supp.extensions[0].name
                        enums_by_category.setdefault(cat, []).append(e)
 
                for cat in sorted(enums_by_category.keys()):
@@ -478,23 +470,25 @@ if funcs or enums:
                                out.write("#endif\n")
                        out.write("\n")
 
-       for f in funcs:
-               out.write("extern %s %s;\n"%(f.typedef, f.name))
-
-out.write("extern Extension %s;\n"%target_ext.name)
+       def write_function_pointer_declarations(self, out):
+               for f in self.funcs:
+                       typedef = self.func_typedefs[f.name]
+                       out.write("typedef %s (APIENTRY *%s)(%s);\n"%(f.return_type, typedef, ", ".join(f.params)))
+                       out.write("extern %s %s;\n"%(typedef, f.name))
+                       out.write("\n")
 
-out.write("""
+       def write_header_outro(self, out):
+               out.write("""
 } // namespace GL
 } // namespace Msp
 
 #endif
 """)
 
-out = file(out_base+".cpp", "w")
-out.write("#include \"%s.h\"\n"%target_ext.name.lower())
-
-if funcs:
-       out.write("""
+       def write_source_intro(self, out):
+               out.write("#include \"%s.h\"\n"%self.target_ext.name.lower())
+               if self.funcs:
+                       out.write("""
 #ifdef __APPLE__
 #define GET_PROC_ADDRESS(x) &::x
 #else
@@ -507,63 +501,200 @@ if funcs:
 #define GET_PROC_ADDRESS_1_1(x) GET_PROC_ADDRESS(x)
 #endif
 """)
-out.write("""
+               out.write("""
 namespace Msp {
 namespace GL {
 
 """)
 
-for f in funcs:
-       out.write("%s %s = 0;\n"%(f.typedef, f.name))
-
-out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.name.lower())
-if core_version:
-       out.write("\tif(is_disabled(\"GL_%s\"))\n\t\treturn Extension::UNSUPPORTED;\n"%target_ext.name)
-       out.write("#if !defined(__APPLE__) || defined(GL_VERSION_%d_%d)\n"%tuple(core_version))
-       out.write("\tif(")
-       if backport_ext:
-               out.write("is_supported(\"GL_%s\") || "%backport_ext.name)
-       out.write("is_supported(Version(%d, %d)"%tuple(core_version))
-       if deprecated_version:
-               out.write(", Version(%d, %d)"%tuple(deprecated_version))
-       out.write("))\n\t{\n")
-       for f in funcs:
-               if target_api in f.supported_apis:
-                       gpa_suffix = ""
-                       if f.version is not None and f.version<=[1, 1]:
-                               gpa_suffix = "_1_1"
-                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS%s(%s));\n"%(f.name, f.typedef, gpa_suffix, f.name))
-       out.write("\t\treturn Extension::CORE;\n")
-       out.write("\t}\n")
-       out.write("#endif\n")
-if source_ext and source_ext!=backport_ext:
-       out.write("#if !defined(__APPLE__) || defined(GL_%s)\n"%target_ext.name)
-       out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(source_ext.name))
-       for f in funcs:
-               if f.sources:
-                       src = None
-                       for s in f.sources:
-                               if s.name.endswith(source_ext.ext_type):
-                                       src = s
-                                       break
-                       if not src:
-                               src = f.sources[0]
-               else:
-                       src = f
-
-               if target_api in src.supported_apis:
-                       if not src.name.endswith(source_ext.ext_type):
-                               print "Warning: %s does not match extension type %s"%(src.name, source_ext.ext_type)
-                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, src.name))
-       out.write("\t\treturn Extension::EXTENSION;\n")
-       out.write("\t}\n")
-       out.write("#endif\n")
-out.write("\treturn Extension::UNSUPPORTED;\n")
-out.write("}\n")
-
-out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
-
-out.write("""
+       def write_function_pointer_definitions(self, out):
+               for f in self.funcs:
+                       out.write("%s %s = 0;\n"%(self.func_typedefs[f.name], f.name))
+
+       def write_init_function(self, out):
+               out.write("\nExtension::SupportLevel init_%s()\n{\n"%self.target_ext.name.lower())
+               if self.core_version:
+                       out.write("\tif(is_disabled(\"GL_%s\"))\n\t\treturn Extension::UNSUPPORTED;\n"%self.target_ext.name)
+                       out.write("#if !defined(__APPLE__) || defined(%s_%s)\n"%(self.api_prefix, self.core_version.as_define()))
+                       out.write("\tif(")
+                       if self.backport_ext:
+                               out.write("is_supported(\"GL_%s\") || "%self.backport_ext.name)
+                       out.write("is_supported(%r"%self.core_version)
+                       if self.deprecated_version:
+                               out.write(", %r"%self.deprecated_version)
+                       out.write("))\n\t{\n")
+                       for f in self.funcs:
+                               supp = f.api_support.get(self.host_api.name)
+                               if supp:
+                                       gpa_suffix = ""
+                                       if supp.core_version is not None and supp.core_version<=Version(1, 1):
+                                               gpa_suffix = "_1_1"
+                                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS%s(%s));\n"%(f.name, self.func_typedefs[f.name], gpa_suffix, f.name))
+                       out.write("\t\treturn Extension::CORE;\n")
+                       out.write("\t}\n")
+                       out.write("#endif\n")
+               if self.source_ext and self.source_ext!=self.backport_ext:
+                       out.write("#if !defined(__APPLE__) || defined(GL_%s)\n"%self.target_ext.name)
+                       out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(self.source_ext.name))
+                       for f in self.funcs:
+                               supp = f.api_support.get(self.host_api.name)
+                               if supp and supp.sources:
+                                       src = None
+                                       for s in supp.sources:
+                                               if s.name.endswith(self.source_ext.ext_type):
+                                                       src = s
+                                                       break
+                                       if not src:
+                                               src = supp.sources[0]
+                               else:
+                                       src = f
+
+                               if self.host_api.name in src.api_support:
+                                       if not src.name.endswith(self.source_ext.ext_type):
+                                               print "Warning: %s does not match extension type %s"%(src.name, self.source_ext.ext_type)
+                                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, self.func_typedefs[f.name], src.name))
+                       out.write("\t\treturn Extension::EXTENSION;\n")
+                       out.write("\t}\n")
+                       out.write("#endif\n")
+               out.write("\treturn Extension::UNSUPPORTED;\n")
+               out.write("}\n")
+
+       def write_source_outro(self, out):
+               out.write("""
 } // namespace GL
 } // namespace Msp
 """)
+
+       def write_header(self, fn):
+               out = file(fn, "w")
+               self.write_header_intro(out)
+               self.write_enum_definitions(out)
+               self.write_function_pointer_declarations(out)
+               out.write("extern Extension %s;\n"%self.target_ext.name)
+               self.write_header_outro(out)
+
+       def write_source(self, fn):
+               out = file(fn, "w")
+               self.write_source_intro(out)
+               self.write_function_pointer_definitions(out)
+               self.write_init_function(out)
+               ext_name = self.target_ext.name
+               out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(ext_name, ext_name, ext_name.lower()))
+               self.write_source_outro(out)
+
+
+class ExtensionParser:
+       def __init__(self, host_api):
+               self.host_api = host_api
+               self.target_ext = None
+               self.core_version = None
+               self.deprecated_version = None
+               self.secondary_exts = []
+               self.backport_ext = None
+               self.ignore_things = []
+
+       def parse(self, fn):
+               for line in open(fn):
+                       line = line.strip()
+                       if line.startswith("#"):
+                               continue
+
+                       parts = line.split()
+                       keyword = parts[0]
+
+                       if keyword=="extension":
+                               self.target_ext = parts[1]
+                       elif keyword=="core_version":
+                               if parts[1]==self.host_api:
+                                       self.core_version = Version(*map(int, parts[2].split('.')))
+                       elif keyword=="deprecated":
+                               if parts[1]==self.host_api:
+                                       self.deprecated_version = Version(*map(int, parts[2].split('.')))
+                       elif keyword=="secondary":
+                               self.secondary_exts.append(parts[1])
+                       elif keyword=="backport":
+                               self.backport_ext = parts[1]
+                       elif keyword=="ignore":
+                               self.ignore_things.append(parts[1])
+
+
+def get_extension(api_map, ext_name):
+       main_api = api_map["gl"]
+       ext = main_api.extensions.get(ext_name)
+       if ext:
+               return ext
+
+       for a in api_map.itervalues():
+               ext = a.extensions.get(ext_name)
+               if ext:
+                       return ext
+
+def collect_things(host_api, target_ext, secondary, ignore):
+       ext_things = [t for n, t in target_ext.things.iteritems() if n not in ignore]
+       core_things = target_ext.api.core_things
+
+       things = []
+       for t in ext_things:
+               found_in_core = False
+               for a in t.aliases:
+                       if a in core_things:
+                               things.append(core_things[a])
+                               found_in_core = True
+               if not found_in_core:
+                       things.append(t)
+
+       for s in secondary:
+               for t in s.things.itervalues():
+                       for a in t.aliases:
+                               if a in core_things and core_things[a] not in things:
+                                       things.append(core_things[a])
+
+       return things
+
+def main():
+       if len(sys.argv)<2:
+               print """Usage:
+  extgen.py [api] <extfile> [<outfile>]
+
+Reads gl.xml and generates C++ source files to use an OpenGL extension
+described in <extfile>.  If <outfile> is absent, the extension's lowercased
+name is used.  Anything after the last dot in <outfile> is removed and
+replaced with cpp and h."""
+               sys.exit(0)
+
+       host_api_name = "gl"
+
+       i = 1
+       if sys.argv[i].startswith("gl"):
+               host_api_name = sys.argv[i]
+               i += 1
+
+       ext_parser = ExtensionParser(host_api_name)
+       ext_parser.parse(sys.argv[i])
+       i += 1
+
+       if i<len(sys.argv):
+               out_base = os.path.splitext(sys.argv[i])[0]
+       else:
+               out_base = ext_parser.target_ext.lower()
+
+       xml_parser = GlXmlParser(host_api_name, ext_parser.target_ext)
+       xml_parser.parse_file("gl.xml")
+       xml_parser.parse_file("gl.fixes.xml")
+       xml_parser.finalize()
+
+       host_api = xml_parser.apis[host_api_name]
+       target_ext = get_extension(xml_parser.apis, ext_parser.target_ext)
+       secondary_exts = [get_extension(xml_parser.apis, s) for s in ext_parser.secondary_exts]
+       things = collect_things(host_api, target_ext, secondary_exts, ext_parser.ignore_things)
+
+       generator = SourceGenerator(host_api, target_ext, things)
+       if ext_parser.core_version:
+               generator.core_version = ext_parser.core_version
+       if ext_parser.deprecated_version:
+               generator.deprecated_version = ext_parser.deprecated_version
+       generator.write_header(out_base+".h")
+       generator.write_source(out_base+".cpp")
+
+if __name__=="__main__":
+       main()