1#!/usr/bin/env python 2 3# (C) Copyright 2015, NVIDIA CORPORATION. 4# All Rights Reserved. 5# 6# Permission is hereby granted, free of charge, to any person obtaining a 7# copy of this software and associated documentation files (the "Software"), 8# to deal in the Software without restriction, including without limitation 9# on the rights to use, copy, modify, merge, publish, distribute, sub 10# license, and/or sell copies of the Software, and to permit persons to whom 11# the Software is furnished to do so, subject to the following conditions: 12# 13# The above copyright notice and this permission notice (including the next 14# paragraph) shall be included in all copies or substantial portions of the 15# Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 20# IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23# IN THE SOFTWARE. 24# 25# Authors: 26# Kyle Brenneman <kbrenneman@nvidia.com> 27 28import collections 29import re 30import sys 31import xml.etree.ElementTree as etree 32 33import os 34GLAPI = os.path.join(os.path.dirname(__file__), "..", "glapi", "gen") 35sys.path.insert(0, GLAPI) 36import static_data 37 38MAPI_TABLE_NUM_DYNAMIC = 4096 39 40_LIBRARY_FEATURE_NAMES = { 41 # libGL and libGLdiapatch both include every function. 42 "gl" : None, 43 "gldispatch" : None, 44 "opengl" : frozenset(( "GL_VERSION_1_0", "GL_VERSION_1_1", 45 "GL_VERSION_1_2", "GL_VERSION_1_3", "GL_VERSION_1_4", "GL_VERSION_1_5", 46 "GL_VERSION_2_0", "GL_VERSION_2_1", "GL_VERSION_3_0", "GL_VERSION_3_1", 47 "GL_VERSION_3_2", "GL_VERSION_3_3", "GL_VERSION_4_0", "GL_VERSION_4_1", 48 "GL_VERSION_4_2", "GL_VERSION_4_3", "GL_VERSION_4_4", "GL_VERSION_4_5", 49 )), 50 "glesv1" : frozenset(("GL_VERSION_ES_CM_1_0", "GL_OES_point_size_array")), 51 "glesv2" : frozenset(("GL_ES_VERSION_2_0", "GL_ES_VERSION_3_0", 52 "GL_ES_VERSION_3_1", "GL_ES_VERSION_3_2", 53 )), 54} 55 56def getFunctions(xmlFiles): 57 """ 58 Reads an XML file and returns all of the functions defined in it. 59 60 xmlFile should be the path to Khronos's gl.xml file. The return value is a 61 sequence of FunctionDesc objects, ordered by slot number. 62 """ 63 roots = [ etree.parse(xmlFile).getroot() for xmlFile in xmlFiles ] 64 return getFunctionsFromRoots(roots) 65 66def getFunctionsFromRoots(roots): 67 functions = {} 68 for root in roots: 69 for func in _getFunctionList(root): 70 functions[func.name] = func 71 functions = functions.values() 72 73 # Sort the function list by name. 74 functions = sorted(functions, key=lambda f: f.name) 75 76 # Lookup for fixed offset/slot functions and use it if available. 77 # Assign a slot number to each function. This isn't strictly necessary, 78 # since you can just look at the index in the list, but it makes it easier 79 # to include the slot when formatting output. 80 81 next_slot = 0 82 for i in range(len(functions)): 83 name = functions[i].name[2:] 84 85 if name in static_data.offsets: 86 functions[i] = functions[i]._replace(slot=static_data.offsets[name]) 87 elif not name.endswith("ARB") and name + "ARB" in static_data.offsets: 88 functions[i] = functions[i]._replace(slot=static_data.offsets[name + "ARB"]) 89 elif not name.endswith("EXT") and name + "EXT" in static_data.offsets: 90 functions[i] = functions[i]._replace(slot=static_data.offsets[name + "EXT"]) 91 else: 92 functions[i] = functions[i]._replace(slot=next_slot) 93 next_slot += 1 94 95 return functions 96 97def getExportNamesFromRoots(target, roots): 98 """ 99 Goes through the <feature> tags from gl.xml and returns a set of OpenGL 100 functions that a library should export. 101 102 target should be one of "gl", "gldispatch", "opengl", "glesv1", or 103 "glesv2". 104 """ 105 featureNames = _LIBRARY_FEATURE_NAMES[target] 106 if featureNames is None: 107 return set(func.name for func in getFunctionsFromRoots(roots)) 108 109 names = set() 110 for root in roots: 111 features = [] 112 for featElem in root.findall("feature"): 113 if featElem.get("name") in featureNames: 114 features.append(featElem) 115 for featElem in root.findall("extensions/extension"): 116 if featElem.get("name") in featureNames: 117 features.append(featElem) 118 for featElem in features: 119 for commandElem in featElem.findall("require/command"): 120 names.add(commandElem.get("name")) 121 return names 122 123class FunctionArg(collections.namedtuple("FunctionArg", "type name")): 124 @property 125 def dec(self): 126 """ 127 Returns a "TYPE NAME" string, suitable for a function prototype. 128 """ 129 rv = str(self.type) 130 if not rv.endswith("*"): 131 rv += " " 132 rv += self.name 133 return rv 134 135class FunctionDesc(collections.namedtuple("FunctionDesc", "name rt args slot")): 136 def hasReturn(self): 137 """ 138 Returns true if the function returns a value. 139 """ 140 return (self.rt != "void") 141 142 @property 143 def decArgs(self): 144 """ 145 Returns a string with the types and names of the arguments, as you 146 would use in a function declaration. 147 """ 148 if not self.args: 149 return "void" 150 else: 151 return ", ".join(arg.dec for arg in self.args) 152 153 @property 154 def callArgs(self): 155 """ 156 Returns a string with the names of the arguments, as you would use in a 157 function call. 158 """ 159 return ", ".join(arg.name for arg in self.args) 160 161 @property 162 def basename(self): 163 assert self.name.startswith("gl") 164 return self.name[2:] 165 166def _getFunctionList(root): 167 for elem in root.findall("commands/command"): 168 yield _parseCommandElem(elem) 169 170def _parseCommandElem(elem): 171 protoElem = elem.find("proto") 172 (rt, name) = _parseProtoElem(protoElem) 173 174 args = [] 175 for ch in elem.findall("param"): 176 # <param> tags have the same format as a <proto> tag. 177 args.append(FunctionArg(*_parseProtoElem(ch))) 178 func = FunctionDesc(name, rt, tuple(args), slot=None) 179 180 return func 181 182def _parseProtoElem(elem): 183 # If I just remove the tags and string the text together, I'll get valid C code. 184 text = _flattenText(elem) 185 text = text.strip() 186 m = re.match(r"^(.+)\b(\w+)(?:\s*\[\s*(\d*)\s*\])?$", text, re.S) 187 if m: 188 typename = _fixupTypeName(m.group(1)) 189 name = m.group(2) 190 if m.group(3): 191 # HACK: glPathGlyphIndexRangeNV defines an argument like this: 192 # GLuint baseAndCount[2] 193 # Convert it to a pointer and hope for the best. 194 typename += "*" 195 return (typename, name) 196 else: 197 raise ValueError("Can't parse element %r -> %r" % (elem, text)) 198 199def _flattenText(elem): 200 """ 201 Returns the text in an element and all child elements, with the tags 202 removed. 203 """ 204 text = "" 205 if elem.text is not None: 206 text = elem.text 207 for ch in elem: 208 text += _flattenText(ch) 209 if ch.tail is not None: 210 text += ch.tail 211 return text 212 213def _fixupTypeName(typeName): 214 """ 215 Converts a typename into a more consistent format. 216 """ 217 218 rv = typeName.strip() 219 220 # Replace "GLvoid" with just plain "void". 221 rv = re.sub(r"\bGLvoid\b", "void", rv) 222 223 # Remove the vendor suffixes from types that have a suffix-less version. 224 rv = re.sub(r"\b(GLhalf|GLintptr|GLsizeiptr|GLint64|GLuint64)(?:ARB|EXT|NV|ATI)\b", r"\1", rv) 225 226 rv = re.sub(r"\bGLDEBUGPROCKHR\b", "GLDEBUGPROC", rv) 227 228 # Clear out any leading and trailing whitespace. 229 rv = rv.strip() 230 231 # Remove any whitespace before a '*' 232 rv = re.sub(r"\s+\*", r"*", rv) 233 234 # Change "foo*" to "foo *" 235 rv = re.sub(r"([^\*])\*", r"\1 *", rv) 236 237 # Condense all whitespace into a single space. 238 rv = re.sub(r"\s+", " ", rv) 239 240 return rv 241 242