2011-11-30 23:05:37 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# Copyright (C) 2011 Google Inc.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
#
|
|
|
|
# ABOUT
|
|
|
|
# This script is used to generate the trace implementations of all
|
|
|
|
# OpenGL calls. When executed, it reads the specs for the OpenGL calls
|
|
|
|
# from the files GLES2/gl2_api.in, GLES2/gl2ext_api.in, GLES_CM/gl_api.in,
|
2014-05-20 21:38:44 +00:00
|
|
|
# and GLES_CM/glext_api.in, and generates trace versions for all the
|
2011-11-30 23:05:37 +00:00
|
|
|
# defined functions.
|
|
|
|
#
|
|
|
|
# PREREQUISITES
|
|
|
|
# To generate C++ files, this script uses the 'pyratemp' template
|
|
|
|
# module. The only reason to use pyratemp is that it is extremly
|
|
|
|
# simple to install:
|
2014-01-28 22:08:46 +00:00
|
|
|
# $ wget http://www.simple-is-better.org/template/pyratemp-0.3.2.tgz
|
|
|
|
# Extract and put the pyratemp.py file in the GLES_trace/tools folder,
|
|
|
|
# or update PYTHONPATH to point to wherever it was downloaded.
|
2011-11-30 23:05:37 +00:00
|
|
|
#
|
|
|
|
# USAGE
|
2012-01-28 22:20:59 +00:00
|
|
|
# $ cd GLES_trace - run the program from GLES2_trace folder
|
2011-11-30 23:05:37 +00:00
|
|
|
# $ ./tools/genapi.py - generates a .cpp and .h file
|
|
|
|
# $ mv *.cpp *.h src/ - move the generated files into the src folder
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import re
|
|
|
|
import pyratemp
|
|
|
|
|
|
|
|
# Constants corresponding to the protobuf DataType.Type
|
|
|
|
class DataType:
|
|
|
|
def __init__(self, name):
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
def __str__(self):
|
2014-01-28 22:08:46 +00:00
|
|
|
if self.name == "pointer": # pointers map to the INT64 DataType
|
|
|
|
return "INT64"
|
2011-11-30 23:05:37 +00:00
|
|
|
return self.name.upper()
|
|
|
|
|
|
|
|
def getProtobufCall(self):
|
|
|
|
if self.name == "void":
|
|
|
|
raise ValueError("Attempt to set void value")
|
|
|
|
elif self.name == "char" or self.name == "byte" \
|
2014-01-28 22:08:46 +00:00
|
|
|
or self.name == "enum":
|
2011-11-30 23:05:37 +00:00
|
|
|
return "add_intvalue((int)"
|
2014-01-28 22:08:46 +00:00
|
|
|
elif self.name == "pointer":
|
|
|
|
return "add_int64value((uintptr_t)"
|
2011-11-30 23:05:37 +00:00
|
|
|
elif self.name == "int":
|
|
|
|
return "add_intvalue("
|
|
|
|
elif self.name == "float":
|
|
|
|
return "add_floatvalue("
|
|
|
|
elif self.name == "bool":
|
|
|
|
return "add_boolvalue("
|
2013-02-08 19:13:46 +00:00
|
|
|
elif self.name == "int64":
|
|
|
|
return "add_int64value("
|
2011-11-30 23:05:37 +00:00
|
|
|
else:
|
|
|
|
raise ValueError("Unknown value type %s" % self.name)
|
|
|
|
|
|
|
|
DataType.VOID = DataType("void")
|
|
|
|
DataType.CHAR = DataType("char")
|
|
|
|
DataType.BYTE = DataType("byte")
|
|
|
|
DataType.ENUM = DataType("enum")
|
|
|
|
DataType.BOOL = DataType("bool")
|
|
|
|
DataType.INT = DataType("int")
|
|
|
|
DataType.FLOAT = DataType("float")
|
|
|
|
DataType.POINTER = DataType("pointer")
|
2013-02-08 19:13:46 +00:00
|
|
|
DataType.INT64 = DataType("int64")
|
2011-11-30 23:05:37 +00:00
|
|
|
|
|
|
|
# mapping of GL types to protobuf DataType
|
2013-02-08 19:13:46 +00:00
|
|
|
GLPROTOBUF_TYPE_MAP = {
|
2011-11-30 23:05:37 +00:00
|
|
|
"GLvoid":DataType.VOID,
|
|
|
|
"void":DataType.VOID,
|
|
|
|
"GLchar":DataType.CHAR,
|
|
|
|
"GLenum":DataType.ENUM,
|
|
|
|
"GLboolean":DataType.BOOL,
|
|
|
|
"GLbitfield":DataType.INT,
|
|
|
|
"GLbyte":DataType.BYTE,
|
|
|
|
"GLshort":DataType.INT,
|
|
|
|
"GLint":DataType.INT,
|
|
|
|
"int":DataType.INT,
|
|
|
|
"GLsizei":DataType.INT,
|
|
|
|
"GLubyte":DataType.BYTE,
|
|
|
|
"GLushort":DataType.INT,
|
|
|
|
"GLuint":DataType.INT,
|
|
|
|
"GLfloat":DataType.FLOAT,
|
|
|
|
"GLclampf":DataType.FLOAT,
|
|
|
|
"GLfixed":DataType.INT,
|
|
|
|
"GLclampx":DataType.INT,
|
2012-02-27 20:02:47 +00:00
|
|
|
"GLsizeiptr":DataType.INT,
|
|
|
|
"GLintptr":DataType.INT,
|
2011-11-30 23:05:37 +00:00
|
|
|
"GLeglImageOES":DataType.POINTER,
|
2013-02-08 19:13:46 +00:00
|
|
|
"GLint64":DataType.INT64,
|
|
|
|
"GLuint64":DataType.INT64,
|
|
|
|
"GLsync":DataType.POINTER,
|
2011-11-30 23:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
API_SPECS = [
|
|
|
|
('GL2','../GLES2/gl2_api.in'),
|
|
|
|
('GL2Ext','../GLES2/gl2ext_api.in'),
|
|
|
|
('GL1','../GLES_CM/gl_api.in'),
|
|
|
|
('GL1Ext','../GLES_CM/glext_api.in'),
|
|
|
|
]
|
|
|
|
|
2011-12-16 01:32:05 +00:00
|
|
|
HEADER_LICENSE = """/*
|
2011-11-30 23:05:37 +00:00
|
|
|
* Copyright 2011, The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
|
|
|
* THIS FILE WAS GENERATED BY A SCRIPT. DO NOT EDIT.
|
|
|
|
*/
|
2011-12-16 01:32:05 +00:00
|
|
|
"""
|
2011-11-30 23:05:37 +00:00
|
|
|
|
2011-12-16 01:32:05 +00:00
|
|
|
HEADER_INCLUDES = """
|
2011-11-30 23:05:37 +00:00
|
|
|
#include <cutils/log.h>
|
2011-12-16 01:32:05 +00:00
|
|
|
#include <utils/Timers.h>
|
2013-02-08 19:13:46 +00:00
|
|
|
#include <GLES3/gl3.h>
|
2011-11-30 23:05:37 +00:00
|
|
|
|
|
|
|
#include "gltrace.pb.h"
|
|
|
|
#include "gltrace_context.h"
|
|
|
|
#include "gltrace_fixup.h"
|
|
|
|
#include "gltrace_transport.h"
|
2011-12-16 01:32:05 +00:00
|
|
|
"""
|
2011-11-30 23:05:37 +00:00
|
|
|
|
2011-12-16 01:32:05 +00:00
|
|
|
HEADER_NAMESPACE_START = """
|
2011-11-30 23:05:37 +00:00
|
|
|
namespace android {
|
|
|
|
namespace gltrace {
|
|
|
|
"""
|
|
|
|
|
|
|
|
FOOTER_TEXT = """
|
|
|
|
}; // namespace gltrace
|
|
|
|
}; // namespace android
|
|
|
|
"""
|
|
|
|
|
|
|
|
TRACE_CALL_TEMPLATE = pyratemp.Template(
|
|
|
|
"""$!retType!$ GLTrace_$!func!$($!inputArgList!$) {
|
|
|
|
GLMessage glmsg;
|
|
|
|
GLTraceContext *glContext = getGLTraceContext();
|
|
|
|
|
|
|
|
glmsg.set_function(GLMessage::$!func!$);
|
|
|
|
<!--(if len(parsedArgs) > 0)-->
|
|
|
|
<!--(for argname, argtype in parsedArgs)-->
|
|
|
|
|
|
|
|
// copy argument $!argname!$
|
|
|
|
GLMessage_DataType *arg_$!argname!$ = glmsg.add_args();
|
|
|
|
arg_$!argname!$->set_isarray(false);
|
|
|
|
arg_$!argname!$->set_type(GLMessage::DataType::$!argtype!$);
|
|
|
|
arg_$!argname!$->$!argtype.getProtobufCall()!$$!argname!$);
|
|
|
|
<!--(end)-->
|
|
|
|
<!--(end)-->
|
|
|
|
|
|
|
|
// call function
|
2012-02-17 18:55:03 +00:00
|
|
|
nsecs_t wallStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
|
|
nsecs_t threadStartTime = systemTime(SYSTEM_TIME_THREAD);
|
2011-11-30 23:05:37 +00:00
|
|
|
<!--(if retType != "void")-->
|
|
|
|
$!retType!$ retValue = glContext->hooks->gl.$!callsite!$;
|
|
|
|
<!--(else)-->
|
|
|
|
glContext->hooks->gl.$!callsite!$;
|
|
|
|
<!--(end)-->
|
2012-02-17 18:55:03 +00:00
|
|
|
nsecs_t threadEndTime = systemTime(SYSTEM_TIME_THREAD);
|
|
|
|
nsecs_t wallEndTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
2011-11-30 23:05:37 +00:00
|
|
|
<!--(if retType != "void")-->
|
|
|
|
|
|
|
|
// set return value
|
|
|
|
GLMessage_DataType *rt = glmsg.mutable_returnvalue();
|
|
|
|
rt->set_isarray(false);
|
|
|
|
rt->set_type(GLMessage::DataType::$!retDataType!$);
|
|
|
|
rt->$!retDataType.getProtobufCall()!$retValue);
|
|
|
|
<!--(end)-->
|
|
|
|
|
2012-02-27 20:02:47 +00:00
|
|
|
void *pointerArgs[] = {
|
|
|
|
<!--(for argname, argtype in parsedArgs)-->
|
|
|
|
<!--(if argtype == DataType.POINTER)-->
|
|
|
|
(void *) $!argname!$,
|
|
|
|
<!--(end)-->
|
|
|
|
<!--(end)-->
|
|
|
|
<!--(if retDataType == DataType.POINTER)-->
|
|
|
|
(void *) retValue,
|
|
|
|
<!--(end)-->
|
|
|
|
};
|
|
|
|
|
2012-02-17 18:55:03 +00:00
|
|
|
fixupGLMessage(glContext, wallStartTime, wallEndTime,
|
|
|
|
threadStartTime, threadEndTime,
|
2012-02-27 20:02:47 +00:00
|
|
|
&glmsg, pointerArgs);
|
2011-12-14 20:19:56 +00:00
|
|
|
glContext->traceGLMessage(&glmsg);
|
2011-11-30 23:05:37 +00:00
|
|
|
<!--(if retType != "void")-->
|
|
|
|
|
|
|
|
return retValue;
|
|
|
|
<!--(end)-->
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
|
|
|
|
def getDataTypeFromKw(kw):
|
|
|
|
""" Get the data type given declaration.
|
|
|
|
All pointer declarations are of type DataType.POINTER
|
|
|
|
|
|
|
|
e.g.: GLvoid -> DataType.VOID"""
|
|
|
|
|
|
|
|
if kw.count('*') > 0:
|
|
|
|
return DataType.POINTER
|
2013-02-08 19:13:46 +00:00
|
|
|
return GLPROTOBUF_TYPE_MAP.get(kw)
|
2011-11-30 23:05:37 +00:00
|
|
|
|
|
|
|
def getNameTypePair(decl):
|
|
|
|
""" Split declaration of a variable to a tuple of (variable name, DataType).
|
|
|
|
e.g. "const GLChar* varName" -> (varName, POINTER) """
|
|
|
|
elements = decl.strip().split(' ')
|
|
|
|
name = None
|
|
|
|
if len(elements) > 1:
|
|
|
|
name = " ".join(elements[-1:]).strip() # last element is the name
|
|
|
|
dataType = " ".join(elements[:-1]).strip() # everything else is the data type
|
|
|
|
|
|
|
|
# if name is a pointer (e.g. "*ptr"), then remove the "*" from the name
|
|
|
|
# and add it to the data type
|
2014-05-20 21:38:44 +00:00
|
|
|
pointersInName = name.count("*")
|
2011-11-30 23:05:37 +00:00
|
|
|
if pointersInName > 0:
|
|
|
|
name = name.replace("*", "")
|
|
|
|
dataType += "*" * pointersInName
|
|
|
|
|
|
|
|
# if name is an array (e.g. "array[10]"), then remove the "[X]" from the name
|
|
|
|
# and make the datatype to be a pointer
|
|
|
|
arraysInName = name.count("[")
|
|
|
|
if arraysInName > 0:
|
|
|
|
name = name.split('[')[0]
|
|
|
|
dataType += "*"
|
|
|
|
else:
|
|
|
|
dataType = elements[0]
|
|
|
|
return (name, getDataTypeFromKw(dataType))
|
|
|
|
|
|
|
|
def parseArgs(arglist):
|
|
|
|
""" Parse the argument list into a list of (var name, DataType) tuples """
|
|
|
|
args = arglist.split(',')
|
|
|
|
args = map(lambda x: x.strip(), args) # remove unnecessary whitespaces
|
|
|
|
argtypelist = map(getNameTypePair, args) # split arg into arg type and arg name
|
|
|
|
if len(argtypelist) == 1:
|
|
|
|
(name, argtype) = argtypelist[0]
|
|
|
|
if argtype == DataType.VOID:
|
|
|
|
return []
|
|
|
|
|
|
|
|
return argtypelist
|
|
|
|
|
|
|
|
class ApiCall(object):
|
|
|
|
"""An ApiCall models all information about a single OpenGL API"""
|
|
|
|
|
|
|
|
# Regex to match API_ENTRY specification:
|
|
|
|
# e.g. void API_ENTRY(glActiveTexture)(GLenum texture) {
|
|
|
|
# the regex uses a non greedy match (?) to match the first closing paren
|
|
|
|
API_ENTRY_REGEX = "(.*)API_ENTRY\(.*?\)\((.*?)\)"
|
|
|
|
|
|
|
|
# Regex to match CALL_GL_API specification:
|
2014-05-20 21:38:44 +00:00
|
|
|
# e.g. CALL_GL_API(glCullFace, mode);
|
2011-11-30 23:05:37 +00:00
|
|
|
# CALL_GL_API_RETURN(glCreateProgram);
|
|
|
|
CALL_GL_API_REGEX = "CALL_GL_API(_RETURN)?\((.*)\);"
|
|
|
|
|
|
|
|
def __init__(self, prefix, apientry, callsite):
|
|
|
|
"""Construct an ApiCall from its specification.
|
|
|
|
|
|
|
|
The specification is provided by the two arguments:
|
|
|
|
prefix: prefix to use for function names
|
|
|
|
defn: specification line containing API_ENTRY macro
|
|
|
|
e.g: void API_ENTRY(glActiveTexture)(GLenum texture) {
|
|
|
|
callsite: specification line containing CALL_GL_API macro
|
2014-05-20 21:38:44 +00:00
|
|
|
e.g: CALL_GL_API(glActiveTexture, texture);
|
2011-11-30 23:05:37 +00:00
|
|
|
"""
|
|
|
|
self.prefix = prefix
|
|
|
|
self.ret = self.getReturnType(apientry)
|
|
|
|
self.arglist = self.getArgList(apientry)
|
|
|
|
|
|
|
|
# some functions (e.g. __glEGLImageTargetRenderbufferStorageOES), define their
|
|
|
|
# names one way in the API_ENTRY and another way in the CALL_GL_API macros.
|
|
|
|
# so self.func is reassigned based on what is there in the call site
|
|
|
|
self.func = self.getFunc(callsite)
|
|
|
|
self.callsite = self.getCallSite(callsite)
|
|
|
|
|
|
|
|
def getReturnType(self, apientry):
|
|
|
|
'''Extract the return type from the API_ENTRY specification'''
|
|
|
|
m = re.search(self.API_ENTRY_REGEX, apientry)
|
|
|
|
if not m:
|
2014-05-20 21:38:44 +00:00
|
|
|
raise ValueError("%s does not match API_ENTRY specification %s"
|
2011-11-30 23:05:37 +00:00
|
|
|
% (apientry, self.API_ENTRY_REGEX))
|
|
|
|
|
|
|
|
return m.group(1).strip()
|
|
|
|
|
|
|
|
def getArgList(self, apientry):
|
|
|
|
'''Extract the argument list from the API_ENTRY specification'''
|
|
|
|
m = re.search(self.API_ENTRY_REGEX, apientry)
|
|
|
|
if not m:
|
2014-05-20 21:38:44 +00:00
|
|
|
raise ValueError("%s does not match API_ENTRY specification %s"
|
2011-11-30 23:05:37 +00:00
|
|
|
% (apientry, self.API_ENTRY_REGEX))
|
|
|
|
|
|
|
|
return m.group(2).strip()
|
|
|
|
|
|
|
|
def parseCallSite(self, callsite):
|
|
|
|
m = re.search(self.CALL_GL_API_REGEX, callsite)
|
|
|
|
if not m:
|
|
|
|
raise ValueError("%s does not match CALL_GL_API specification (%s)"
|
|
|
|
% (callsite, self.CALL_GL_API_REGEX))
|
|
|
|
|
|
|
|
arglist = m.group(2)
|
|
|
|
args = arglist.split(',')
|
|
|
|
args = map(lambda x: x.strip(), args)
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
def getCallSite(self, callsite):
|
|
|
|
'''Extract the callsite from the CALL_GL_API specification'''
|
|
|
|
args = self.parseCallSite(callsite)
|
|
|
|
return "%s(%s)" % (args[0], ", ".join(args[1:]))
|
|
|
|
|
|
|
|
def getFunc(self, callsite):
|
|
|
|
'''Extract the function name from the CALL_GL_API specification'''
|
|
|
|
args = self.parseCallSite(callsite)
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
def genDeclaration(self):
|
|
|
|
return "%s GLTrace_%s(%s);" % (self.ret, self.func, self.arglist)
|
|
|
|
|
|
|
|
def genCode(self):
|
2014-05-20 21:38:44 +00:00
|
|
|
return TRACE_CALL_TEMPLATE(func = self.func,
|
2011-11-30 23:05:37 +00:00
|
|
|
retType = self.ret,
|
|
|
|
retDataType = getDataTypeFromKw(self.ret),
|
|
|
|
inputArgList = self.arglist,
|
|
|
|
callsite = self.callsite,
|
|
|
|
parsedArgs = parseArgs(self.arglist),
|
|
|
|
DataType=DataType)
|
|
|
|
|
|
|
|
def getApis(apiEntryFile, prefix):
|
|
|
|
'''Get a list of all ApiCalls in provided specification file'''
|
|
|
|
lines = open(apiEntryFile).readlines()
|
|
|
|
|
|
|
|
apis = []
|
|
|
|
for i in range(0, len(lines)/3):
|
|
|
|
apis.append(ApiCall(prefix, lines[i*3], lines[i*3+1]))
|
|
|
|
|
|
|
|
return apis
|
|
|
|
|
|
|
|
def parseAllSpecs(specs):
|
|
|
|
apis = []
|
|
|
|
for name, specfile in specs:
|
|
|
|
a = getApis(specfile, name)
|
|
|
|
print 'Parsed %s APIs from %s, # of entries = %d' % (name, specfile, len(a))
|
|
|
|
apis.extend(a)
|
|
|
|
return apis
|
|
|
|
|
|
|
|
def removeDuplicates(apis):
|
|
|
|
'''Remove all duplicate function entries.
|
|
|
|
|
2013-02-08 19:13:46 +00:00
|
|
|
The input list contains functions declared in GL1, GL2, and GL3 APIs.
|
2011-11-30 23:05:37 +00:00
|
|
|
This will return a list that contains only the first function if there are
|
|
|
|
multiple functions with the same name.'''
|
|
|
|
uniqs = []
|
|
|
|
funcs = set()
|
|
|
|
for api in apis:
|
|
|
|
if api.func not in funcs:
|
|
|
|
uniqs.append(api)
|
|
|
|
funcs.add(api.func)
|
|
|
|
|
|
|
|
return uniqs
|
|
|
|
|
|
|
|
def genHeaders(apis, fname):
|
|
|
|
lines = []
|
2011-12-16 01:32:05 +00:00
|
|
|
lines.append(HEADER_LICENSE)
|
|
|
|
lines.append(HEADER_NAMESPACE_START)
|
2011-11-30 23:05:37 +00:00
|
|
|
prefix = ""
|
|
|
|
for api in apis:
|
|
|
|
if prefix != api.prefix:
|
|
|
|
lines.append("\n// Declarations for %s APIs\n\n" % api.prefix)
|
|
|
|
prefix = api.prefix
|
|
|
|
lines.append(api.genDeclaration())
|
|
|
|
lines.append("\n")
|
|
|
|
lines.append(FOOTER_TEXT)
|
|
|
|
|
|
|
|
with open(fname, "w") as f:
|
|
|
|
f.writelines(lines)
|
|
|
|
|
|
|
|
def genSrcs(apis, fname):
|
|
|
|
lines = []
|
2011-12-16 01:32:05 +00:00
|
|
|
lines.append(HEADER_LICENSE)
|
|
|
|
lines.append(HEADER_INCLUDES)
|
|
|
|
lines.append(HEADER_NAMESPACE_START)
|
2011-11-30 23:05:37 +00:00
|
|
|
prefix = ""
|
|
|
|
for api in apis:
|
|
|
|
if prefix != api.prefix:
|
|
|
|
lines.append("\n// Definitions for %s APIs\n\n" % api.prefix)
|
|
|
|
prefix = api.prefix
|
|
|
|
lines.append(api.genCode())
|
|
|
|
lines.append("\n")
|
|
|
|
lines.append(FOOTER_TEXT)
|
|
|
|
|
|
|
|
with open(fname, "w") as f:
|
|
|
|
f.writelines(lines)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2013-02-08 19:13:46 +00:00
|
|
|
apis = parseAllSpecs(API_SPECS) # read in all the specfiles
|
|
|
|
apis = removeDuplicates(apis) # remove duplication of functions common to multiple versions
|
2011-11-30 23:05:37 +00:00
|
|
|
genHeaders(apis, 'gltrace_api.h') # generate header file
|
|
|
|
genSrcs(apis, 'gltrace_api.cpp') # generate source file
|