From 137b642f43f175a98a45b95de5b0735c5c487a2e Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 17 Nov 2017 23:54:43 +0200 Subject: [PATCH] Refactor the structure of extgen.py Functionality should be more or less preserved at this stage, modulo new or fixed bugs. Some extensions are detected differently on OpenGL ES but those were largely incorrect anyway and will be fixed soon. --- scripts/extgen.py | 979 ++++++++++++++++++++++++++-------------------- 1 file changed, 555 insertions(+), 424 deletions(-) diff --git a/scripts/extgen.py b/scripts/extgen.py index c7c06b2c..f5df83f2 100755 --- a/scripts/extgen.py +++ b/scripts/extgen.py @@ -5,116 +5,109 @@ 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 ### + def __str__(self): + return "%d.%d"%(self.major, self.minor) -if len(sys.argv)<2: - print """Usage: - extgen.py [api] [] [ ...] - extgen.py [api] [] + def __repr__(self): + return "Version(%d, %d)"%(self.major, self.minor) -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. + def as_define(self): + return "VERSION_%d_%d"%(self.major, self.minor) -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) + def __lt__(self, other): + if other is None: + return False -target_api = "gl" + if self.major!=other.major: + return self.majorother.major + return self.minor>other.minor -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]=="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+11: + 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 -# 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) + return min_version -def get_key_api(things): - common_apis = set(target_ext.supported_apis) +def detect_backport_extension(host_api, target_ext, things): + candidates = [] 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) + 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) -# 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 + if len(candidates)>1: + print "Warning: multiple backport extension candidates: %s"%(" ".join(e.name for e in candidates)) - # 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 + for e in candidates: + if e.base_name==target_ext.base_name: + return e - 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: - 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: - bpe_name = backport_ext - backport_ext = extensions.get(backport_ext) + if len(candidates)==1: + print "Warning: potential backport extension has mismatched name: %s"%candidates[0].name - if backport_ext not in backport_ext_candidates: - print "Warning: explicitly specified backport extension %s does not look like a backport extension"%bpe_name -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)) +def collect_extensions(thing, api, exts): + supp = thing.api_support.get(api) + if not supp: + return - for e in backport_ext_candidates: - if e.base_name==target_ext.base_name: - backport_ext = e + for e in supp.extensions: + if not e.backport and e not in exts: + exts.append(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 s in supp.sources: + collect_extensions(s, api, exts) -for f in funcs: - f.typedef = "FPtr_%s"%f.name +def detect_source_extension(host_api, target_ext, things): + if target_ext.name in host_api.extensions: + return target_ext -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(""" + 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 @@ -449,24 +449,16 @@ namespace GL { """) -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" - + def write_enum_definitions(self, out): enums_by_category = {} - for e in enums: + for e in self.enums: cat = None - if e.version: - cat = api_prefix+"_VERSION_"+"_".join(map(str, e.version)) - elif e.extension: - cat = "GL_"+e.extension.name + 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()): @@ -478,23 +470,25 @@ if funcs or enums: 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) + 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(out_base+".cpp", "w") -out.write("#include \"%s.h\"\n"%target_ext.name.lower()) - -if funcs: - out.write(""" + 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 @@ -507,63 +501,200 @@ if funcs: #define GET_PROC_ADDRESS_1_1(x) GET_PROC_ADDRESS(x) #endif """) -out.write(""" + 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"%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("#if !defined(__APPLE__) || defined(GL_VERSION_%d_%d)\n"%tuple(core_version)) - 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") - out.write("#endif\n") -if source_ext and source_ext!=backport_ext: - out.write("#if !defined(__APPLE__) || defined(GL_%s)\n"%target_ext.name) - 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("#endif\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(""" + 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