#!/usr/bin/python
import sys
+import os
+import xml.dom
+import xml.dom.minidom
+import itertools
-ext = sys.argv[1]
+### Command line processing ###
-funcs = []
-cur_func = None
-for line in file("gl.spec"):
- if line[0]=='#' or line.find(':')>=0:
- continue
- elif line[0]=='\t' and cur_func:
+if len(sys.argv)<2:
+ print """Usage:
+ extgen.py [api] <extension> [<core_version>] [<secondary> ...]
+ extgen.py [api] <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)
+
+target_api = "gl"
+
+i = 1
+if sys.argv[i].startswith("gl"):
+ target_api = sys.argv[i]
+ i += 1
+
+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]=="category" and parts[1]==ext:
- funcs.append(cur_func)
+ 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
+
+ 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 = []
+
+class Function(Thing):
+ def __init__(self, name):
+ Thing.__init__(self, name, Thing.FUNCTION)
+ self.return_type = "void"
+ self.params = []
+ self.typedef = None
+
+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)
+
+class Extension:
+ def __init__(self, name):
+ self.name = name
+ self.supported_apis = []
+ underscore = name.find('_')
+ self.ext_type = name[0:underscore]
+ self.base_name = name[underscore+1:]
+
+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 c in childElements:
+ if c.tagName==head:
+ result += get_nested_elements(c, tail)
+ return result
+ else:
+ 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 = ""
+ for c in node.childNodes:
+ if c.nodeType==xml.dom.Node.TEXT_NODE or c.nodeType==xml.dom.Node.CDATA_SECTION_NODE:
+ result += c.data
+ else:
+ 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
+
+ aliases = get_nested_elements(cmd, "alias")
+ func.aliases = [a.getAttribute("name") for a in aliases]
+
+ ptype = get_first_child(proto, "ptype")
+ if ptype:
+ func.return_type = get_text_contents(ptype)
+ else:
+ for c in proto.childNodes:
+ if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip():
+ func.return_type = c.data.strip()
+ break
+
+ params = get_nested_elements(cmd, "param")
+ func.params = map(get_text_contents, params)
+
+def parse_enum(en):
+ name = en.getAttribute("name")
+ enum = things.get(name)
+ if not enum:
+ enum = Enum(name)
+ things[name] = enum
+
+ enum.value = int(en.getAttribute("value"), 16)
+
+def parse_feature(feat):
+ api = feat.getAttribute("api")
+ version = feat.getAttribute("number")
+ if version:
+ version = map(int, version.split('.'))
+ else:
+ version = None
+
+ requires = get_nested_elements(feat, "require")
+ for req in requires:
+ commands = get_nested_elements(req, "command")
+ enums = get_nested_elements(req, "enum")
+ for t in itertools.chain(commands, enums):
+ name = t.getAttribute("name")
+ thing = things.get(name)
+ if thing:
+ thing.supported_apis.setdefault(api, version)
+
+ if not api or api==target_api:
+ removes = get_nested_elements(feat, "remove")
+ for rem in removes:
+ profile = rem.getAttribute("profile")
+ commands = get_nested_elements(rem, "command")
+ enums = get_nested_elements(rem, "enum")
+
+ for t in itertools.chain(commands, enums):
+ name = t.getAttribute("name")
+ if name in things:
+ if profile!="core":
+ del things[name]
+ else:
+ things[name].deprecated.setdefault(api, version)
+
+def parse_extension(ext):
+ ext_name = ext.getAttribute("name")
+ if ext_name.startswith("GL_"):
+ ext_name = ext_name[3:]
+
+ supported = ext.getAttribute("supported").split('|')
+ if target_api not in supported and ext_name!=target_ext:
+ return
+
+ extension = extensions.get(ext_name)
+ if not extension:
+ extension = Extension(ext_name)
+ extensions[ext_name] = extension
+
+ extension.supported_apis = supported
+
+ 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(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
+
+ 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
+
+ thing.extension = extension
+ for a in supported:
+ thing.supported_apis.setdefault(a, "ext")
+
+def parse_file(fn):
+ doc = xml.dom.minidom.parse(fn)
+ root = doc.documentElement
+
+ commands = get_nested_elements(root, "commands/command")
+ for cmd in commands:
+ parse_command(cmd)
+
+ enums = get_nested_elements(root, "enums/enum")
+ for en in enums:
+ parse_enum(en)
+
+ features = get_nested_elements(root, "feature")
+ for feat in features:
+ parse_feature(feat)
+
+ extensions = get_nested_elements(root, "extensions/extension")
+ for ext in extensions:
+ parse_extension(ext)
+
+parse_file("gl.xml")
+parse_file("gl.fixes.xml")
+
+### Additional processing ###
+
+if target_ext in extensions:
+ target_ext = extensions[target_ext]
+else:
+ print "Extension %s not found"%target_ext
+ sys.exit(1)
+
+# 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)
+
+def get_key_api(things):
+ common_apis = set(target_ext.supported_apis)
+ for t in things:
+ common_apis.intersection_update(t.supported_apis.keys())
+ if common_apis:
+ return common_apis.pop()
+ else:
+ return target_api
+
+for e in enums:
+ if all(v=="ext" for v in e.supported_apis.values()) and e.value in core_enums_by_value:
+ if core_enums_by_value[e.value] is None:
+ candidates = [ce for ce in core_enums if ce.value==e.value]
+ key_api = get_key_api(candidates)
+ core_enums_by_value[e.value] = list(sorted(candidates, key=(lambda x: x.supported_apis.get(key_api, "ext"))))
+ for ce in core_enums_by_value[e.value]:
+ if ce.bitmask==e.bitmask:
+ e.aliases.append(ce.name)
+ break
+
+# Create references from core things to their extension counterparts
+for t in things.itervalues():
+ if t.extension:
+ for a in t.aliases:
+ alias = things.get(a)
+ if alias:
+ if target_api in t.supported_apis:
+ alias.sources.insert(0, t)
+ else:
+ alias.sources.append(t)
+
+# 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:
- paren = line.find('(')
- if paren>0:
- cur_func = line[:paren]
+ 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:
+ backport_ext = extensions[backport_ext]
+
+ if backport_ext not in backport_ext_candidates:
+ print "Warning: explicitly specified backport extension %s does not look like a backport extension"
+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))
-out = file(ext.lower()+".h", "w")
-out.write("#ifndef MSP_GL_%s_\n"%ext.upper())
-out.write("#define MSP_GL_%s_\n"%ext.upper())
+ 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]]
+ 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("""
-#include "gl.h"
-#include <GL/glext.h>
+#include <msp/gl/extension.h>
+#include <msp/gl/gl.h>
namespace Msp {
namespace GL {
""")
-for f in funcs:
- out.write("extern PFNGL%sPROC gl%s;\n"%(f.upper(), f))
+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"
-out.write("\nvoid init_%s();\n"%ext.lower())
+ enums_by_category = {}
+ for e in enums:
+ cat = None
+ if e.version:
+ cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version))
+ elif e.extension:
+ cat = "GL_"+e.extension.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")
+
+ for f in funcs:
+ out.write("extern %s %s;\n"%(f.typedef, f.name))
+
+out.write("extern Extension %s;\n"%target_ext.name)
out.write("""
} // namespace GL
#endif
""")
-out = file(ext.lower()+".cpp", "w")
-out.write("#include \"extension.h\"\n")
-out.write("#include \"%s.h\"\n"%ext.lower())
+out = file(out_base+".cpp", "w")
+out.write("#include \"%s.h\"\n"%target_ext.name.lower())
+
+if funcs:
+ out.write("""
+#ifdef __APPLE__
+#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("PFNGL%sPROC gl%s = 0;\n"%(f.upper(), f))
+ out.write("%s %s = 0;\n"%(f.typedef, f.name))
-out.write("\nvoid init_%s()\n{\n"%ext.lower())
-for f in funcs:
- out.write("\tgl%s = reinterpret_cast<PFNGL%sPROC>(get_proc_address(\"gl%s\"));\n"%(f, f.upper(), f))
+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("\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")
+if source_ext and source_ext!=backport_ext:
+ out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(source_ext.name))
+ for f in funcs:
+ if f.sources:
+ src = None
+ for s in f.sources:
+ if s.name.endswith(source_ext.ext_type):
+ src = s
+ break
+ if not src:
+ src = f.sources[0]
+ else:
+ src = f
+
+ if target_api in src.supported_apis:
+ if not src.name.endswith(source_ext.ext_type):
+ print "Warning: %s does not match extension type %s"%(src.name, source_ext.ext_type)
+ out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, f.typedef, src.name))
+ out.write("\t\treturn Extension::EXTENSION;\n")
+ out.write("\t}\n")
+out.write("\treturn Extension::UNSUPPORTED;\n")
out.write("}\n")
+out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
+
out.write("""
} // namespace GL
} // namespace Msp