X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=scripts%2Fextgen.py;h=f5df83f21588ab0cbb71f94d8a94a8d05023af7a;hb=137b642f43f175a98a45b95de5b0735c5c487a2e;hp=0bf6bd67aa19ad5bfd96a574c39147063e7e06e1;hpb=5d290d603f88cfd8c60a4305601677988353141f;p=libs%2Fgl.git diff --git a/scripts/extgen.py b/scripts/extgen.py index 0bf6bd67..f5df83f2 100755 --- a/scripts/extgen.py +++ b/scripts/extgen.py @@ -4,76 +4,128 @@ import sys 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 ### - -if len(sys.argv)<2: - print """Usage: - extgen.py [] [ ...] - extgen.py [] - -Reads gl.xml and generates files to use . Any promoted functions -are exposed with their promoted names. If extensions are given, -any promoted functions from those are pulled in as well. can -be given to override the version where was promoted to core. - -In the second form, the parameters are read from . If is -absent, the extension's lowercased name is used. Anything after the last dot -in 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 Function: - def __init__(self, name): + 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.majorother.major + return self.minor>other.minor + + +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.typedef = None - self.version = None - self.extension = None - self.vectorequiv = None + self.kind = kind self.aliases = [] - self.extfunc = 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 = [] + + +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 = {} -funcs = {} 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 = "" @@ -84,172 +136,565 @@ 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: - name = get_text_contents(get_nested_elements(cmd, "proto/name")[0]) - func = funcs.get(name) - if not func: - func = Function(name) - funcs[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") - - features = root.getElementsByTagName("feature") - for feat in features: - api = feat.getAttribute("api") - if api=="gl": - version = feat.getAttribute("number") - - commands = get_nested_elements(feat, "require/command") - for c in commands: - name = c.getAttribute("name") - func = funcs.get(name) - if func: - func.version = version - - if feat.getAttribute("name")=="MSPGL_REMOVE": - commands = get_nested_elements(feat, "remove/command") - for c in commands: - name = c.getAttribute("name") - if name in funcs: - del funcs[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") - for c in commands: - name = c.getAttribute("name") - func = funcs.get(name) - if func: - func.extension = ext_name - -parse_file("gl.xml") -parse_file("gl.fixes.xml") - -### Additional processing ### - -# Create references from core functions to their extension counterparts -for f in funcs.itervalues(): - if f.extension==ext or f.extension in secondary: - for a in f.aliases: - aliasfunc = funcs.get(a) - if aliasfunc: - aliasfunc.extfunc = f - -def is_relevant(f): - # Unpromoted extension functions are relevant - if f.extension==ext and not f.aliases: - return True - - # Core functions promoted from the extension are also relevant - if f.extfunc: - e = f.extfunc - if e.extension==ext or e.extension in secondary: - return True +def get_or_create(map, name, type, *args): + obj = map.get(name) + if not obj: + obj = type(name, *args) + map[name] = obj + return obj - return False -funcs = [f for f in funcs.itervalues() if is_relevant(f)] -funcs.sort(key=(lambda f: f.name)) +class GlXmlParser: + def __init__(self, host_api_name, target_ext_name): + self.host_api_name = host_api_name + self.target_ext_name = target_ext_name + self.apis = {} + self.things = {} -for f in funcs: - if not ver: - ver = f.version + 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) - # Functions in backport extensions don't acquire an extension suffix - if f.extension and not f.name.endswith(exttype): - backport_ext = f.extension + aliases = get_nested_elements(cmd, "alias") + func.aliases = [a.getAttribute("name") for a in aliases] - if f.extfunc: - # Typedefs for early core functions are not available in all - # implementations - f.typedef = "PFN%sPROC"%f.extfunc.name.upper() - else: - f.typedef = "PFN%sPROC"%f.name.upper() + 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) -if ver: - ver = map(int, ver.split('.')) + enum.value = int(en.getAttribute("value"), 16) -### Output ### + def parse_feature(self, feat): + api_name = feat.getAttribute("api") + if not api_name: + api_name = self.host_api_name + api = get_or_create(self.apis, api_name, Api) -out = file(out_base+".h", "w") -out.write("#ifndef MSP_GL_%s_\n"%ext.upper()) -out.write("#define MSP_GL_%s_\n"%ext.upper()) + 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 version1: + candidates.sort(reverse=True) + if candidates[1][0]+1>=candidates[0][0]: + print "Warning: multiple likely core version candidates: %s %s"%(candidates[0][1], candidates[1][1]) + return candidates[0][1] + +def detect_deprecated_version(host_api, things): + min_version = None + for t in things: + supp = t.api_support.get(host_api.name) + if supp and supp.deprecated_version: + if min_version is None: + min_version = supp.deprecated_version + else: + min_version = min(min_version, supp.deprecated_version) + else: + return None + + return min_version + +def detect_backport_extension(host_api, target_ext, 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) + + if len(candidates)>1: + print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in candidates)) + + for e in candidates: + if e.base_name==target_ext.base_name: + return e + + if len(candidates)==1: + print "Warning: potential backport extension has mismatched name: %s"%candidates[0].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 not in exts: + exts.append(e) + + for s in supp.sources: + collect_extensions(s, api, exts) + +def detect_source_extension(host_api, target_ext, things): + if target_ext.name in host_api.extensions: + return target_ext + + things_by_ext = {} + for t in things: + exts = [] + collect_extensions(t, host_api.name, exts) + for e in exts: + things_by_ext.setdefault(e, []).append(t) + + largest_ext = None + largest_count = 0 + for e, t in things_by_ext.iteritems(): + count = len(t) + if count>largest_count: + largest_ext = e + largest_count = count + + return largest_ext + + +class SourceGenerator: + def __init__(self, host_api, target_ext, things): + self.host_api = host_api + self.api_prefix = "GL" + if self.host_api.name=="gles2": + self.api_prefix = "GL_ES" + self.target_ext = target_ext + self.funcs = filter((lambda t: t.kind==Thing.FUNCTION), things) + self.funcs.sort(key=(lambda f: f.name)) + self.func_typedefs = dict((f.name, "FPtr_"+f.name) for f in self.funcs) + self.enums = filter((lambda t: t.kind==Thing.ENUM), things) + self.enums.sort(key=(lambda e: e.value)) + self.core_version = detect_core_version(host_api, things) + self.deprecated_version = detect_deprecated_version(host_api, things) + self.backport_ext = detect_backport_extension(host_api, target_ext, things); + self.source_ext = detect_source_extension(host_api, target_ext, things) + + def write_header_intro(self, out): + out.write("#ifndef MSP_GL_%s_\n"%self.target_ext.name.upper()) + out.write("#define MSP_GL_%s_\n"%self.target_ext.name.upper()) + + out.write(""" #include #include namespace Msp { namespace GL { -""") - -if funcs: - out.write("\n") -for f in funcs: - out.write("extern %s %s;\n"%(f.typedef, f.name)) -out.write("\nextern 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()) + def write_source_intro(self, out): + out.write("#include \"%s.h\"\n"%self.target_ext.name.lower()) + if self.funcs: + out.write(""" +#ifdef __APPLE__ +#define GET_PROC_ADDRESS(x) &::x +#else +#define GET_PROC_ADDRESS(x) get_proc_address(#x) +#endif -out.write(""" +#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 { + """) -if funcs: - out.write("\n") -for f in funcs: - out.write("%s %s = 0;\n"%(f.typedef, f.name)) - -out.write("\nExtension::SupportLevel init_%s()\n{\n"%ext.lower()) -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") - 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)) - for f in funcs: - n = f.name - if f.extfunc: - n = f.extfunc.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("\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.target_ext.name.lower()) + if self.core_version: + out.write("\tif(is_disabled(\"GL_%s\"))\n\t\treturn Extension::UNSUPPORTED;\n"%self.target_ext.name) + out.write("#if !defined(__APPLE__) || defined(%s_%s)\n"%(self.api_prefix, self.core_version.as_define())) + out.write("\tif(") + if self.backport_ext: + out.write("is_supported(\"GL_%s\") || "%self.backport_ext.name) + out.write("is_supported(%r"%self.core_version) + if self.deprecated_version: + out.write(", %r"%self.deprecated_version) + out.write("))\n\t{\n") + for f in self.funcs: + supp = f.api_support.get(self.host_api.name) + if supp: + gpa_suffix = "" + if supp.core_version is not None and supp.core_version<=Version(1, 1): + gpa_suffix = "_1_1" + out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS%s(%s));\n"%(f.name, self.func_typedefs[f.name], gpa_suffix, f.name)) + out.write("\t\treturn Extension::CORE;\n") + out.write("\t}\n") + out.write("#endif\n") + if self.source_ext and self.source_ext!=self.backport_ext: + out.write("#if !defined(__APPLE__) || defined(GL_%s)\n"%self.target_ext.name) + out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(self.source_ext.name)) + for f in self.funcs: + supp = f.api_support.get(self.host_api.name) + if supp and supp.sources: + src = None + for s in supp.sources: + if s.name.endswith(self.source_ext.ext_type): + src = s + break + if not src: + src = supp.sources[0] + else: + src = f + + if self.host_api.name in src.api_support: + if not src.name.endswith(self.source_ext.ext_type): + print "Warning: %s does not match extension type %s"%(src.name, self.source_ext.ext_type) + out.write("\t\t%s = reinterpret_cast<%s>(GET_PROC_ADDRESS(%s));\n"%(f.name, self.func_typedefs[f.name], src.name)) + out.write("\t\treturn Extension::EXTENSION;\n") + out.write("\t}\n") + out.write("#endif\n") + out.write("\treturn Extension::UNSUPPORTED;\n") + out.write("}\n") + + def write_source_outro(self, out): + out.write(""" } // namespace GL } // namespace Msp """) + + def write_header(self, fn): + out = file(fn, "w") + self.write_header_intro(out) + self.write_enum_definitions(out) + self.write_function_pointer_declarations(out) + out.write("extern Extension %s;\n"%self.target_ext.name) + self.write_header_outro(out) + + def write_source(self, fn): + out = file(fn, "w") + self.write_source_intro(out) + self.write_function_pointer_definitions(out) + self.write_init_function(out) + ext_name = self.target_ext.name + out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(ext_name, ext_name, ext_name.lower())) + self.write_source_outro(out) + + +class ExtensionParser: + def __init__(self, host_api): + self.host_api = host_api + self.target_ext = None + self.core_version = None + self.deprecated_version = None + self.secondary_exts = [] + self.backport_ext = None + self.ignore_things = [] + + def parse(self, fn): + for line in open(fn): + line = line.strip() + if line.startswith("#"): + continue + + parts = line.split() + keyword = parts[0] + + if keyword=="extension": + self.target_ext = parts[1] + elif keyword=="core_version": + if parts[1]==self.host_api: + self.core_version = Version(*map(int, parts[2].split('.'))) + elif keyword=="deprecated": + if parts[1]==self.host_api: + self.deprecated_version = Version(*map(int, parts[2].split('.'))) + elif keyword=="secondary": + self.secondary_exts.append(parts[1]) + elif keyword=="backport": + self.backport_ext = parts[1] + elif keyword=="ignore": + self.ignore_things.append(parts[1]) + + +def get_extension(api_map, ext_name): + main_api = api_map["gl"] + ext = main_api.extensions.get(ext_name) + if ext: + return ext + + for a in api_map.itervalues(): + ext = a.extensions.get(ext_name) + if ext: + return ext + +def collect_things(host_api, target_ext, secondary, ignore): + ext_things = [t for n, t in target_ext.things.iteritems() if n not in ignore] + core_things = target_ext.api.core_things + + things = [] + for t in ext_things: + found_in_core = False + for a in t.aliases: + if a in core_things: + things.append(core_things[a]) + found_in_core = True + if not found_in_core: + things.append(t) + + for s in secondary: + for t in s.things.itervalues(): + for a in t.aliases: + if a in core_things and core_things[a] not in things: + things.append(core_things[a]) + + return things + +def main(): + if len(sys.argv)<2: + print """Usage: + extgen.py [api] [] + +Reads gl.xml and generates C++ source files to use an OpenGL extension +described in . If is absent, the extension's lowercased +name is used. Anything after the last dot in is removed and +replaced with cpp and h.""" + sys.exit(0) + + host_api_name = "gl" + + i = 1 + if sys.argv[i].startswith("gl"): + host_api_name = sys.argv[i] + i += 1 + + ext_parser = ExtensionParser(host_api_name) + ext_parser.parse(sys.argv[i]) + i += 1 + + if i