1#!/usr/bin/env python
2#
3# Copyright (C) 2011 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# ABOUT
18#   This script is used to generate the trace implementations of all
19#   OpenGL calls. When executed, it reads the specs for the OpenGL calls
20#   from the files GLES2/gl2_api.in, GLES2/gl2ext_api.in, GLES_CM/gl_api.in,
21#   and GLES_CM/glext_api.in, and generates trace versions for all the
22#   defined functions.
23#
24# PREREQUISITES
25#   To generate C++ files, this script uses the 'pyratemp' template
26#   module. The only reason to use pyratemp is that it is extremly
27#   simple to install:
28#   $ wget http://www.simple-is-better.org/template/pyratemp-current/pyratemp.py
29#   Put the file in the GLES_trace/tools folder, or update PYTHONPATH
30#   to point to wherever it was downloaded.
31#
32# USAGE
33#   $ cd GLES_trace       - run the program from GLES2_trace folder
34#   $ ./tools/genapi.py   - generates a .cpp and .h file
35#   $ mv *.cpp *.h src/   - move the generated files into the src folder
36
37import sys
38import re
39import pyratemp
40
41# Constants corresponding to the protobuf DataType.Type
42class DataType:
43    def __init__(self, name):
44        self.name = name
45
46    def __str__(self):
47        if self.name == "pointer":  # pointers map to the INT DataType
48            return "INT"
49        return self.name.upper()
50
51    def getProtobufCall(self):
52        if self.name == "void":
53            raise ValueError("Attempt to set void value")
54        elif self.name == "char" or self.name == "byte" \
55                or self.name == "pointer" or self.name == "enum":
56            return "add_intvalue((int)"
57        elif self.name == "int":
58            return "add_intvalue("
59        elif self.name == "float":
60            return "add_floatvalue("
61        elif self.name == "bool":
62            return "add_boolvalue("
63        elif self.name == "int64":
64            return "add_int64value("
65        else:
66            raise ValueError("Unknown value type %s" % self.name)
67
68DataType.VOID = DataType("void")
69DataType.CHAR = DataType("char")
70DataType.BYTE = DataType("byte")
71DataType.ENUM = DataType("enum")
72DataType.BOOL = DataType("bool")
73DataType.INT = DataType("int")
74DataType.FLOAT = DataType("float")
75DataType.POINTER = DataType("pointer")
76DataType.INT64 = DataType("int64")
77
78# mapping of GL types to protobuf DataType
79GLPROTOBUF_TYPE_MAP = {
80    "GLvoid":DataType.VOID,
81    "void":DataType.VOID,
82    "GLchar":DataType.CHAR,
83    "GLenum":DataType.ENUM,
84    "GLboolean":DataType.BOOL,
85    "GLbitfield":DataType.INT,
86    "GLbyte":DataType.BYTE,
87    "GLshort":DataType.INT,
88    "GLint":DataType.INT,
89    "int":DataType.INT,
90    "GLsizei":DataType.INT,
91    "GLubyte":DataType.BYTE,
92    "GLushort":DataType.INT,
93    "GLuint":DataType.INT,
94    "GLfloat":DataType.FLOAT,
95    "GLclampf":DataType.FLOAT,
96    "GLfixed":DataType.INT,
97    "GLclampx":DataType.INT,
98    "GLsizeiptr":DataType.INT,
99    "GLintptr":DataType.INT,
100    "GLeglImageOES":DataType.POINTER,
101    "GLint64":DataType.INT64,
102    "GLuint64":DataType.INT64,
103    "GLsync":DataType.POINTER,
104}
105
106API_SPECS = [
107    ('GL3','../GLES2/gl3_api.in'),
108    ('GL3Ext','../GLES2/gl3ext_api.in'),
109    ('GL2','../GLES2/gl2_api.in'),
110    ('GL2Ext','../GLES2/gl2ext_api.in'),
111    ('GL1','../GLES_CM/gl_api.in'),
112    ('GL1Ext','../GLES_CM/glext_api.in'),
113]
114
115HEADER_LICENSE = """/*
116 * Copyright 2011, The Android Open Source Project
117 *
118 * Licensed under the Apache License, Version 2.0 (the "License");
119 * you may not use this file except in compliance with the License.
120 * You may obtain a copy of the License at
121 *
122 *     http://www.apache.org/licenses/LICENSE-2.0
123 *
124 * Unless required by applicable law or agreed to in writing, software
125 * distributed under the License is distributed on an "AS IS" BASIS,
126 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
127 * See the License for the specific language governing permissions and
128 * limitations under the License.
129 *
130 * THIS FILE WAS GENERATED BY A SCRIPT. DO NOT EDIT.
131 */
132"""
133
134HEADER_INCLUDES = """
135#include <cutils/log.h>
136#include <utils/Timers.h>
137#include <GLES3/gl3.h>
138
139#include "gltrace.pb.h"
140#include "gltrace_context.h"
141#include "gltrace_fixup.h"
142#include "gltrace_transport.h"
143"""
144
145HEADER_NAMESPACE_START = """
146namespace android {
147namespace gltrace {
148"""
149
150FOOTER_TEXT = """
151}; // namespace gltrace
152}; // namespace android
153"""
154
155TRACE_CALL_TEMPLATE = pyratemp.Template(
156"""$!retType!$ GLTrace_$!func!$($!inputArgList!$) {
157    GLMessage glmsg;
158    GLTraceContext *glContext = getGLTraceContext();
159
160    glmsg.set_function(GLMessage::$!func!$);
161<!--(if len(parsedArgs) > 0)-->
162    <!--(for argname, argtype in parsedArgs)-->
163
164    // copy argument $!argname!$
165    GLMessage_DataType *arg_$!argname!$ = glmsg.add_args();
166    arg_$!argname!$->set_isarray(false);
167    arg_$!argname!$->set_type(GLMessage::DataType::$!argtype!$);
168    arg_$!argname!$->$!argtype.getProtobufCall()!$$!argname!$);
169    <!--(end)-->
170<!--(end)-->
171
172    // call function
173    nsecs_t wallStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
174    nsecs_t threadStartTime = systemTime(SYSTEM_TIME_THREAD);
175<!--(if retType != "void")-->
176    $!retType!$ retValue = glContext->hooks->gl.$!callsite!$;
177<!--(else)-->
178    glContext->hooks->gl.$!callsite!$;
179<!--(end)-->
180    nsecs_t threadEndTime = systemTime(SYSTEM_TIME_THREAD);
181    nsecs_t wallEndTime = systemTime(SYSTEM_TIME_MONOTONIC);
182<!--(if retType != "void")-->
183
184    // set return value
185    GLMessage_DataType *rt = glmsg.mutable_returnvalue();
186    rt->set_isarray(false);
187    rt->set_type(GLMessage::DataType::$!retDataType!$);
188    rt->$!retDataType.getProtobufCall()!$retValue);
189<!--(end)-->
190
191    void *pointerArgs[] = {
192<!--(for argname, argtype in parsedArgs)-->
193    <!--(if argtype == DataType.POINTER)-->
194        (void *) $!argname!$,
195    <!--(end)-->
196<!--(end)-->
197<!--(if retDataType == DataType.POINTER)-->
198        (void *) retValue,
199<!--(end)-->
200    };
201
202    fixupGLMessage(glContext, wallStartTime, wallEndTime,
203                              threadStartTime, threadEndTime,
204                              &glmsg, pointerArgs);
205    glContext->traceGLMessage(&glmsg);
206<!--(if retType != "void")-->
207
208    return retValue;
209<!--(end)-->
210}
211""")
212
213def getDataTypeFromKw(kw):
214    """ Get the data type given declaration.
215    All pointer declarations are of type DataType.POINTER
216
217    e.g.: GLvoid -> DataType.VOID"""
218
219    if kw.count('*') > 0:
220        return DataType.POINTER
221    return GLPROTOBUF_TYPE_MAP.get(kw)
222
223def getNameTypePair(decl):
224    """ Split declaration of a variable to a tuple of (variable name, DataType).
225    e.g. "const GLChar* varName" -> (varName, POINTER) """
226    elements = decl.strip().split(' ')
227    name = None
228    if len(elements) > 1:
229        name = " ".join(elements[-1:]).strip()      # last element is the name
230        dataType = " ".join(elements[:-1]).strip()  # everything else is the data type
231
232        # if name is a pointer (e.g. "*ptr"), then remove the "*" from the name
233        # and add it to the data type
234        pointersInName = name.count("*")
235        if pointersInName > 0:
236            name = name.replace("*", "")
237            dataType += "*" * pointersInName
238
239        # if name is an array (e.g. "array[10]"), then remove the "[X]" from the name
240        # and make the datatype to be a pointer
241        arraysInName = name.count("[")
242        if arraysInName > 0:
243            name = name.split('[')[0]
244            dataType += "*"
245    else:
246        dataType = elements[0]
247    return (name, getDataTypeFromKw(dataType))
248
249def parseArgs(arglist):
250    """ Parse the argument list into a list of (var name, DataType) tuples """
251    args = arglist.split(',')
252    args = map(lambda x: x.strip(), args)    # remove unnecessary whitespaces
253    argtypelist = map(getNameTypePair, args) # split arg into arg type and arg name
254    if len(argtypelist) == 1:
255        (name, argtype) = argtypelist[0]
256        if argtype == DataType.VOID:
257            return []
258
259    return argtypelist
260
261class ApiCall(object):
262    """An ApiCall models all information about a single OpenGL API"""
263
264    # Regex to match API_ENTRY specification:
265    #       e.g. void API_ENTRY(glActiveTexture)(GLenum texture) {
266    # the regex uses a non greedy match (?) to match the first closing paren
267    API_ENTRY_REGEX = "(.*)API_ENTRY\(.*?\)\((.*?)\)"
268
269    # Regex to match CALL_GL_API specification:
270    #       e.g. CALL_GL_API(glCullFace, mode);
271    #            CALL_GL_API_RETURN(glCreateProgram);
272    CALL_GL_API_REGEX = "CALL_GL_API(_RETURN)?\((.*)\);"
273
274    def __init__(self, prefix, apientry, callsite):
275        """Construct an ApiCall from its specification.
276
277        The specification is provided by the two arguments:
278        prefix: prefix to use for function names
279        defn: specification line containing API_ENTRY macro
280              e.g: void API_ENTRY(glActiveTexture)(GLenum texture) {
281        callsite: specification line containing CALL_GL_API macro
282              e.g: CALL_GL_API(glActiveTexture, texture);
283        """
284        self.prefix = prefix
285        self.ret = self.getReturnType(apientry)
286        self.arglist = self.getArgList(apientry)
287
288        # some functions (e.g. __glEGLImageTargetRenderbufferStorageOES), define their
289        # names one way in the API_ENTRY and another way in the CALL_GL_API macros.
290        # so self.func is reassigned based on what is there in the call site
291        self.func = self.getFunc(callsite)
292        self.callsite = self.getCallSite(callsite)
293
294    def getReturnType(self, apientry):
295        '''Extract the return type from the API_ENTRY specification'''
296        m = re.search(self.API_ENTRY_REGEX, apientry)
297        if not m:
298            raise ValueError("%s does not match API_ENTRY specification %s"
299                             % (apientry, self.API_ENTRY_REGEX))
300
301        return m.group(1).strip()
302
303    def getArgList(self, apientry):
304        '''Extract the argument list from the API_ENTRY specification'''
305        m = re.search(self.API_ENTRY_REGEX, apientry)
306        if not m:
307            raise ValueError("%s does not match API_ENTRY specification %s"
308                             % (apientry, self.API_ENTRY_REGEX))
309
310        return m.group(2).strip()
311
312    def parseCallSite(self, callsite):
313        m = re.search(self.CALL_GL_API_REGEX, callsite)
314        if not m:
315            raise ValueError("%s does not match CALL_GL_API specification (%s)"
316                             % (callsite, self.CALL_GL_API_REGEX))
317
318        arglist = m.group(2)
319        args = arglist.split(',')
320        args = map(lambda x: x.strip(), args)
321
322        return args
323
324    def getCallSite(self, callsite):
325        '''Extract the callsite from the CALL_GL_API specification'''
326        args = self.parseCallSite(callsite)
327        return "%s(%s)" % (args[0], ", ".join(args[1:]))
328
329    def getFunc(self, callsite):
330        '''Extract the function name from the CALL_GL_API specification'''
331        args = self.parseCallSite(callsite)
332        return args[0]
333
334    def genDeclaration(self):
335        return "%s GLTrace_%s(%s);" % (self.ret, self.func, self.arglist)
336
337    def genCode(self):
338        return TRACE_CALL_TEMPLATE(func = self.func,
339                                   retType = self.ret,
340                                   retDataType = getDataTypeFromKw(self.ret),
341                                   inputArgList = self.arglist,
342                                   callsite = self.callsite,
343                                   parsedArgs = parseArgs(self.arglist),
344                                   DataType=DataType)
345
346def getApis(apiEntryFile, prefix):
347    '''Get a list of all ApiCalls in provided specification file'''
348    lines = open(apiEntryFile).readlines()
349
350    apis = []
351    for i in range(0, len(lines)/3):
352        apis.append(ApiCall(prefix, lines[i*3], lines[i*3+1]))
353
354    return apis
355
356def parseAllSpecs(specs):
357    apis = []
358    for name, specfile in specs:
359        a = getApis(specfile, name)
360        print 'Parsed %s APIs from %s, # of entries = %d' % (name, specfile, len(a))
361        apis.extend(a)
362    return apis
363
364def removeDuplicates(apis):
365    '''Remove all duplicate function entries.
366
367    The input list contains functions declared in GL1, GL2, and GL3 APIs.
368    This will return a list that contains only the first function if there are
369    multiple functions with the same name.'''
370    uniqs = []
371    funcs = set()
372    for api in apis:
373        if api.func not in funcs:
374            uniqs.append(api)
375            funcs.add(api.func)
376
377    return uniqs
378
379def genHeaders(apis, fname):
380    lines = []
381    lines.append(HEADER_LICENSE)
382    lines.append(HEADER_NAMESPACE_START)
383    prefix = ""
384    for api in apis:
385        if prefix != api.prefix:
386            lines.append("\n// Declarations for %s APIs\n\n" % api.prefix)
387            prefix = api.prefix
388        lines.append(api.genDeclaration())
389        lines.append("\n")
390    lines.append(FOOTER_TEXT)
391
392    with open(fname, "w") as f:
393        f.writelines(lines)
394
395def genSrcs(apis, fname):
396    lines = []
397    lines.append(HEADER_LICENSE)
398    lines.append(HEADER_INCLUDES)
399    lines.append(HEADER_NAMESPACE_START)
400    prefix = ""
401    for api in apis:
402        if prefix != api.prefix:
403            lines.append("\n// Definitions for %s APIs\n\n" % api.prefix)
404            prefix = api.prefix
405        lines.append(api.genCode())
406        lines.append("\n")
407    lines.append(FOOTER_TEXT)
408
409    with open(fname, "w") as f:
410        f.writelines(lines)
411
412if __name__ == '__main__':
413    apis = parseAllSpecs(API_SPECS)    # read in all the specfiles
414    apis = removeDuplicates(apis)      # remove duplication of functions common to multiple versions
415    genHeaders(apis, 'gltrace_api.h')  # generate header file
416    genSrcs(apis, 'gltrace_api.cpp')   # generate source file
417