X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=blobdiff_plain;f=scripts%2Fextgen.py;h=ff40ec60eadd1f515336f78d4d82eec2e0cfdfff;hp=2c515f22ecee971f234796924428376d58f80c8f;hb=e19309340e90ee881e9cb2f8b7c33a5b89681aa6;hpb=aa6020a158c85fdb3b7e9993065861dd1b6531ad diff --git a/scripts/extgen.py b/scripts/extgen.py index 2c515f22..ff40ec60 100755 --- a/scripts/extgen.py +++ b/scripts/extgen.py @@ -1,67 +1,866 @@ #!/usr/bin/python import sys +import os +import xml.dom +import xml.dom.minidom +import itertools +import re -ext=sys.argv[1] - -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: - parts=line.split() - if parts[0]=="category" and parts[1]==ext: - funcs.append(cur_func) +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 + + def __eq__(self, other): + if other is None: + return False + + return (self.major==other.major and 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.kind = kind + self.aliases = [] + 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: - paren=line.find('(') - if paren>0: - cur_func=line[:paren] + 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 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) + else: + for c in proto.childNodes: + if c.nodeType==xml.dom.Node.TEXT_NODE and c.data.strip(): + func.return_type = c.data.strip() + break + + params = get_nested_elements(cmd, "param") + func.params = map(get_text_contents, params) + + def parse_enum(self, en): + name = en.getAttribute("name") + enum = get_or_create(self.things, name, Enum) + + enum.value = int(en.getAttribute("value"), 16) + + alias = en.getAttribute("alias") + if alias: + enum.aliases.append(alias) + + def parse_feature(self, feat): + api_name = feat.getAttribute("api") + api = get_or_create(self.apis, api_name, Api) + + version = feat.getAttribute("number") + if version: + version = Version(*map(int, version.split('.'))) + else: + version = None + + requires = get_nested_elements(feat, "require") + for req in requires: + commands = get_nested_elements(req, "command") + enums = get_nested_elements(req, "enum") + for t in itertools.chain(commands, enums): + name = t.getAttribute("name") + thing = self.things.get(name) + if thing: + supp = thing.get_or_create_api_support(api.name) + if not supp.core_version or versionmax_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 + 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_versionlargest_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 +#include namespace Msp { namespace GL { """) -for f in funcs: - out.write("extern PFNGL%sPROC gl%s;\n"%(f.upper(), f)) + 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("\nvoid init_%s();\n"%ext.lower()) + 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 \"extension.h\"\n") -out.write("#include \"%s.h\"\n"%ext.lower()) + def write_source_intro(self, out): + out.write("#include \"%s.h\"\n"%self.ext_name.lower()) + if self.funcs: + out.write(""" +#ifdef __APPLE__ +#define GET_PROC_ADDRESS(x) &::x +#else +#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 { """) -for f in funcs: - out.write("PFNGL%sPROC gl%s=0;\n"%(f.upper(), f)) + 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 -out.write("\nvoid init_%s()\n{\n"%ext.lower()) -for f in funcs: - out.write("\tgl%s=reinterpret_cast(get_proc_address(\"gl%s\"));\n"%(f, f.upper(), f)) -out.write("}\n") + 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") -out.write(""" + 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] [] + +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(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