]> git.tdb.fi Git - libs/gl.git/blobdiff - scripts/extgen.py
Implement 2D array textures
[libs/gl.git] / scripts / extgen.py
index bcff3b4498ccafeefbc061c81872c3fbef3051be..45094a4df35df26fe2608e0f07a4005378ec125e 100755 (executable)
 
 import sys
 import os
+import xml.dom
+import xml.dom.minidom
+import itertools
+
+### Command line processing ###
 
 if len(sys.argv)<2:
        print """Usage:
-  extgen.py <extension> [<version>] [<secondary> ...]"
+  extgen.py [api] <extension> [<core_version>] [<secondary> ...]
+  extgen.py [api] <extfile> [<outfile>]
 
-Reads gl.spec and generates files to use <extension>.  Any promoted functions
+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.  <version> can be
-given to override the version where <extension> was promoted to core."""
+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)
 
-ext = sys.argv[1]
+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
 out_base = None
-if ext.endswith(".glext"):
-       fn = ext
-       ext = None
-       ver = 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":
-                       ext = parts[1]
+                       target_ext = parts[1]
                elif parts[0]=="core_version":
-                       ver = parts[1]
+                       if parts[1]==target_api:
+                               core_version = parts[2]
                elif parts[0]=="secondary":
                        secondary.append(parts[1])
-       if len(sys.argv)>=3:
-               out_base = os.path.splitext(sys.argv[2])[0]
+               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[2:]
-       ver = None
+       secondary = sys.argv[i+1:]
+       core_version = None
        if secondary and secondary[0][0].isdigit():
-               ver = secondary.pop(0)
+               core_version = secondary.pop(0)
+
+ext_type = target_ext.split('_')[0]
 
-exttype = ext.split('_')[0]
-bp_ext = None
+if core_version:
+       core_version = map(int, core_version.split('.'))
 
 if not out_base:
-       out_base = ext.lower()
+       out_base = target_ext.lower()
 
-class Function:
-       def __init__(self, name):
+### XML file parsing ###
+
+class Thing:
+       FUNCTION = 1
+       ENUM = 2
+
+       def __init__(self, name, kind):
                self.name = name
-               self.typedef = None
+               self.kind = kind
                self.version = None
-               self.category = None
-               self.vectorequiv = None
+               self.extension = None
+               self.supported_apis = {}
                self.aliases = []
-               self.extfunc = None
+               self.sources = []
 
-funcs = {}
-cur_func = None
+class Function(Thing):
+       def __init__(self, name):
+               Thing.__init__(self, name, Thing.FUNCTION)
+               self.return_type = "void"
+               self.params = []
+               self.typedef = 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
+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:
-                       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)
-                                       break
+                       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 profile!="core" and name in things:
+                                       del things[name]
+
+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 a in f.aliases:
-                       if a in funcs:
-                               funcs[a].extfunc = f
+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
 
-def is_relevant(f):
-       if f.category==ext and not f.aliases:
+# 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
-       if f.extfunc:
-               e = f.extfunc
-               if e.category==ext or e.category in secondary:
+
+       # 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 = [f for f in funcs.itervalues() if is_relevant(f)]
+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
+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 not core_version:
+               core_version = t.version
+
+       # 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 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))
+
+       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:
-       if f.extfunc:
-               f.typedef = "PFNGL%sPROC"%f.extfunc.name.upper()
-               if not ver:
-                       ver = f.version
-               if not f.category.startswith("VERSION_"):
-                       bp_ext = f.category
+       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:
-               f.typedef = "PFNGL%sPROC"%f.name.upper()
-               if not f.name.endswith(exttype):
-                       bp_ext = f.category
+               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
 
-if ver:
-       ver = map(int, ver.split('.'))
+### Output ###
 
 out = file(out_base+".h", "w")
-out.write("#ifndef MSP_GL_%s_\n"%ext.upper())
-out.write("#define MSP_GL_%s_\n"%ext.upper())
+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 <msp/gl/extension.h>
 #include <msp/gl/gl.h>
-#include <GL/glext.h>
 
 namespace Msp {
 namespace GL {
+
 """)
 
-if funcs:
-       out.write("\n")
-for f in funcs:
-       out.write("extern %s gl%s;\n"%(f.typedef, f.name))
+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("\nextern Extension %s;\n"%ext)
+               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
@@ -153,41 +456,69 @@ out.write("""
 """)
 
 out = file(out_base+".cpp", "w")
-out.write("#include \"%s.h\"\n"%ext.lower())
+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 {
+
 """)
 
-if funcs:
-       out.write("\n")
 for f in funcs:
-       out.write("%s gl%s = 0;\n"%(f.typedef, f.name))
+       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 bp_ext:
-               out.write(" || is_supported(\"GL_%s\")"%bp_ext)
+out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.name.lower())
+if core_version:
+       out.write("\tif(is_version_at_least(%d, %d)"%tuple(core_version))
+       if backport_ext:
+               out.write(" || is_supported(\"GL_%s\")"%backport_ext.name)
        out.write(")\n\t{\n")
        for f in funcs:
-               out.write("\t\tgl%s = reinterpret_cast<%s>(get_proc_address(\"gl%s\"));\n"%(f.name, f.typedef, f.name))
+               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 ext!=bp_ext:
-       out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(ext))
+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:
-               n = f.name
-               if f.extfunc:
-                       n = f.extfunc.name
-               out.write("\t\tgl%s = reinterpret_cast<%s>(get_proc_address(\"gl%s\"));\n"%(f.name, f.typedef, n))
+               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"%(ext, ext, ext.lower()))
+out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext.name, target_ext.name, target_ext.name.lower()))
 
 out.write("""
 } // namespace GL