| /// 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;'; |
| } |