• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.cElementTree as etree
32
33MAPI_TABLE_NUM_DYNAMIC = 4096
34
35_LIBRARY_FEATURE_NAMES = {
36    # libGL and libGLdiapatch both include every function.
37    "gl" : None,
38    "gldispatch" : None,
39    "opengl" : frozenset(( "GL_VERSION_1_0", "GL_VERSION_1_1",
40        "GL_VERSION_1_2", "GL_VERSION_1_3", "GL_VERSION_1_4", "GL_VERSION_1_5",
41        "GL_VERSION_2_0", "GL_VERSION_2_1", "GL_VERSION_3_0", "GL_VERSION_3_1",
42        "GL_VERSION_3_2", "GL_VERSION_3_3", "GL_VERSION_4_0", "GL_VERSION_4_1",
43        "GL_VERSION_4_2", "GL_VERSION_4_3", "GL_VERSION_4_4", "GL_VERSION_4_5",
44    )),
45    "glesv1" : frozenset(("GL_VERSION_ES_CM_1_0", "GL_OES_point_size_array")),
46    "glesv2" : frozenset(("GL_ES_VERSION_2_0", "GL_ES_VERSION_3_0",
47            "GL_ES_VERSION_3_1" "GL_ES_VERSION_3_2",
48    )),
49}
50
51def getFunctions(xmlFiles):
52    """
53    Reads an XML file and returns all of the functions defined in it.
54
55    xmlFile should be the path to Khronos's gl.xml file. The return value is a
56    sequence of FunctionDesc objects, ordered by slot number.
57    """
58    roots = [ etree.parse(xmlFile).getroot() for xmlFile in xmlFiles ]
59    return getFunctionsFromRoots(roots)
60
61def getFunctionsFromRoots(roots):
62    functions = {}
63    for root in roots:
64        for func in _getFunctionList(root):
65            functions[func.name] = func
66    functions = functions.values()
67
68    # Sort the function list by name.
69    functions = sorted(functions, key=lambda f: f.name)
70
71    # Assign a slot number to each function. This isn't strictly necessary,
72    # since you can just look at the index in the list, but it makes it easier
73    # to include the slot when formatting output.
74    for i in range(len(functions)):
75        functions[i] = functions[i]._replace(slot=i)
76
77    return functions
78
79def getExportNamesFromRoots(target, roots):
80    """
81    Goes through the <feature> tags from gl.xml and returns a set of OpenGL
82    functions that a library should export.
83
84    target should be one of "gl", "gldispatch", "opengl", "glesv1", or
85    "glesv2".
86    """
87    featureNames = _LIBRARY_FEATURE_NAMES[target]
88    if featureNames is None:
89        return set(func.name for func in getFunctionsFromRoots(roots))
90
91    names = set()
92    for root in roots:
93        features = []
94        for featElem in root.findall("feature"):
95            if featElem.get("name") in featureNames:
96                features.append(featElem)
97        for featElem in root.findall("extensions/extension"):
98            if featElem.get("name") in featureNames:
99                features.append(featElem)
100        for featElem in features:
101            for commandElem in featElem.findall("require/command"):
102                names.add(commandElem.get("name"))
103    return names
104
105class FunctionArg(collections.namedtuple("FunctionArg", "type name")):
106    @property
107    def dec(self):
108        """
109        Returns a "TYPE NAME" string, suitable for a function prototype.
110        """
111        rv = str(self.type)
112        if not rv.endswith("*"):
113            rv += " "
114        rv += self.name
115        return rv
116
117class FunctionDesc(collections.namedtuple("FunctionDesc", "name rt args slot")):
118    def hasReturn(self):
119        """
120        Returns true if the function returns a value.
121        """
122        return (self.rt != "void")
123
124    @property
125    def decArgs(self):
126        """
127        Returns a string with the types and names of the arguments, as you
128        would use in a function declaration.
129        """
130        if not self.args:
131            return "void"
132        else:
133            return ", ".join(arg.dec for arg in self.args)
134
135    @property
136    def callArgs(self):
137        """
138        Returns a string with the names of the arguments, as you would use in a
139        function call.
140        """
141        return ", ".join(arg.name for arg in self.args)
142
143    @property
144    def basename(self):
145        assert self.name.startswith("gl")
146        return self.name[2:]
147
148def _getFunctionList(root):
149    for elem in root.findall("commands/command"):
150        yield _parseCommandElem(elem)
151
152def _parseCommandElem(elem):
153    protoElem = elem.find("proto")
154    (rt, name) = _parseProtoElem(protoElem)
155
156    args = []
157    for ch in elem.findall("param"):
158        # <param> tags have the same format as a <proto> tag.
159        args.append(FunctionArg(*_parseProtoElem(ch)))
160    func = FunctionDesc(name, rt, tuple(args), slot=None)
161
162    return func
163
164def _parseProtoElem(elem):
165    # If I just remove the tags and string the text together, I'll get valid C code.
166    text = _flattenText(elem)
167    text = text.strip()
168    m = re.match(r"^(.+)\b(\w+)(?:\s*\[\s*(\d*)\s*\])?$", text, re.S)
169    if m:
170        typename = _fixupTypeName(m.group(1))
171        name = m.group(2)
172        if m.group(3):
173            # HACK: glPathGlyphIndexRangeNV defines an argument like this:
174            # GLuint baseAndCount[2]
175            # Convert it to a pointer and hope for the best.
176            typename += "*"
177        return (typename, name)
178    else:
179        raise ValueError("Can't parse element %r -> %r" % (elem, text))
180
181def _flattenText(elem):
182    """
183    Returns the text in an element and all child elements, with the tags
184    removed.
185    """
186    text = ""
187    if elem.text is not None:
188        text = elem.text
189    for ch in elem:
190        text += _flattenText(ch)
191        if ch.tail is not None:
192            text += ch.tail
193    return text
194
195def _fixupTypeName(typeName):
196    """
197    Converts a typename into a more consistent format.
198    """
199
200    rv = typeName.strip()
201
202    # Replace "GLvoid" with just plain "void".
203    rv = re.sub(r"\bGLvoid\b", "void", rv)
204
205    # Remove the vendor suffixes from types that have a suffix-less version.
206    rv = re.sub(r"\b(GLhalf|GLintptr|GLsizeiptr|GLint64|GLuint64)(?:ARB|EXT|NV|ATI)\b", r"\1", rv)
207
208    rv = re.sub(r"\bGLvoid\b", "void", rv)
209
210    # Clear out any leading and trailing whitespace.
211    rv = rv.strip()
212
213    # Remove any whitespace before a '*'
214    rv = re.sub(r"\s+\*", r"*", rv)
215
216    # Change "foo*" to "foo *"
217    rv = re.sub(r"([^\*])\*", r"\1 *", rv)
218
219    # Condense all whitespace into a single space.
220    rv = re.sub(r"\s+", " ", rv)
221
222    return rv
223
224