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