]> git.tdb.fi Git - libs/gl.git/blobdiff - scripts/extgen.py
Make extensions compatible with OpenGL ES
[libs/gl.git] / scripts / extgen.py
index 7ab8abc3fc83ad8efebe031dc859866d98d6dda0..ff40ec60eadd1f515336f78d4d82eec2e0cfdfff 100755 (executable)
@@ -5,91 +5,133 @@ 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)
+
+       def __str__(self):
+               return "%d.%d"%(self.major, self.minor)
+
+       def __repr__(self):
+               return "Version(%d, %d)"%(self.major, self.minor)
+
+       def as_define(self):
+               return "VERSION_%d_%d"%(self.major, self.minor)
+
+       def __lt__(self, other):
+               if other is None:
+                       return False
+
+               if self.major!=other.major:
+                       return self.major<other.major
+               return self.minor<other.minor
+
+       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)
 
-### Command line processing ###
-
-if len(sys.argv)<2:
-       print """Usage:
-  extgen.py <extension> [<core_version>] [<secondary> ...]
-  extgen.py <extfile> [<outfile>]
-
-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.
-
-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)
-
-ext = sys.argv[1]
-out_base = None
-if ext.endswith(".glext"):
-       fn = ext
-       ext = None
-       ver = None
-       secondary = []
-       for line in open(fn):
-               parts = line.split()
-               if parts[0]=="extension":
-                       ext = parts[1]
-               elif parts[0]=="core_version":
-                       ver = parts[1]
-               elif parts[0]=="secondary":
-                       secondary.append(parts[1])
-       if len(sys.argv)>=3:
-               out_base = os.path.splitext(sys.argv[2])[0]
-else:
-       secondary = sys.argv[2:]
-       ver = None
-       if secondary and secondary[0][0].isdigit():
-               ver = secondary.pop(0)
-
-exttype = ext.split('_')[0]
-backport_ext = None
-
-if not out_base:
-       out_base = 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.extension = None
                self.aliases = []
-               self.source = None
+               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
-               self.vectorequiv = 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 = bool(r_bitmask.search(self.name))
+
+
+class Extension:
+       def __init__(self, name, api):
+               self.name = name
+               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 = {}
 
-things = {}
 
 def get_nested_elements(elem, path):
+       childElements = [c for c in elem.childNodes if c.nodeType==xml.dom.Node.ELEMENT_NODE]
        if '/' in path:
                head, tail = path.split('/', 1)
                result = []
-               for e in elem.getElementsByTagName(head):
-                       result += get_nested_elements(e, tail)
+               for c in childElements:
+                       if c.tagName==head:
+                               result += get_nested_elements(c, tail)
                return result
        else:
-               return elem.getElementsByTagName(path)
+               return [c for c in childElements if c.tagName==path]
+
+def get_first_child(elem, tag):
+       for c in elem.childNodes:
+               if c.nodeType==xml.dom.Node.ELEMENT_NODE and c.tagName==tag:
+                       return c
+       return None
 
 def get_text_contents(node):
        result = ""
@@ -100,153 +142,404 @@ def get_text_contents(node):
                        result += get_text_contents(c)
        return result
 
-def parse_file(fn):
-       doc = xml.dom.minidom.parse(fn)
-       root = doc.documentElement
-       commands = get_nested_elements(root, "commands/command")
-       for cmd in commands:
-               proto = cmd.getElementsByTagName("proto")[0]
-               name = get_text_contents(proto.getElementsByTagName("name")[0])
-               func = things.get(name)
-               if not func:
-                       func = Function(name)
-                       things[name] = func
-
-               aliases = cmd.getElementsByTagName("alias")
-               for a in aliases:
-                       func.aliases.append(a.getAttribute("name"))
-
-               vec = cmd.getElementsByTagName("vecequiv")
-               if vec:
-                       func.vectorequiv = vec[0].getAttribute("name")
-
-               ptype = proto.getElementsByTagName("ptype")
+def get_or_create(map, name, type, *args):
+       obj = map.get(name)
+       if not obj:
+               obj = type(name, *args)
+               map[name] = obj
+       return obj
+
+
+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[0])
+                       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 = cmd.getElementsByTagName("param")
-               for p in params:
-                       func.params.append(get_text_contents(p))
+               params = get_nested_elements(cmd, "param")
+               func.params = map(get_text_contents, params)
 
-       enums = get_nested_elements(root, "enums/enum")
-       for en in enums:
+       def parse_enum(self, en):
                name = en.getAttribute("name")
-               enum = things.get(name)
-               if not enum:
-                       enum = Enum(name)
-                       things[name] = enum
+               enum = get_or_create(self.things, name, Enum)
 
                enum.value = int(en.getAttribute("value"), 16)
 
-       features = root.getElementsByTagName("feature")
-       for feat in features:
-               api = feat.getAttribute("api")
-               if api=="gl":
-                       version = feat.getAttribute("number")
+               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
 
-                       commands = get_nested_elements(feat, "require/command")
-                       enums = get_nested_elements(feat, "require/enum")
+               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)
+                               thing = self.things.get(name)
                                if thing:
-                                       thing.version = version
-
-                       if feat.getAttribute("name")=="MSPGL_REMOVE":
-                               commands = get_nested_elements(feat, "remove/command")
-                               enums = get_nested_elements(feat, "require/enum")
-                               for t in itertools.chain(commands, enums):
-                                       name = t.getAttribute("name")
-                                       if name in things:
-                                               del things[name]
-
-       extensions = get_nested_elements(root, "extensions/extension")
-       for ext in extensions:
-               ext_name = ext.getAttribute("name")
-               if ext_name.startswith("GL_"):
-                       ext_name = ext_name[3:]
-               supported = ext.getAttribute("supported").split('|')
-               if "gl" in supported:
-                       commands = get_nested_elements(ext, "require/command")
-                       enums = get_nested_elements(ext, "require/enum")
+                                       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:
+                       commands = get_nested_elements(rem, "command")
+                       enums = get_nested_elements(rem, "enum")
+
                        for t in itertools.chain(commands, enums):
                                name = t.getAttribute("name")
-                               thing = things.get(name)
+                               thing = self.things.get(name)
                                if thing:
-                                       thing.extension = ext_name
-
-parse_file("gl.xml")
-parse_file("gl.fixes.xml")
-
-### Additional processing ###
-
-# Find aliases for enums
-enums = [t for t in things.itervalues() if t.kind==Thing.ENUM]
-core_enums_by_value = dict((e.value, e) for e in enums if e.version)
-
-for e in enums:
-       if e.kind==t.ENUM and e.extension:
-               ce = core_enums_by_value.get(e.value)
-               if ce and ce!=e:
-                       e.aliases.append(ce.name)
-
-# Create references from core things to their extension counterparts
-for t in things.itervalues():
-       if t.extension==ext or t.extension in secondary:
-               for a in t.aliases:
-                       alias = things.get(a)
-                       if alias:
-                               alias.source = t
-
-def is_relevant(t):
-       # Unpromoted extension things are relevant
-       if t.extension==ext and not t.aliases:
-               return True
-
-       # Core things promoted from the extension are also relevant
-       if t.source:
-               e = t.source
-               if e.extension==ext or e.extension 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 = [e for e in enums if is_relevant(e)]
-enums.sort(key=(lambda e: e.value))
+                                       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)
 
-for t in itertools.chain(funcs, enums):
-       if not ver:
-               ver = t.version
+               ext_name = ext.getAttribute("name")
+               if ext_name.startswith("GL_"):
+                       ext_name = ext_name[3:]
 
-       # Things in backport extensions don't acquire an extension suffix
-       if t.extension and not t.name.endswith(exttype):
-               backport_ext = t.extension
+               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
+
+                       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():
+                       if e.ext_type!="ARB":
+                               continue
+
+                       e.backport = True
+                       for t in e.things.itervalues():
+                               if t.name.endswith(e.ext_type):
+                                       e.backport = False
+                                       break
 
-for f in funcs:
-       if f.source:
-               # Typedefs for early core functions are not available in all
-               # implementations
-               f.typedef = "PFN%sPROC"%f.source.name.upper()
+       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)
+
+       if lower_count>max_count or (missing and len(missing)*2<lower_count+max_count):
+               print "Warning: Inconsistent core version %s"%max_version
+
+       if missing:
+               if debug:
+                       print "---"
+                       print "%d things missing from core:"%len(missing)
+                       for t in missing:
+                               print "  "+t.name
+               return None
+
+       return max_version
+
+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
+
+       for e in supp.extensions:
+               if not e.backport and e.ext_type!="MSP" and e not in exts:
+                       exts.append(e)
+
+       for s in supp.sources:
+               collect_extensions(s, api, exts)
+
+def detect_source_extension(host_api, things, debug=False):
+       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)
+
+       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:
+                                       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:
-               f.typedef = "PFN%sPROC"%f.name.upper()
-
-if ver:
-       ver = map(int, ver.split('.'))
-
-### Output ###
-
-out = file(out_base+".h", "w")
-out.write("#ifndef MSP_GL_%s_\n"%ext.upper())
-out.write("#define MSP_GL_%s_\n"%ext.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>
 
@@ -255,78 +548,319 @@ namespace GL {
 
 """)
 
-if funcs or enums:
-       if funcs:
-               out.write("#if defined(__APPLE__) || !defined(GL_%s)\n"%ext)
-               for f in funcs:
-                       out.write("typedef %s (*%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
-               out.write("#endif\n\n")
-
-       if enums:
-               out.write("#ifndef GL_%s\n"%ext)
-               for e in enums:
-                       out.write("#define %s 0x%04X\n"%(e.name, e.value))
-               out.write("#endif\n\n")
-
-       for f in funcs:
-               out.write("extern %s %s;\n"%(f.typedef, f.name))
-
-out.write("extern Extension %s;\n"%ext)
-
-out.write("""
+       def write_enum_definitions(self, out):
+               enums_by_category = {}
+               for e in self.enums:
+                       cat = None
+                       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()):
+                       if cat:
+                               out.write("#ifndef %s\n"%cat)
+                       for e in enums_by_category[cat]:
+                               out.write("#define %s 0x%04X\n"%(e.name, e.value))
+                       if cat:
+                               out.write("#endif\n")
+                       out.write("\n")
+
+       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")
+
+       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"%ext.lower())
-
-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
+#define GET_PROC_ADDRESS(x) &::x
 #else
 #define GET_PROC_ADDRESS(x) get_proc_address(#x)
 #endif
 
+#ifdef _WIN32
+#define GET_PROC_ADDRESS_1_1(x) &::x
+#else
+#define GET_PROC_ADDRESS_1_1(x) GET_PROC_ADDRESS(x)
+#endif
+""")
+               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"%ext.lower())
-out.write("#ifdef GL_%s\n"%ext)
-if ver:
-       out.write("\tif(is_version_at_least(%d, %d)"%tuple(ver))
-       if backport_ext:
-               out.write(" || is_supported(\"GL_%s\")"%backport_ext)
-       out.write(")\n\t{\n")
-       if funcs:
-               for f in funcs:
-                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, f.name))
-       out.write("\t\treturn Extension::CORE;\n")
-       out.write("\t}\n")
-if ext!=backport_ext:
-       out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(ext))
-       if funcs:
-               for f in funcs:
-                       n = f.name
-                       if f.source:
-                               n = f.source.name
-                       out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, n))
-       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"%(ext, ext, ext.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.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:
+                       rthings.append(t)
+
+       return rthings
+
+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)
+
+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()