#!/usr/bin/env python3 """Generate the Android guest's OpenGL ES entry points. The guest (ILP32 code, see port/android/README.md) cannot link against the host's libGLESv3, but it can call it: a host function receives the guest's pointer arguments zero-extended, and 32-bit integers and floats go in the same registers under both ABIs. What differs is - GLsizeiptr and GLintptr, 32-bit in the guest and 64-bit in the host; - arguments passed on the stack (beyond the eighth integer argument), which the guest packs by size while the host expects 8-byte slots; - returned pointers, which point at host memory. For each function in the Android list in port/linux/src/gl.h this writes a guest wrapper that widens such arguments and calls an import named hostgl_, which the host resolves to the real entry point. Functions returning a string go through host_gl_get_string instead. Usage: android_gl_stubs.py gl.h gl32.h gl2ext.h output.c imports.list """ import re import sys INTEGER_REGISTER_COUNT = 8 WIDE_TYPES = {"GLsizeiptr", "GLintptr", "GLint64", "GLuint64", "GLint64EXT", "GLuint64EXT"} FLOAT_TYPES = {"GLfloat", "GLclampf", "GLdouble", "GLclampd", "float", "double"} def android_functions(gl_header: str): text = open(gl_header, encoding="utf-8").read() start = text.index("/* ANDROID_GL_FUNCTIONS_BEGIN */") end = text.index("/* ANDROID_GL_FUNCTIONS_END */") return re.findall(r"X\((\w+)\)", text[start:end]) def prototypes(*headers): result = {} pattern = re.compile(r"GL_APICALL\s+(.+?)\s*GL_APIENTRY\s+(\w+)\s*\((.*?)\);", re.S) for header in headers: text = open(header, encoding="utf-8").read() for ret, name, params in pattern.findall(text): params = " ".join(params.split()) result.setdefault(name, (ret.strip(), params)) return result def split_parameter(param: str): param = param.strip() m = re.match(r"(.*?)(\w+)\s*$", param) ptype, pname = m.group(1).strip(), m.group(2) if not ptype: # unnamed ptype, pname = param, None return ptype, pname def main(): gl_header, gl32, gl2ext, output, imports = sys.argv[1:6] functions = android_functions(gl_header) protos = prototypes(gl32, gl2ext) out = [ "/* generated by tools/android_gl_stubs.py from port/linux/src/gl.h; do not edit */", "#include ", "#include ", "#include ", "", "void host_gl_get_string(unsigned int name, int index, char *buffer, unsigned int size);", "", ] table = [] import_names = [] for name in functions: if name not in protos: raise SystemExit(f"{name}: no prototype in the GLES headers") ret, params = protos[name] if params in ("void", ""): plist = [] else: plist = [split_parameter(p) for p in params.split(",")] if name == "glGetString": out += [ "static const GLubyte *GL_APIENTRY guest_glGetString(GLenum name)", "{", "\tstatic __thread char buffer[4][4096];", "\tstatic __thread int next;", "\tchar *result = buffer[next++ & 3];", "", "\thost_gl_get_string(name, -1, result, sizeof(buffer[0]));", "\treturn (const GLubyte *)result;", "}", "", ] table.append(name) continue if name == "glShaderSource": # an array of pointers: widen each to a host pointer import_names.append("hostgl_glShaderSource") out += [ "void hostgl_glShaderSource(GLuint shader, GLsizei count, const unsigned long long *strings, " "const GLint *lengths);", "static void GL_APIENTRY guest_glShaderSource(GLuint shader, GLsizei count, " "const GLchar *const *strings, const GLint *lengths)", "{", "\tunsigned long long wide[16];", "\tGLsizei index;", "", "\tif (count > 16)", "\t\tcount = 16;", "\tfor (index = 0; index < count; index++)", "\t\twide[index] = (unsigned long long)(unsigned int)strings[index];", "\thostgl_glShaderSource(shader, count, wide, lengths);", "}", "", ] table.append(name) continue if "*" in ret: raise SystemExit(f"{name}: returns a pointer; add a special case") if any(p.count("*") > 1 for p, _ in plist): raise SystemExit(f"{name}: takes an array of pointers; add a special case") decl_params, host_params, call_args = [], [], [] integer_index = 0 for index, (ptype, pname) in enumerate(plist): arg = f"a{index}" decl_params.append(f"{ptype} {arg}") is_pointer = "*" in ptype base = ptype.replace("const", "").strip() if base in FLOAT_TYPES and not is_pointer: host_params.append(ptype) call_args.append(arg) continue wide = base in WIDE_TYPES and not is_pointer on_stack = integer_index >= INTEGER_REGISTER_COUNT integer_index += 1 if is_pointer: if on_stack: host_params.append("unsigned long long") call_args.append(f"(unsigned long long)(unsigned int){arg}") else: host_params.append(ptype) call_args.append(arg) elif wide or on_stack: host_params.append("long long") call_args.append(f"(long long){arg}") else: host_params.append(ptype) call_args.append(arg) host_name = "hostgl_" + name import_names.append(host_name) out.append(f"{ret} {host_name}({', '.join(host_params) or 'void'});") out.append(f"static {ret} GL_APIENTRY guest_{name}({', '.join(decl_params) or 'void'})") out.append("{") call = f"{host_name}({', '.join(call_args)})" out.append(f"\t{'return ' if ret != 'void' else ''}{call};") out.append("}") out.append("") table.append(name) out.append("static const struct { const char *name; void *function; } guest_gl_functions[] =") out.append("{") for name in table: out.append(f'\t{{ "{name}", (void *)guest_{name} }},') out.append("};") out.append("") out.append("typedef void (*guest_gl_function)(void);") out.append("guest_gl_function guest_gl_get_proc_address(const char *name)") out.append("{") out.append("\tunsigned long index;") out.append("") out.append("\tfor (index = 0; index < sizeof(guest_gl_functions) / sizeof(guest_gl_functions[0]); index++)") out.append("\t{") out.append("\t\tif (!strcmp(guest_gl_functions[index].name, name))") out.append("\t\t\treturn (guest_gl_function)guest_gl_functions[index].function;") out.append("\t}") out.append("\treturn 0;") out.append("}") with open(output, "w", encoding="utf-8") as f: f.write("\n".join(out) + "\n") with open(imports, "w", encoding="utf-8") as f: f.write("\n".join(import_names) + "\n") if __name__ == "__main__": main()