Files
project-solana/games/rac1/tools/gen_glthread.py
T
2026-10-13 20:04:31 -04:00

114 lines
5.9 KiB
Python

#!/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)))