blob: 280932e72c8bcf0d1775edef407955d8db644828 [file]
/// Consumes "gl2.h" header file and generates bindings for libgl_extension.so
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
String outPath;
main(List<String> args) async {
var parser = new ArgParser()
..addOption('gl_path',
help: 'path to directory containing gl2.h',
abbr: 'g',
valueHelp: 'path')
..addOption('out',
help: 'path to output directory', abbr: 'o', valueHelp: 'path')
..addOption('whitelist',
help: 'list of functions that are bound',
abbr: 'w',
valueHelp: 'whitelist.txt')
..addFlag('help', abbr: 'h', negatable: false);
var results = parser.parse(args);
if (results.wasParsed('help')) {
stdout.writeln(parser.usage);
exit(0);
}
toErr(String msg, [int exitVal = 1]) {
stderr..writeln(msg)..writeln(parser.usage);
exit(exitVal);
}
if (!results.wasParsed('gl_path')) {
toErr('error: --gl_path must be provided');
}
var glPath = results['gl_path'];
outPath = '.';
if (results.wasParsed('out')) {
outPath = results['out'];
}
var generated = await new Directory('$outPath/generated').create();
outPath = generated.path;
var calls = [];
var consts = <String, CConst>{};
var whitelist;
if (results.wasParsed('whitelist')) {
var lines = await new File(results['whitelist']).readAsLines();
whitelist = lines.map((l) => l.trim()).toList();
}
for (var file in ['gl2.h', 'gl2ext.h']) {
var readStream = new File(path.join(glPath, file)).openRead();
await for (var line
in readStream.transform(UTF8.decoder).transform(new LineSplitter())) {
if (line.startsWith('#define GL_')) {
var match = CConst.defineReg.firstMatch(line);
if (match == null) {
print("bad define: $line");
continue;
}
consts.putIfAbsent(match[1], () => new CConst(match[1], match[2]));
} else if (line.startsWith('GL_APICALL ')) {
calls.add(line
.replaceAll('GL_APICALL ', '')
.replaceAll('GL_APIENTRY ', '')
.trim());
}
}
}
var decls = <CDecl>[];
for (var call in calls) {
var decl = new CDecl(call);
if (whitelist != null && !whitelist.contains(decl.name)) {
print("non-whitelisted decl skipped: $decl");
continue;
}
decls.add(decl);
}
writeFunctionListH(decls);
writeFunctionListC(decls);
writeGlBindingsH(decls);
writeGlBindingsC(decls);
writeGlConstantsDart(consts.values);
writeGlNativeFunctions(decls);
}
const String copyright = '''
// Copyright (c) 2015, the Dart GL extension authors. All rights reserved.
// Please see the AUTHORS file for details. Use of this source code is governed
// by a BSD-style license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// This file is auto-generated by scripts in the tools/ directory.
''';
/// Maps C types to Dart types.
const typeMap = const <String, String>{
"const GLubyte*": "String",
"const GLvoid*": "TypedData",
"const void*": "TypedData",
"GLenum": "int",
"GLint": "int",
"GLfloat": "double",
"GLclampf": "double",
"const GLchar*": "String",
"GLboolean": "int",
"GLuint": "int",
"GLsizei": "int",
"GLsizeiptr": "int",
"GLintptr": "int",
"GLbitfield": "int",
"int": "int",
"bool": "int",
"void": "void",
"const char*": "String",
"double": "double",
"float": "double",
};
/// If the return type is "GLboolean", return bool back to dart code as
/// conditional statements expect boolean evaluation in Dart.
const returnTypeOverride = const <String, String>{
"GLboolean": "bool",
};
/// Maps special GL API arguments to Dart types.
///
/// NOTE: This is **very** brittle.
const argumentTypeHint = const <String, String>{
// for glUniform*iv
"const GLint* value": "TypedData",
"const GLint* v": "TypedData",
"const GLuint* arrays": "TypedData",
// for glUniform*fv
"const GLfloat* value": "TypedData",
// for glVertexAttrib*fv
"const GLfloat* v": "TypedData",
"const GLfloat* values": "TypedData",
};
writeFunctionListH(List<CDecl> decls) {
new File('$outPath/function_list.h').writeAsString('''
$copyright
#ifndef DART_GL_LIB_SRC_GENERATED_FUNCTION_LIST_H_
#define DART_GL_LIB_SRC_GENERATED_FUNCTION_LIST_H_
#include "dart_api.h"
struct FunctionLookup {
const char* name;
Dart_NativeFunction function;
};
extern const struct FunctionLookup *function_list;
#endif // DART_GL_LIB_SRC_GENERATED_FUNCTION_LIST_H_
''');
}
writeGlConstantsDart(Iterable<CConst> consts) {
new File('$outPath/gl_constants.dart').openWrite()
..write(copyright)
..writeln('\n// Generated GL constants.')
..writeAll(consts, '\n')
..close();
}
writeGlNativeFunctions(List<CDecl> decls) {
var sink = new File('$outPath/gl_native_functions.dart').openWrite()
..write(copyright)
..writeln()
..writeln('/// Dart definitions for GL native extension.')
..writeln('part of gl;')
..writeln();
for (var decl in decls) {
if (decl.hasManualBinding || decl.needsManualBinding) continue;
sink.write('${decl.dartReturnType} ${decl.name}');
if (decl.dartArguments.isEmpty || decl.arguments.first.right == "void") {
sink.write('()');
} else {
sink.write('(${decl.dartArguments.join(', ')})');
}
sink.writeln(' native "${decl.name}";');
}
sink.close();
}
writeFunctionListC(List<CDecl> decls) {
functionListLine(CDecl c) => ' '
'${c.needsManualBinding && !c.hasManualBinding ? "// " : ""}'
'{"${c.name}", ${c.nativeName}},';
new File('$outPath/function_list.cc').openWrite()
..write(copyright)
..write('''
#include <stdlib.h>
#include "../manual_bindings.h"
#include "function_list.h"
#include "gl_bindings.h"
// function_list is used by ResolveName in lib/src/gl_extension.cc.
const struct FunctionLookup _function_list[] = {
''')
..write(decls.map(functionListLine).join('\n'))
..writeln()
..write('''
{NULL, NULL}};
// This prevents the compiler from complaining about initializing improperly.
const struct FunctionLookup *function_list = _function_list;
''')
..close();
}
writeGlBindingsH(List<CDecl> decls) {
var sink = new File('$outPath/gl_bindings.h').openWrite()
..write(copyright)
..write('''
#ifndef DART_GL_LIB_SRC_GENERATED_GENERATED_BINDINGS_H_
#define DART_GL_LIB_SRC_GENERATED_GENERATED_BINDINGS_H_
#include "dart_api.h"
''');
sink
..writeln()
..writeln('// Header file for generated GL function bindings.')
..writeln()
..writeln(decls
.where((d) => !d.hasManualBinding && !d.needsManualBinding)
.map((d) => 'void ${d.nativeName}(Dart_NativeArguments arguments);')
.join('\n'))
..writeln()
..write('#endif // DART_GL_LIB_SRC_GENERATED_GENERATED_BINDINGS_H_')
..writeln()
..close();
}
writeGlBindingsC(List<CDecl> decls) {
var sink = new File('$outPath/gl_bindings.cc').openWrite();
sink..write(copyright)..write('''
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include "../util.h"
#include "gl_bindings.h"
// Generated GL function bindings for Dart.
''');
// Create a binding function for each declaration in the GL header file.
for (var decl in decls) {
// If the function has a manual binding, or we've detected that it needs
// one, skip it.
if (decl.needsManualBinding || decl.hasManualBinding) continue;
// Write the first line (return type, name, and arguments).
sink
..writeln('void ${decl.nativeName}(Dart_NativeArguments arguments) {')
..writeln('TRACE_START(${decl.name}_);');
// For each argument, generate the code needed to extract it from the
// Dart_NativeArguments structure.
int i = 0;
var typed = [];
var arguments = [];
if (decl.isGenerator) {
var arg = decl.arguments[0];
var dartArg = decl.dartArguments[0];
sink.writeln(dartTypeToArg[dartArg.left](arg, i));
arguments.add(arg.right);
if (dartArg.left == 'TypedData') {
typed.add(arg);
}
} else if (decl.isDeleter) {
sink.writeln('Dart_Handle values_obj = '
'HANDLE(Dart_GetNativeArgument(arguments, 0));');
} else {
for (var arg in decl.arguments) {
if (arg.right == "void") continue;
var dartArg = decl.dartArguments[i];
if (dartTypeToArg[dartArg.left] == null) {
throw "dartTypeToArg($dartArg) is null; $decl";
}
sink.writeln(dartTypeToArg[dartArg.left](arg, i));
arguments.add(arg.right);
if (dartArg.left == 'TypedData') {
typed.add(arg);
}
i++;
}
}
// Be sure to capture the return value from the GL function call, if
// necessary.
var ret = "";
var retHandle = "";
if (decl.returnType != "void") {
ret = '${decl.returnType} ret = ';
retHandle = dartTypeToRet[decl.dartReturnType]();
}
if (decl.isGenerator) {
String count = decl.arguments[0].right;
sink..writeln('''
GLuint *values = static_cast<GLuint *>(malloc(sizeof(GLuint) * $count));
${decl.name}($count, values);
Dart_Handle values_obj = Dart_NewList($count);
for (int i = 0; i < $count; i++) {
Dart_Handle i_obj = HANDLE(Dart_NewInteger(values[i]));
HANDLE(Dart_ListSetAt(values_obj, i, i_obj));
}
Dart_SetReturnValue(arguments, values_obj);
free(values);
''');
} else if (decl.isDeleter) {
sink.writeln('''
GLuint *values = NULL;
intptr_t n = 0;
HANDLE(Dart_ListLength(values_obj, &n));
values = static_cast<GLuint *>(malloc(sizeof(GLuint) * n));
for (int i = 0; i < n; i++) {
Dart_Handle i_obj = HANDLE(Dart_ListGetAt(values_obj, i));
HANDLE(Dart_IntegerToUInt(i_obj, &values[i]));
}
${decl.name}(n, values);
free(values);
''');
} else {
// Generate the actual GL function call, using the native arguments
// extracted above.
ret = '$ret ${decl.name}(${arguments.join(", ")});';
sink..writeln(ret)..writeln(retHandle);
}
// If we acquired any TypedData while processing arguments above, release
// it now.
for (var arg in typed) {
sink.writeln(typedRelease(arg));
}
sink..writeln('TRACE_END(${decl.name}_);')..writeln('}')..writeln();
}
sink.close();
}
typedef DartTypeToC(Pair arg, int index);
typedef DartTypeToRet();
/// Map of Dart argument types to unpacking functions.
final dartTypeToArg = <String, DartTypeToC>{
'int': intToC,
'double': doubleToC,
'String': stringToC,
'bool': boolToC,
'TypedData': typedToC
};
/// Map of Dart return types to packing functions.
final dartTypeToRet = <String, DartTypeToRet>{
'int': intToRet,
'double': doubleToRet,
'String': stringToRet,
'bool': boolToRet,
};
/// Unpacks Dart int arguments to C.
intToC(Pair arg, int index) {
String name = arg.right;
return '''
int64_t $name;
HANDLE(Dart_GetNativeIntegerArgument(arguments, $index, &$name));
''';
}
/// Unpacks Dart double arguments to C.
doubleToC(Pair arg, int index) {
String name = arg.right;
return '''
double $name;
HANDLE(Dart_GetNativeDoubleArgument(arguments, $index, &$name));
''';
}
/// Unpacks Dart bool arguments to C.
boolToC(Pair arg, int index) {
String name = arg.right;
return '''
bool $name;
HANDLE(Dart_GetNativeBooleanArgument(arguments, $index, &$name));
''';
}
/// Unpacks Dart String arguments to C.
stringToC(Pair arg, int index) {
String name = arg.right;
return '''
void* ${name}_peer = NULL;
Dart_Handle ${name}_arg = HANDLE(Dart_GetNativeStringArgument(arguments, $index, (void**)&${name}_peer));
const char *${name};
HANDLE(Dart_StringToCString(${name}_arg, &${name}));
''';
}
/// Unpacks Dart TypedData arguments to C.
///
/// NOTE: Must be freed by calling [typedRelease].
typedToC(Pair arg, int index) {
String name = arg.right;
String type = arg.left;
return '''
Dart_Handle ${name}_obj = HANDLE(Dart_GetNativeArgument(arguments, $index));
void* ${name}_data = nullptr;
Dart_TypedData_Type ${name}_typeddata_type;
intptr_t ${name}_typeddata_length;
if (!Dart_IsNull(${name}_obj)) {
HANDLE(Dart_TypedDataAcquireData(${name}_obj, &${name}_typeddata_type, &${name}_data, &${name}_typeddata_length));
}
$type $name = static_cast<$type>(${name}_data);
''';
}
/// Converts GL int to Dart int for return.
intToRet() => 'Dart_SetIntegerReturnValue(arguments, ret);';
/// Converts GL float to Dart double for return.
doubleToRet() => 'Dart_SetDoubleReturnValue(arguments, ret);';
/// Converts GL boolean to Dart bool for return.
boolToRet() => 'Dart_SetBooleanReturnValue(arguments, ret);';
/// Converts GL strings to Dart String for return.
stringToRet() {
return 'Dart_SetReturnValue(arguments, '
'HANDLE(Dart_NewStringFromCString(reinterpret_cast<const char *>(ret))));';
}
/// Releases unpacked TypedData arguments.
typedRelease(Pair arg) {
String name = arg.right;
String type = arg.left;
return '''
if (!Dart_IsNull(${name}_obj)) {
HANDLE(Dart_TypedDataReleaseData(${name}_obj));
}
''';
}
/// A simple left/right String tuple.
class Pair {
String left;
String right;
Pair(this.left, this.right);
Pair.fromList(List pairs) : this(pairs[0], pairs[1]);
String toString() => '$left $right'.trim();
}
/// C declaration parser which parses an API function declaration from gl2.h
/// into its constituent parts.
class CDecl {
static final ws = new RegExp(r'\s+');
static final comma = new RegExp(r'\s*,\s+');
String name;
String returnType;
List<Pair> arguments = [];
String dartReturnType;
List<Pair> dartArguments = [];
/// Was this function not easily parsed?
bool needsManualBinding = false;
/// Does this function already have a manual binding?
bool get hasManualBinding => manualBindings.contains(name);
/// Removes trailing characters from a String.
static String removeTrailing(String str, int num) =>
str.substring(0, str.length - num);
/// Normalizes pointers to sit with the [type].
///
/// Examples:
/// (int, *hello) -> (int*, hello)
/// (const int, **const*hello) -> (const int**const, hello)
///
/// cdecl.org says the second one means
/// "declare hello as const pointer to pointer to const int"
static List<String> normalizePointer(String type, String name) {
if (name.startsWith('*')) {
return normalizePointer('${type}*', name.substring(1));
} else if (name.startsWith('&')) {
return normalizePointer('${type}&', name.substring(1));
} else if (name.startsWith('const*')) {
return normalizePointer('${type}const*', name.substring(6));
}
return [type, name];
}
static RegExp generatorFunction = new RegExp(r'glGen[A-Z]');
static RegExp deleterFunction = new RegExp(r'glDelete[A-Z]');
CDecl(String string) {
var left = (string.split('(')[0].trim().split(ws)..removeLast()).join(" ");
var right = string.split('(')[0].trim().split(ws).last;
var norms = normalizePointer(left, right);
name = norms[1];
returnType = norms[0];
int noName = 0;
for (var arg
in removeTrailing(string.split('(')[1], 2).trim().split(comma)) {
right = arg.split(ws).last;
left = (arg.split(ws)..removeLast()).join(" ");
if (left == '' && right != "void") {
// API without explicitly named variable
left = right;
right = 'noName${noName++}';
}
arguments.add(new Pair.fromList(normalizePointer(left, right)));
}
if (hasManualBinding) return;
dartReturnType = returnTypeOverride[returnType];
dartReturnType ??= typeMap[returnType];
if (dartReturnType == null) {
needsManualBinding = true;
print("$name RETURN TYPE NEEDS MANUAL BINDING: $returnType");
}
var reason = '';
dartArguments = arguments.map((pair) {
if (pair.right == "void") return new Pair("", "void");
var type = argumentTypeHint['$pair'];
if (type != null) {
return new Pair(type, pair.right);
}
type = typeMap[pair.left];
if (type == null) {
reason = '${reason} Unknown type: ${pair.left}';
needsManualBinding = true;
return new Pair(null, pair.right);
}
return new Pair(type, pair.right);
}).toList();
if (isGenerator) {
dartReturnType = 'List<int>';
dartArguments = [new Pair('int', arguments[0].right)];
needsManualBinding = false;
} else if (isDeleter) {
dartReturnType = 'void';
dartArguments = [new Pair('List<int>', 'values')];
needsManualBinding = false;
}
if (needsManualBinding) {
print("$name NEEDS MANUAL BINDINGS: $string$reason, "
"Discovered: $arguments $dartArguments");
}
}
bool get isGenerator =>
name.startsWith(generatorFunction) &&
arguments.length == 2 &&
typeMap[arguments.first.left] == 'int';
bool get isDeleter =>
name.startsWith(deleterFunction) &&
arguments.length == 2 &&
typeMap[arguments.first.left] == 'int';
String get nativeName => '${name}_native';
String toString() => '$returnType $name(${arguments.join(', ')}); '
'// $dartArguments -> $dartReturnType';
/// These functions have manual bindings defined in lib/src/manual_bindings.cc
static final Set<String> manualBindings = new Set.from([
"glGetActiveAttrib",
"glGetActiveUniform",
"glGetAttachedShaders",
"glGetBooleanv",
"glGetBufferParameteriv",
"glGetFloatv",
"glGetFramebufferAttachmentParameteriv",
"glGetIntegerv",
"glGetProgramiv",
"glGetProgramInfoLog",
"glGetRenderbufferParameteriv",
"glGetShaderiv",
"glGetShaderInfoLog",
"glGetShaderPrecisionFormat",
"glGetShaderSource",
"glGetTexParameterfv",
"glGetTexParameteriv",
"glGetUniformfv",
"glGetUniformiv",
"glGetVertexAttribfv",
"glGetVertexAttribiv",
"glGetVertexAttribPointerv",
"glReadPixels",
//"glShaderBinary",
"glShaderSource",
"glTexParameterfv",
"glTexParameteriv",
"glVertexAttribPointer",
]);
}
/// Parses a C const from gl2.h.
class CConst {
static final ws = new RegExp(r'\s+');
static final defineReg =
new RegExp(r'#define (GL_[_A-Za-z0-9]+)\s*(0x[0-9a-fA-F]+|[0-9]+)');
String name;
String value;
CConst(this.name, this.value);
String toString() => 'const int $name = $value;';
}