]> git.tdb.fi Git - libs/gl.git/blobdiff - scripts/extgen.py
Make extensions compatible with OpenGL ES
[libs/gl.git] / scripts / extgen.py
index c7c06b2c07c115a11bf6986f8f52d2761ef87999..ff40ec60eadd1f515336f78d4d82eec2e0cfdfff 100755 (executable)
@@ -5,116 +5,115 @@ 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
+
+       def __eq__(self, other):
+               if other is None:
+                       return False
+
+               return (self.major==other.major and 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 +142,404 @@ 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:
+class GlXmlParser:
+       def __init__(self):
+               self.apis = {}
+               self.things = {}
+
+       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)
+
+               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(self, en):
+               name = en.getAttribute("name")
+               enum = get_or_create(self.things, name, Enum)
+
+               enum.value = int(en.getAttribute("value"), 16)
+
+               alias = en.getAttribute("alias")
+               if alias:
+                       enum.aliases.append(alias)
+
+       def parse_feature(self, feat):
+               api_name = feat.getAttribute("api")
+               api = get_or_create(self.apis, api_name, Api)
+
+               version = feat.getAttribute("number")
+               if version:
+                       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 = 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
+
                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")
 
                        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
+                               thing = self.things.get(name)
+                               if thing:
+                                       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
 
-       extension = extensions.get(ext_name)
-       if not extension:
-               extension = Extension(ext_name)
-               extensions[ext_name] = extension
+                       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)
 
-       extension.supported_apis = supported
+       def parse_file(self, fn):
+               doc = xml.dom.minidom.parse(fn)
+               root = doc.documentElement
 
-       requires = get_nested_elements(ext, "require")
-       for req in requires:
-               api = req.getAttribute("api")
-               if api:
-                       supported = [api]
-               else:
-                       supported = extension.supported_apis
+               commands = get_nested_elements(root, "commands/command")
+               for cmd in commands:
+                       self.parse_command(cmd)
 
-               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
+               enums = get_nested_elements(root, "enums/enum")
+               for en in enums:
+                       self.parse_enum(en)
 
-                       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
+               features = get_nested_elements(root, "feature")
+               for feat in features:
+                       self.parse_feature(feat)
 
-                               thing.extension = extension
-                               for a in supported:
-                                       thing.supported_apis.setdefault(a, "ext")
+               extensions = get_nested_elements(root, "extensions/extension")
+               for ext in extensions:
+                       self.parse_extension(ext)
 
-def parse_file(fn):
-       doc = xml.dom.minidom.parse(fn)
-       root = doc.documentElement
+       def check_backport_extensions(self, api):
+               for e in api.extensions.itervalues():
+                       if e.ext_type!="ARB":
+                               continue
 
-       commands = get_nested_elements(root, "commands/command")
-       for cmd in commands:
-               parse_command(cmd)
+                       e.backport = True
+                       for t in e.things.itervalues():
+                               if t.name.endswith(e.ext_type):
+                                       e.backport = False
+                                       break
 
-       enums = get_nested_elements(root, "enums/enum")
-       for en in enums:
-               parse_enum(en)
+       def resolve_enum_aliases(self, api):
+               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 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 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, debug=None):
+       max_version = Version(1, 0)
+       max_count = 0
+       lower_count = 0
+       missing = []
+       for t in things:
+               supp = t.api_support.get(host_api.name)
+               if supp and supp.core_version:
+                       if supp.core_version>max_version:
+                               max_version = supp.core_version
+                               lower_count += max_count
+                               max_count = 1
+                       elif supp.core_version==max_version:
+                               max_count += 1
+                       else:
+                               lower_count += 1
+               else:
+                       missing.append(t)
 
-       features = get_nested_elements(root, "feature")
-       for feat in features:
-               parse_feature(feat)
+       if lower_count>max_count or (missing and len(missing)*2<lower_count+max_count):
+               print "Warning: Inconsistent core version %s"%max_version
 
-       extensions = get_nested_elements(root, "extensions/extension")
-       for ext in extensions:
-               parse_extension(ext)
+       if missing:
+               if debug:
+                       print "---"
+                       print "%d things missing from core:"%len(missing)
+                       for t in missing:
+                               print "  "+t.name
+               return None
 
-parse_file("gl.xml")
-parse_file("gl.fixes.xml")
+       return max_version
 
-### Additional processing ###
+def detect_deprecated_version(host_api, things, debug):
+       min_version = None
+       deprecated = []
+       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)
+                       deprecated.append(t)
+
+       if min_version and len(deprecated)*2<len(things):
+               print "Warning: Inconsistent deprecation version %s"%min_version
+               if debug:
+                       print "---"
+                       print "%d things are deprecated:"%len(deprecated)
+                       for t in deprecated:
+                               print "  "+t.name
+
+       return min_version
+
+def detect_backport_extension(host_api, things):
+       candidates = []
+       for t in things:
+               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)
+
+       total_count = len(things)
+       best_ext = None
+       best_count = 0
+       for e in candidates:
+               things_in_ext = filter((lambda t: t.name in e.things), things)
+               count = len(things_in_ext)
+               if count==total_count:
+                       return e
+               elif count>best_count:
+                       best_ext = e
+                       best_count = count
+
+       if best_count*2>=total_count:
+               print "Warning: Inconsistent backport extension %s"%best_ext.name
+
+def collect_extensions(thing, api, exts):
+       supp = thing.api_support.get(api)
+       if not supp:
+               return
 
-if target_ext in extensions:
-       target_ext = extensions[target_ext]
-else:
-       print "Extension %s not found"%target_ext
-       sys.exit(1)
+       for e in supp.extensions:
+               if not e.backport and e.ext_type!="MSP" and e not in exts:
+                       exts.append(e)
 
-# 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)
+       for s in supp.sources:
+               collect_extensions(s, api, exts)
 
-def get_key_api(things):
-       common_apis = set(target_ext.supported_apis)
+def detect_source_extension(host_api, things, debug=False):
+       things_by_ext = {}
        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)
+               exts = []
+               collect_extensions(t, host_api.name, exts)
+               for e in exts:
+                       things_by_ext.setdefault(e, []).append(t)
+
+       if debug:
+               print "---"
+               print "Looking for %d things in %d extensions"%(len(things), len(things_by_ext))
+
+       extensions = []
+       keep_exts = 0
+       base_version = None
+       recheck_base_version = True
+       missing = set(things)
+       while 1:
+               if recheck_base_version:
+                       max_version = Version(1, 0)
+                       for t in missing:
+                               supp = t.api_support.get(host_api.name)
+                               if supp and supp.core_version and max_version:
+                                       max_version = max(max_version, supp.core_version)
                                else:
-                                       alias.sources.append(t)
-
-# 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
-
-       # 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
-
-       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 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))
-
-       for e in backport_ext_candidates:
-               if e.base_name==target_ext.base_name:
-                       backport_ext = 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 f in funcs:
-       f.typedef = "FPtr_%s"%f.name
-
-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]]
+                                       max_version = None
+
+                       if max_version:
+                               if not base_version or max_version<base_version:
+                                       base_version = max_version
+                                       keep_exts = len(extensions)
+                       elif not base_version:
+                               keep_exts = len(extensions)
+
+                       recheck_base_version = False
+
+               if not missing or not things_by_ext:
+                       break
+
+               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
+                       elif count==largest_count and e.preference>largest_ext.preference:
+                               largest_ext = e
+
+               if debug:
+                       print "Found %d things in %s"%(largest_count, largest_ext.name)
+
+               extensions.append(largest_ext)
+               for t in things_by_ext[largest_ext]:
+                       missing.remove(t)
+
+                       supp = t.api_support.get(host_api.name)
+                       if supp and supp.core_version==base_version:
+                               recheck_base_version = True
+
+               del things_by_ext[largest_ext]
+               for e in things_by_ext.keys():
+                       unseen = filter((lambda t: t in missing), things_by_ext[e])
+                       if unseen:
+                               things_by_ext[e] = unseen
+                       else:
+                               del things_by_ext[e]
+
+       if not missing:
+               return None, extensions
+       elif base_version:
+               if debug:
+                       print "Found remaining things in version %s"%base_version
+                       if keep_exts<len(extensions):
+                               print "Discarding %d extensions that do not improve base version"%(len(extensions)-keep_exts)
+               del extensions[keep_exts:]
+               return base_version, extensions
        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("""
+               if debug:
+                       print "%d things still missing:"%len(missing)
+                       for t in missing:
+                               print "  "+t.name
+               return None, None
+
+
+class SourceGenerator:
+       def __init__(self, host_api, ext_name, things, optional_things, debug=False):
+               self.host_api = host_api
+               self.api_prefix = "GL"
+               if self.host_api.name=="gles2":
+                       self.api_prefix = "GL_ES"
+               self.ext_name = ext_name
+               all_things = things+optional_things
+               self.funcs = filter((lambda t: t.kind==Thing.FUNCTION), all_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), all_things)
+               self.enums.sort(key=(lambda e: e.value))
+               self.core_version = detect_core_version(host_api, things, debug)
+               self.deprecated_version = detect_deprecated_version(host_api, things, debug)
+               self.backport_ext = detect_backport_extension(host_api, things);
+               b, e = detect_source_extension(host_api, things, debug)
+               self.base_version = b
+               self.source_exts = e
+
+               if not self.core_version and not self.backport_ext and not self.source_exts:
+                       print "Warning: Not supportable on host API"
+
+       def dump_info(self):
+               print "--- Extension information ---"
+               print "Extension %s"%self.ext_name
+               print "Core %s"%self.core_version
+               print "Deprecated %s"%self.deprecated_version
+               if self.backport_ext:
+                       print "Backport %s"%self.backport_ext.name
+               if self.source_exts:
+                       names = [e.name for e in self.source_exts]
+                       if self.base_version:
+                               names.insert(0, "Version %s"%self.base_version)
+                       print "Sources %s"%", ".join(names)
+
+       def write_header_intro(self, out):
+               out.write("#ifndef MSP_GL_%s_\n"%self.ext_name.upper())
+               out.write("#define MSP_GL_%s_\n"%self.ext_name.upper())
+
+               out.write("""
 #include <msp/gl/extension.h>
 #include <msp/gl/gl.h>
 
@@ -449,24 +548,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 +569,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.ext_name.lower())
+               if self.funcs:
+                       out.write("""
 #ifdef __APPLE__
 #define GET_PROC_ADDRESS(x) &::x
 #else
@@ -507,63 +600,267 @@ 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]
+       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.ext_name.lower())
+               if self.core_version:
+                       out.write("\tif(is_disabled(\"GL_%s\"))\n\t\treturn Extension::UNSUPPORTED;\n"%self.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_exts:
+                       out.write("#if !defined(__APPLE__) || defined(GL_%s)\n"%self.ext_name)
+                       out.write("\tif(")
+                       if self.base_version:
+                               out.write("is_supported(%r) && "%self.base_version)
+                       out.write("%s)\n\t{\n"%" && ".join("is_supported(\"GL_%s\")"%s.name for s in self.source_exts))
+                       for f in self.funcs:
+                               supp = f.api_support.get(self.host_api.name)
+                               src = None
+                               for e in self.source_exts:
+                                       if f in e.things:
+                                               src = f
+                                       elif supp:
+                                               for s in supp.sources:
+                                                       if s.name in e.things:
+                                                               src = s
+                                                               break
+                                       if src:
+                                               break
+                               if not src and supp and supp.core_version and self.base_version>=supp.core_version:
+                                       sec = f
+
+                               if src:
+                                       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.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)
+               out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(self.ext_name, self.ext_name, self.ext_name.lower()))
+               self.write_source_outro(out)
+
+
+def dump_api_support(supp, api, indent):
+       if supp.core_version:
+               print indent+"core in version "+str(supp.core_version)
+       if supp.deprecated_version:
+               print indent+"deprecated in version "+str(supp.deprecated_version)
+       for e in supp.extensions:
+               print indent+"extension %s (preference %d)"%(e.name, e.preference)
+       for r in supp.sources:
+               print indent+"source "+r.name
+               dump_thing_info(r, api, indent+"  ")
+
+def dump_thing_info(thing, api, indent):
+       for a in thing.aliases:
+               print indent+"alias "+a
+       if api:
+               supp = thing.api_support.get(api)
+               dump_api_support(supp, api, indent)
+       else:
+               for a, s in thing.api_support.iteritems():
+                       print indent+"api "+a
+                       dump_api_support(s, a, indent+"  ")
+
+
+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.backport_ext = None
+               self.source_exts = []
+               self.ignore_things = []
+               self.optional_things = []
+
+       def parse(self, fn):
+               for line in open(fn):
+                       line = line.strip()
+                       if not line or line.startswith("#"):
+                               continue
+
+                       parts = line.split()
+                       api = None
+                       keyword = parts[0]
+                       if ":" in keyword:
+                               api, keyword = keyword.split(":")
+
+                       if api is not None and api!=self.host_api:
+                               continue
+
+                       if keyword=="extension":
+                               self.target_ext = parts[1]
+                       elif keyword=="core_version":
+                               self.core_version = Version(*map(int, parts[1].split('.')))
+                       elif keyword=="deprecated":
+                               self.deprecated_version = Version(*map(int, parts[1].split('.')))
+                       elif keyword=="backport":
+                               self.backport_ext = parts[1]
+                       elif keyword=="source":
+                               self.source_exts.append(parts[1])
+                       elif keyword=="ignore":
+                               self.ignore_things.append(parts[1])
+                       elif keyword=="optional":
+                               self.optional_things.append(parts[1])
+                       else:
+                               print "Unknown keyword "+keyword
+                               return False
+
+               return True
+
+
+def get_extension(api_map, ext_name):
+       if "." in ext_name:
+               ext_api_name, ext_name = ext_name.split(".")
+       else:
+               ext_api_name = "gl"
+
+       return api_map[ext_api_name].extensions[ext_name]
+
+def resolve_things(api, things):
+       rthings = []
+       for t in things:
+               ct = filter(None, map(api.core_things.get, t.aliases))
+               if ct:
+                       rthings += ct
                else:
-                       src = f
+                       rthings.append(t)
 
-               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")
+       return rthings
 
-out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
+def collect_extension_things(host_api, target_ext, ignore):
+       ext_things = [t for n, t in target_ext.things.iteritems() if n not in ignore]
+       return resolve_things(target_ext.api, ext_things)
 
-out.write("""
-} // namespace GL
-} // namespace Msp
-""")
+def collect_optional_things(target_ext, names):
+       things = []
+       for t in names:
+               if t in target_ext.things:
+                       things.append(target_ext.things[t])
+               else:
+                       things.append(target_ext.api.core_things[t])
+       return resolve_things(target_ext.api, 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(1)
+
+       i = 1
+
+       debug = False
+       if sys.argv[i]=="-g":
+               debug = True
+               i += 1
+
+       host_api_name = "gl"
+       if sys.argv[i].startswith("gl"):
+               host_api_name = sys.argv[i]
+               i += 1
+
+       ext_parser = ExtensionParser(host_api_name)
+       if not ext_parser.parse(sys.argv[i]):
+               sys.exit(1)
+       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()
+       xml_parser.parse_file("gl.xml")
+       xml_parser.parse_file("gl.fixes.xml")
+       xml_parser.parse_file("gl.msp.xml")
+       xml_parser.finalize()
+
+       host_api = xml_parser.apis[host_api_name]
+       target_ext = get_extension(xml_parser.apis, ext_parser.target_ext)
+       things = collect_extension_things(host_api, target_ext, ext_parser.ignore_things+ext_parser.optional_things)
+       optional_things = collect_optional_things(target_ext, ext_parser.optional_things)
+
+       if debug:
+               print "--- Things included in this extension ---"
+               all_things = things+optional_things
+               all_things.sort(key=(lambda t: t.name))
+               for t in all_things:
+                       print t.name
+                       if t in optional_things:
+                               print "  optional"
+                       dump_thing_info(t, None, "  ")
+
+       generator = SourceGenerator(host_api, target_ext.name, things, optional_things, debug)
+       if ext_parser.core_version:
+               generator.core_version = ext_parser.core_version
+       if ext_parser.deprecated_version:
+               generator.deprecated_version = ext_parser.deprecated_version
+       if ext_parser.backport_ext:
+               if ext_parser.backport_ext=="none":
+                       generator.backport_ext = None
+               else:
+                       generator.backport_ext = get_extension(xml_parser.apis, ext_parser.backport_ext)
+       if ext_parser.source_exts:
+               generator.base_version = None
+               if len(ext_parser.source_exts)==1 and ext_parser.source_exts[0]=="none":
+                       generator.source_exts = []
+               else:
+                       generator.source_exts = map((lambda e: get_extension(xml_parser.apis, e)), ext_parser.source_exts)
+       if debug:
+               generator.dump_info()
+       generator.write_header(out_base+".h")
+       generator.write_source(out_base+".cpp")
+
+if __name__=="__main__":
+       main()