X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=scripts%2Fextgen.py;h=f5df83f21588ab0cbb71f94d8a94a8d05023af7a;hb=137b642f43f175a98a45b95de5b0735c5c487a2e;hp=d6727123d97cb842a5836ea1b7d2f4a901396962;hpb=6afbace895a7bbcf216ab8e48280ea0303ab5892;p=libs%2Fgl.git diff --git a/scripts/extgen.py b/scripts/extgen.py index d6727123..f5df83f2 100755 --- a/scripts/extgen.py +++ b/scripts/extgen.py @@ -1,169 +1,700 @@ #!/usr/bin/python import sys +import os +import xml.dom +import xml.dom.minidom +import itertools +import re -if len(sys.argv)<2: - print """Usage: - extgen.py [] [ ...]" - -Reads gl.spec 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.""" - sys.exit(0) - -ext = sys.argv[1] -exttype = ext.split('_')[0] -secondary = sys.argv[2:] -ver = None -if secondary and secondary[0][0].isdigit(): - ver = secondary.pop(0) -bp_ext = None - -class Function: - def __init__(self, name): +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.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.version = None - self.category = None - self.vectorequiv = None + self.kind = kind self.aliases = [] - self.extfunc = None - -funcs = {} -cur_func = None - -def parse_file(fn): - for line in open(fn): - if line[0]=='#' or line.find(':')>=0: - continue - elif line[0]=='\t': - if cur_func: - parts = line.split() - if parts[0]=="category": - cur_func.category = parts[1] - elif parts[0]=="vectorequiv": - cur_func.vectorequiv = parts[1] - elif parts[0]=="alias": - cur_func.aliases.append(parts[1]) - elif parts[0]=="version": - cur_func.version = parts[1] - elif parts[0]=="delete": - del funcs[cur_func.name] - cur_func = 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 = {} + + +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: - paren = line.find('(') - if paren>0: - name = line[:paren] - if name in funcs: - cur_func = funcs[name] - else: - cur_func = Function(name) - funcs[name] = cur_func - -parse_file("gl.spec") -parse_file("gl.spec.fixes") - -for f in funcs.itervalues(): - if f.category==ext or f.category in secondary: - if not f.aliases and f.vectorequiv: - for g in funcs.itervalues(): - if g!=f and g.vectorequiv==f.vectorequiv and f.name.startswith(g.name): - f.aliases.append(g.name) + result += get_text_contents(c) + return result + +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, host_api_name, target_ext_name): + self.host_api_name = host_api_name + self.target_ext_name = target_ext_name + 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 - for a in f.aliases: - if a in funcs: - funcs[a].extfunc = f + params = get_nested_elements(cmd, "param") + func.params = map(get_text_contents, params) -def is_relevant(f): - if f.category==ext and not f.aliases: - return True - if f.extfunc: - e = f.extfunc - if e.category==ext or e.category in secondary: - return True - return False + def parse_enum(self, en): + name = en.getAttribute("name") + enum = get_or_create(self.things, name, Enum) -funcs = [f for f in funcs.itervalues() if is_relevant(f)] -funcs.sort(key=(lambda f: f.name)) + enum.value = int(en.getAttribute("value"), 16) + + 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) + + version = feat.getAttribute("number") + if version: + version = Version(*map(int, version.split('.'))) + else: + version = None -for f in funcs: - if f.extfunc: - if not ver: - ver = f.version - if not f.category.startswith("VERSION_"): - bp_ext = f.category - if not f.extfunc and not f.name.endswith(exttype): - bp_ext = f.category + 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 + name = n.name + if name.endswith(enum_suffix): + name = name[:-len(enum_suffix)] + ce = api.core_things.get(name) + if not ce and n.value in core_enums_by_value: + if core_enums_by_value[n.value] is None: + core_enums_by_value[n.value] = filter((lambda e: e.value==n.value), core_enums) + for c in core_enums_by_value[n.value]: + if c.bitmask==n.bitmask: + ce = c + break + if ce and ce.value==n.value and ce.name not in n.aliases: + n.aliases.append(ce.name) + + def resolve_sources(self, api): + for e in api.extensions.itervalues(): + for t in e.things.itervalues(): + for a in t.aliases: + # There are a few cases where a vendor function is aliased to + # an EXT or ARB function but those are rare and not relevant for + # our use + alias = api.core_things.get(a) + if alias: + sources = alias.api_support[api.name].sources + if t not in sources: + sources.append(t) + + def sort_extensions(self): + for a in self.apis.itervalues(): + e = a.extensions.get(self.target_ext_name) + if e: + e.preference = 3 + for t in self.things.itervalues(): + for s in t.api_support.itervalues(): + s.extensions.sort(key=(lambda e: e.preference), reverse=True) + + def finalize(self): + for a in self.apis.itervalues(): + self.check_backport_extensions(a) + self.resolve_enum_aliases(a) + self.resolve_sources(a) + self.sort_extensions() + + +def detect_core_version(host_api, things): + candidates = {} + for t in things: + supp = t.api_support.get(host_api.name) + if supp and supp.core_version: + candidates[supp.core_version] = candidates.get(supp.core_version, 0)+1 + + if candidates: + candidates = list((v, k) for k, v in candidates.items()) + if len(candidates)>1: + candidates.sort(reverse=True) + if candidates[1][0]+1>=candidates[0][0]: + print "Warning: multiple likely core version candidates: %s %s"%(candidates[0][1], candidates[1][1]) + return candidates[0][1] + +def detect_deprecated_version(host_api, things): + min_version = None + for t in things: + supp = t.api_support.get(host_api.name) + if supp and supp.deprecated_version: + if min_version is None: + min_version = supp.deprecated_version + else: + min_version = min(min_version, supp.deprecated_version) + else: + return None + + 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 PFNGL%sPROC gl%s;\n"%(f.name.upper(), f.name)) + 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") -out.write("\nextern Extension %s;\n"%ext) + 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(ext.lower()+".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("PFNGL%sPROC gl%s = 0;\n"%(f.name.upper(), 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 bp_ext: - out.write(" || is_supported(\"GL_%s\")"%bp_ext) - out.write(")\n\t{\n") - for f in funcs: - out.write("\t\tgl%s = reinterpret_cast(get_proc_address(\"gl%s\"));\n"%(f.name, f.name.upper(), f.name)) - out.write("\t\treturn Extension::CORE;\n") - out.write("\t}\n") -if ext!=bp_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\tgl%s = reinterpret_cast(get_proc_address(\"gl%s\"));\n"%(f.name, f.name.upper(), 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