#!/usr/bin/env python3 """gen_glthread.py: writes host/src/gs/gs_gl_thread.h from the GL function list in host/src/gs/gs_gl_api.h (GSGL_FUNCS). The GL renderer (gs_gl.c) calls GL through p_glX. With the GL thread on, each p_glX records the call (its arguments, and copies of the data it points at) in a queue that the GL thread, which owns the context, replays through r_glX (the real entry points). Calls that return something or write through a pointer (glGet*, glGen*, glCreate*, glReadPixels, glMapBufferRange...) are run by the GL thread while the caller waits. Without the GL thread p_glX calls r_glX. Run it again after changing GSGL_FUNCS: python3 tools/gen_glthread.py """ import os, re HERE = os.path.dirname(os.path.abspath(__file__)) API = os.path.join(HERE, "..", "host", "src", "gs", "gs_gl_api.h") OUT = os.path.join(HERE, "..", "host", "src", "gs", "gs_gl_thread.h") src = open(API).read() funcs = re.findall(r"X\((gl\w+),\s*([^,]+?),\s*\(([^)]*)\)\)", src) SCALAR = {"GLenum", "GLuint", "GLbitfield", "GLint", "GLsizei", "GLboolean", "GLubyte", "GLsizeiptr", "GLintptr"} FLOAT = {"GLfloat"} # calls whose pointer argument is data to copy into the queue (bytes: a C expression) DATA = { "glTexImage2D": (8, "glt_pix_bytes(a3, a4, a6, a7)"), "glTexSubImage2D": (8, "glt_pix_bytes(a4, a5, a6, a7)"), "glBufferData": (2, "(size_t)a1"), "glBufferSubData": (3, "(size_t)a2"), "glUniform4iv": (2, "(size_t)a1 * 16"), "glUniform1iv": (2, "(size_t)a1 * 4"), "glDeleteTextures": (1, "(size_t)a0 * 4"), "glDeleteFramebuffers": (1, "(size_t)a0 * 4"), } # pointer arguments that are offsets into the bound buffer, not data OFFSET_PTR = {"glVertexAttribPointer": 5, "glVertexAttribIPointer": 4} out = ['/* generated by tools/gen_glthread.py from gs_gl_api.h: do not edit */', '/* included by gs_gl.c, after the GL thread helpers (glt_cmd, glt_call, glt_q, glt_pix_bytes) */'] ids = ["GLT_" + f[0] for f in funcs] out.append("enum { " + ", ".join(ids) + ", GLT_SWAP, GLT_VB, GLT_CALLBACK, GLT_N };") out.append("#define GSGL_DEF(name, ret, args) PFN_##name r_##name;\nGSGL_FUNCS(GSGL_DEF)\n#undef GSGL_DEF") def conv_in(t, name): """an argument to its u64 slot""" t = t.strip() if t in FLOAT: return "glt_fbits(%s)" % name if "*" in t: return "(u64)(uintptr_t)(%s)" % name return "(u64)(s64)(%s)" % name def conv_out(t, slot): """a u64 slot back to the argument's type""" t = t.strip() if t in FLOAT: return "glt_bitsf(%s)" % slot if "*" in t: return "(%s)(uintptr_t)(%s)" % (t, slot) return "(%s)(%s)" % (t, slot) replay = [] for name, ret, args in funcs: ret = ret.strip() ts = [] if args.strip() in ("void", "") else [a.strip() for a in args.split(",")] n = len(ts) params = ", ".join("%s a%d" % (t, i) for i, t in enumerate(ts)) or "void" call_r = "r_%s(%s)" % (name, ", ".join("a%d" % i for i in range(n))) ptr_args = [i for i, t in enumerate(ts) if "*" in t] data = DATA.get(name) is_async = ret == "void" and (not ptr_args or data or (name in OFFSET_PTR and ptr_args == [OFFSET_PTR[name]])) body = [] if name == "glPixelStorei": body.append(" glt_track_store(a0, a1); /* the unpack layout the queued texel copies are sized by */") if is_async: body.append(" if (!glt_q) { %s; return; }" % call_r) if data: pi, expr = data body.append(" size_t nb = a%d ? %s : 0;" % (pi, expr)) body.append(" u64 *c = glt_cmd(%s, %d, nb);" % ("GLT_" + name, n)) else: body.append(" u64 *c = glt_cmd(%s, %d, 0);" % ("GLT_" + name, n)) for i, t in enumerate(ts): if data and i == data[0]: body.append(" c[%d] = 0; if (nb) memcpy(c + %d, a%d, nb);" % (i, n, i)) else: body.append(" c[%d] = %s;" % (i, conv_in(t, "a%d" % i))) # replay: the data pointer is the blob after the arguments rargs = [] for i, t in enumerate(ts): if data and i == data[0]: rargs.append("blob_n ? (%s)(const void *)(c + %d) : (%s)0" % (t, n, t)) else: rargs.append(conv_out(t, "c[%d]" % i)) replay.append(" case GLT_%s: r_%s(%s); break;" % (name, name, ", ".join(rargs))) else: if ret == "void": body.append(" if (!glt_q) { %s; return; }" % call_r) else: body.append(" if (!glt_q) return %s;" % call_r) body.append(" u64 a[%d + 1]; u64 r = 0;" % n) for i, t in enumerate(ts): body.append(" a[%d] = %s;" % (i, conv_in(t, "a%d" % i))) body.append(" glt_call(%s, a, %d, &r);" % ("GLT_" + name, n)) if ret != "void": if ret in FLOAT: body.append(" return glt_bitsf(r);") elif "*" in ret: body.append(" return (%s)(uintptr_t)r;" % ret) else: body.append(" return (%s)r;" % ret) rargs = ", ".join(conv_out(t, "c[%d]" % i) for i, t in enumerate(ts)) if ret == "void": replay.append(" case GLT_%s: r_%s(%s); break;" % (name, name, rargs)) elif "*" in ret: replay.append(" case GLT_%s: *rp = (u64)(uintptr_t)r_%s(%s); break;" % (name, name, rargs)) else: replay.append(" case GLT_%s: *rp = (u64)r_%s(%s); break;" % (name, name, rargs)) out.append("static %s p_%s(%s)\n{\n%s\n}" % (ret, name, params, "\n".join(body))) nargs = [str(0 if a.strip() in ("void", "") else len(a.split(","))) for _, _, a in funcs] out.append("static const unsigned char glt_nargs[GLT_N] = { " + ", ".join(nargs) + ", 0, 1, 0 }; /* ..., SWAP, VB, CALLBACK */") out.append("/* the GL thread: one recorded call (c: its arguments, then its data; rp: where a result goes) */") out.append("static void glt_exec(u32 id, const u64 *c, size_t blob_n, u64 *rp)\n{\n (void)blob_n; (void)rp;\n switch (id) {") out += replay out.append(" default: glt_exec_extra(id, c, blob_n); break;\n }\n}") open(OUT, "w").write("\n".join(out) + "\n") print("wrote %s: %d GL functions" % (OUT, len(funcs)))