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