]> git.tdb.fi Git - libs/gl.git/blobdiff - scripts/extgen.py
Refactor and optimize the extension generator script
[libs/gl.git] / scripts / extgen.py
index 2c515f22ecee971f234796924428376d58f80c8f..c8f87c3bf79278bb98e5e1342912f66570752cab 100755 (executable)
 #!/usr/bin/python
 
 import sys
+import os
+import xml.dom
+import xml.dom.minidom
+import itertools
 
-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)
+### Command line processing ###
+
+if len(sys.argv)<2:
+       print """Usage:
+  extgen.py <extension> [<core_version>] [<secondary> ...]
+  extgen.py <extfile> [<outfile>]
+
+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.  <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)
+
+target_ext = sys.argv[1]
+out_base = None
+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":
+                       core_version = 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:]
+       core_version = None
+       if secondary and secondary[0][0].isdigit():
+               core_version = secondary.pop(0)
+
+ext_type = target_ext.split('_')[0]
+backport_ext = None
+
+if core_version:
+       core_version = map(int, core_version.split('.'))
+
+if not out_base:
+       out_base = target_ext.lower()
+
+### XML file parsing ###
+
+class Thing:
+       FUNCTION = 1
+       ENUM = 2
+
+       def __init__(self, name, kind):
+               self.name = name
+               self.kind = kind
+               self.version = None
+               self.extension = None
+               self.aliases = []
+               self.source = None
+
+class Function(Thing):
+       def __init__(self, name):
+               Thing.__init__(self, name, Thing.FUNCTION)
+               self.return_type = "void"
+               self.params = []
+               self.typedef = None
+
+class Enum(Thing):
+       def __init__(self, name):
+               Thing.__init__(self, name, Thing.ENUM)
+               self.value = 0
+
+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:
-               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 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")
+       if api=="gl":
+               version = feat.getAttribute("number")
+               if version:
+                       version = map(int, version.split('.'))
+               else:
+                       version = None
+
+               commands = get_nested_elements(feat, "require/command")
+               enums = get_nested_elements(feat, "require/enum")
+               for t in itertools.chain(commands, enums):
+                       name = t.getAttribute("name")
+                       thing = things.get(name)
+                       if thing:
+                               thing.version = version
+
+               if feat.getAttribute("name")=="MSPGL_REMOVE":
+                       commands = get_nested_elements(feat, "remove/command")
+                       enums = get_nested_elements(feat, "remove/enum")
+                       for t in itertools.chain(commands, enums):
+                               name = t.getAttribute("name")
+                               if 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 "gl" in supported:
+               commands = get_nested_elements(ext, "require/command")
+               enums = get_nested_elements(ext, "require/enum")
+               for t in itertools.chain(commands, enums):
+                       name = t.getAttribute("name")
+                       thing = things.get(name)
+                       if thing:
+                               thing.extension = ext_name
 
-out=file(ext.lower()+".h", "w")
-out.write("#ifndef MSP_GL_%s_\n"%ext.upper())
-out.write("#define MSP_GL_%s_\n"%ext.upper())
+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 ###
+
+# Find aliases for enums
+enums = [t for t in things.itervalues() if t.kind==Thing.ENUM]
+core_enums_by_value = dict((e.value, e) for e in enums if e.version)
+
+for e in enums:
+       if e.kind==t.ENUM and e.extension:
+               ce = core_enums_by_value.get(e.value)
+               if ce and ce!=e:
+                       e.aliases.append(ce.name)
+
+# Create references from core things to their extension counterparts
+for t in things.itervalues():
+       if t.extension==target_ext or t.extension in secondary:
+               for a in t.aliases:
+                       alias = things.get(a)
+                       if alias:
+                               alias.source = t
+
+def is_relevant(t):
+       # Unpromoted extension things are relevant
+       if t.extension==target_ext and not t.aliases:
+               return True
+
+       # Core things promoted from the extension are also relevant
+       if t.source:
+               e = t.source
+               if e.extension==target_ext or e.extension in secondary:
+                       return True
+
+       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))
+
+for t in itertools.chain(funcs, enums):
+       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):
+               backport_ext = t.extension
+
+for f in funcs:
+       if f.source:
+               # Typedefs for early core functions are not available in all
+               # implementations
+               f.typedef = "PFN%sPROC"%f.source.name.upper()
+       else:
+               f.typedef = "PFN%sPROC"%f.name.upper()
+
+### Output ###
+
+out = file(out_base+".h", "w")
+out.write("#ifndef MSP_GL_%s_\n"%target_ext.upper())
+out.write("#define MSP_GL_%s_\n"%target_ext.upper())
 
 out.write("""
-#include "gl.h"
-#include <GL/glext.h>
+#include <msp/gl/extension.h>
+#include <msp/gl/gl.h>
 
 namespace Msp {
 namespace GL {
 
 """)
 
-for f in funcs:
-       out.write("extern PFNGL%sPROC gl%s;\n"%(f.upper(), f))
+if funcs or enums:
+       if funcs:
+               out.write("#if defined(__APPLE__) || !defined(GL_%s)\n"%target_ext)
+               for f in funcs:
+                       out.write("typedef %s (*%s)(%s);\n"%(f.return_type, f.typedef, ", ".join(f.params)))
+               out.write("#endif\n\n")
 
-out.write("\nvoid init_%s();\n"%ext.lower())
+       if enums:
+               if core_version:
+                       out.write("#ifndef GL_VERSION_%s\n"%"_".join(map(str, core_version)))
+               else:
+                       out.write("#ifndef GL_%s\n"%target_ext)
+               for e in enums:
+                       out.write("#define %s 0x%04X\n"%(e.name, e.value))
+               out.write("#endif\n\n")
+
+       for f in funcs:
+               out.write("extern %s %s;\n"%(f.typedef, f.name))
+
+out.write("extern Extension %s;\n"%target_ext)
 
 out.write("""
 } // namespace GL
@@ -43,10 +302,17 @@ out.write("""
 #endif
 """)
 
-out=file(ext.lower()+".cpp", "w")
-out.write("#include \"extension.h\"\n")
-out.write("#include \"%s.h\"\n"%ext.lower())
+out = file(out_base+".cpp", "w")
+out.write("#include \"%s.h\"\n"%target_ext.lower())
 
+if funcs:
+       out.write("""
+#ifdef __APPLE__
+#define GET_PROC_ADDRESS(x) ::x
+#else
+#define GET_PROC_ADDRESS(x) get_proc_address(#x)
+#endif
+""")
 out.write("""
 namespace Msp {
 namespace GL {
@@ -54,13 +320,36 @@ namespace GL {
 """)
 
 for f in funcs:
-       out.write("PFNGL%sPROC gl%s=0;\n"%(f.upper(), f))
+       out.write("%s %s = 0;\n"%(f.typedef, f.name))
 
-out.write("\nvoid init_%s()\n{\n"%ext.lower())
-for f in funcs:
-       out.write("\tgl%s=reinterpret_cast<PFNGL%sPROC>(get_proc_address(\"gl%s\"));\n"%(f, f.upper(), f))
+out.write("\nExtension::SupportLevel init_%s()\n{\n"%target_ext.lower())
+out.write("#ifdef GL_%s\n"%target_ext)
+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)
+       out.write(")\n\t{\n")
+       if funcs:
+               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 target_ext!=backport_ext:
+       out.write("\tif(is_supported(\"GL_%s\"))\n\t{\n"%(target_ext))
+       if funcs:
+               for f in funcs:
+                       n = f.name
+                       if f.source:
+                               n = f.source.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("#endif\n")
+out.write("\treturn Extension::UNSUPPORTED;\n")
 out.write("}\n")
 
+out.write("\nExtension %s(\"GL_%s\", init_%s);\n"%(target_ext, target_ext, target_ext.lower()))
+
 out.write("""
 } // namespace GL
 } // namespace Msp