nt_unixcall: glBlendFunc, glBlendFuncSeparate, glViewport, glTexParameteri and glUniform1f on the ring's fast path (E1)

The next GL calls in Noita's frame that still made an NT syscall each
(GEX_NT_GL_STATS on the ejected page: 39, 8, 9, 12 and 14 a frame, about
0.4 ms of the main thread's frame with the stats' timing), all commands
with no layer state. The provider writes them as the layer's hot_one
does; tests/nt glfast checks each by a query (x86 with and without
--cgen, x64), and gex-kernel's constants test covers the codes.

The provider is C (gex-cgen/providers): no unit re-keys but the NT
trampolines' (a few functions), whose key carries the providers it binds.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_d2453757b8a84fdbb4ee7c9e093141df
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-07 17:45:59 -04:00
1 parent 90291e6415
commit 430acec794
5 files changed
+78 -5

No files matched your search

+19 -1
View File
@@ -58,6 +58,11 @@
#define FN_glUniform1i 2524u
#define FN_glUniform2f 2542u
#define FN_glUseProgram 2645u
#define FN_glBlendFunc 16u
#define FN_glBlendFuncSeparate 463u
#define FN_glViewport 344u
#define FN_glTexParameteri 313u
#define FN_glUniform1f 2520u
#define FN_glEnableClientState 82u
#define FN_glDisableClientState 73u
#define OP_ENABLE 19u
@@ -65,12 +70,17 @@
#define OP_USE_PROGRAM 55u
#define OP_UNIFORM_1I 64u
#define OP_UNIFORM_2F 66u
#define OP_VIEWPORT 18u
#define OP_BLEND_FUNC 21u
#define OP_BLEND_FUNC_SEPARATE 37u
#define OP_UNIFORM_1F 65u
#define OP_TEX_PARAMETER_I 100u
#define OP_BIND_TEXTURE 98u
#define OP_ACTIVE_TEXTURE 99u
#define OP_ENABLE_CLIENT_STATE 185u
#define OP_DISABLE_CLIENT_STATE 186u
/* Append one command of `n` argument words (1 to 3) taken from the params
/* Append one command of `n` argument words (1 to 4) taken from the params
* struct (after its TEB pointer). 1 when it is in the ring. */
GEX_INLINE int gl_put(uint32_t op, uint32_t params, uint32_t n) {
uint32_t ring = __atomic_load_n(GA(GL_FAST_RING), __ATOMIC_ACQUIRE);
@@ -134,6 +144,14 @@ static GEX_PROVIDER(p_unix_call) {
case FN_glBindTexture: op = OP_BIND_TEXTURE, n = 2; break;
case FN_glUniform1i: op = OP_UNIFORM_1I, n = 2; break;
case FN_glUniform2f: op = OP_UNIFORM_2F, n = 3; break;
/* The next most frequent in Noita's frame after the seven above
* (GEX_NT_GL_STATS: 39, 8, 9, 12 and 14 a frame), all commands
* with no layer state, as the layer's `hot_one` writes them. */
case FN_glBlendFunc: op = OP_BLEND_FUNC, n = 2; break;
case FN_glBlendFuncSeparate: op = OP_BLEND_FUNC_SEPARATE, n = 4; break;
case FN_glViewport: op = OP_VIEWPORT, n = 4; break;
case FN_glTexParameteri: op = OP_TEX_PARAMETER_I, n = 3; break;
case FN_glUniform1f: op = OP_UNIFORM_1F, n = 2; break;
case FN_glEnableClientState:
case FN_glDisableClientState:
if (params >= 0x10000u && client_state(params, code == FN_glEnableClientState)) {
+23 -1
View File
@@ -1470,6 +1470,11 @@ enum Hot {
BindTexture,
Uniform1i,
Uniform2f,
BlendFunc,
BlendFuncSeparate,
Viewport,
TexParameteri,
Uniform1f,
}
fn hot(code: u32) -> Option<Hot> {
@@ -1485,6 +1490,11 @@ fn hot(code: u32) -> Option<Hot> {
"glBindTexture" => Some(Hot::BindTexture),
"glUniform1i" => Some(Hot::Uniform1i),
"glUniform2f" => Some(Hot::Uniform2f),
"glBlendFunc" => Some(Hot::BlendFunc),
"glBlendFuncSeparate" => Some(Hot::BlendFuncSeparate),
"glViewport" => Some(Hot::Viewport),
"glTexParameteri" => Some(Hot::TexParameteri),
"glUniform1f" => Some(Hot::Uniform1f),
_ => None,
})
.collect()
@@ -1501,6 +1511,13 @@ fn hot_one(c: &mut Call<'_>, h: Hot, p: &Params) -> R<()> {
Hot::BindTexture => cmd(c, op::BIND_TEXTURE, &[p.u32(c, 1)?, p.u32(c, 2)?], &[]),
Hot::Uniform1i => cmd(c, op::UNIFORM_1I, &[p.u32(c, 1)?, p.u32(c, 2)?], &[]),
Hot::Uniform2f => cmd(c, op::UNIFORM_2F, &[p.u32(c, 1)?, p.u32(c, 2)?, p.u32(c, 3)?], &[]),
Hot::BlendFunc => cmd(c, op::BLEND_FUNC, &[p.u32(c, 1)?, p.u32(c, 2)?], &[]),
Hot::BlendFuncSeparate => {
cmd(c, op::BLEND_FUNC_SEPARATE, &[p.u32(c, 1)?, p.u32(c, 2)?, p.u32(c, 3)?, p.u32(c, 4)?], &[])
}
Hot::Viewport => cmd(c, op::VIEWPORT, &[p.u32(c, 1)?, p.u32(c, 2)?, p.u32(c, 3)?, p.u32(c, 4)?], &[]),
Hot::TexParameteri => cmd(c, op::TEX_PARAMETER_I, &[p.u32(c, 1)?, p.u32(c, 2)?, p.u32(c, 3)?], &[]),
Hot::Uniform1f => cmd(c, op::UNIFORM_1F, &[p.u32(c, 1)?, p.u32(c, 2)?], &[]),
}
}
@@ -2569,6 +2586,11 @@ mod tests {
("FN_glBindTexture", "OP_BIND_TEXTURE", "glBindTexture", op::BIND_TEXTURE),
("FN_glUniform1i", "OP_UNIFORM_1I", "glUniform1i", op::UNIFORM_1I),
("FN_glUniform2f", "OP_UNIFORM_2F", "glUniform2f", op::UNIFORM_2F),
("FN_glBlendFunc", "OP_BLEND_FUNC", "glBlendFunc", op::BLEND_FUNC),
("FN_glBlendFuncSeparate", "OP_BLEND_FUNC_SEPARATE", "glBlendFuncSeparate", op::BLEND_FUNC_SEPARATE),
("FN_glViewport", "OP_VIEWPORT", "glViewport", op::VIEWPORT),
("FN_glTexParameteri", "OP_TEX_PARAMETER_I", "glTexParameteri", op::TEX_PARAMETER_I),
("FN_glUniform1f", "OP_UNIFORM_1F", "glUniform1f", op::UNIFORM_1F),
] {
let code = def(f) as usize;
assert_eq!(table::FUNCS[code].0, name, "{f}");
@@ -2577,7 +2599,7 @@ mod tests {
assert!(hot(code as u32).is_some(), "{name} is not a hot call here");
}
// i386 params: the TEB pointer, then 4-byte fields.
for sig in ["pi", "pii", "piff"] {
for sig in ["pi", "pii", "piff", "piiii", "pf", "pif"] {
let offs = layout(sig, Abi::P32);
for (i, o) in offs[..sig.len()].iter().enumerate() {
assert_eq!(*o, 4 * i as u64, "{sig}");
Binary file not shown.
Binary file not shown.
+36 -3
View File
@@ -1,7 +1,8 @@
/* The GL calls the unix-call dispatcher's provider writes into the command
* ring itself (gex-cgen/providers/nt_unixcall.c): glEnable, glDisable,
* glActiveTexture, glUseProgram, glBindTexture, glUniform1i, glUniform2f,
* glEnableClientState, glDisableClientState (which also leave the arrays'
* glBlendFunc, glBlendFuncSeparate, glViewport, glTexParameteri,
* glUniform1f, glEnableClientState, glDisableClientState (which also leave the arrays'
* flags where the layer reads them before a draw: client-memory arrays are
* uploaded only when the layer sees them enabled).
* Each is checked by a query (which the NT layer answers after a submit),
@@ -33,6 +34,8 @@ PROC(void, glUseProgram, (GLuint))
PROC(GLint, glGetUniformLocation, (GLuint, const char *))
PROC(void, glUniform1i, (GLint, GLint))
PROC(void, glUniform2f, (GLint, float, float))
PROC(void, glUniform1f, (GLint, float))
PROC(void, glBlendFuncSeparate, (GLenum, GLenum, GLenum, GLenum))
PROC(void, glGetUniformfv, (GLuint, GLint, float *))
PROC(void, glGetUniformiv, (GLuint, GLint, GLint *))
PROC(void, glClientActiveTexture, (GLenum))
@@ -68,7 +71,7 @@ int main(void) {
LOAD(glActiveTexture); LOAD(glCreateShader); LOAD(glShaderSource); LOAD(glCompileShader);
LOAD(glCreateProgram); LOAD(glAttachShader); LOAD(glLinkProgram); LOAD(glUseProgram);
LOAD(glGetUniformLocation); LOAD(glUniform1i); LOAD(glUniform2f); LOAD(glGetUniformfv); LOAD(glGetUniformiv);
LOAD(glGetUniformLocation); LOAD(glUniform1i); LOAD(glUniform2f); LOAD(glUniform1f); LOAD(glBlendFuncSeparate); LOAD(glGetUniformfv); LOAD(glGetUniformiv);
LOAD(glClientActiveTexture);
CHECK("GL 2.0 entry points", pglActiveTexture && pglUseProgram && pglUniform2f);
if (!pglActiveTexture || !pglUseProgram || !pglUniform2f) return 1;
@@ -80,7 +83,7 @@ int main(void) {
pglActiveTexture(GL_TEXTURE0);
const char *vs = "#version 110\nvoid main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";
const char *fs = "#version 110\nuniform vec2 off; uniform int k; void main() { gl_FragColor = vec4(off, float(k), 1.0); }";
const char *fs = "#version 110\nuniform vec2 off; uniform int k; uniform float a; void main() { gl_FragColor = vec4(off, float(k), a); }";
GLuint v = pglCreateShader(0x8B31), fr = pglCreateShader(0x8B30), prog = pglCreateProgram();
pglShaderSource(v, 1, &vs, 0);
pglShaderSource(fr, 1, &fs, 0);
@@ -104,6 +107,36 @@ int main(void) {
pglGetUniformiv(prog, s, &iv);
CHECK("glUniform2f", fv[0] == 0.25f && fv[1] == -1.5f);
CHECK("glUniform1i", iv == 5);
if (pglUniform1f) {
GLint a = pglGetUniformLocation(prog, "a");
float av = 0;
pglUniform1f(a, 0.75f);
pglGetUniformfv(prog, a, &av);
CHECK("glUniform1f", av == 0.75f);
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLint bs = 0, bd = 0;
glGetIntegerv(GL_BLEND_SRC, &bs);
glGetIntegerv(GL_BLEND_DST, &bd);
CHECK("glBlendFunc", bs == GL_SRC_ALPHA && bd == GL_ONE_MINUS_SRC_ALPHA);
if (pglBlendFuncSeparate) {
GLint v4[4] = {0, 0, 0, 0};
pglBlendFuncSeparate(GL_ONE, GL_ZERO, GL_DST_ALPHA, GL_ONE);
glGetIntegerv(0x80C9, &v4[0]); /* GL_BLEND_SRC_RGB */
glGetIntegerv(0x80C8, &v4[1]); /* GL_BLEND_DST_RGB */
glGetIntegerv(0x80CB, &v4[2]); /* GL_BLEND_SRC_ALPHA */
glGetIntegerv(0x80CA, &v4[3]); /* GL_BLEND_DST_ALPHA */
CHECK("glBlendFuncSeparate", v4[0] == GL_ONE && v4[1] == GL_ZERO && v4[2] == GL_DST_ALPHA && v4[3] == GL_ONE);
}
glViewport(3, 5, 40, 30);
GLint vp[4] = {0, 0, 0, 0};
glGetIntegerv(GL_VIEWPORT, vp);
CHECK("glViewport", vp[0] == 3 && vp[1] == 5 && vp[2] == 40 && vp[3] == 30);
glBindTexture(GL_TEXTURE_2D, tex[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
GLint mf = 0;
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &mf);
CHECK("glTexParameteri", mf == GL_NEAREST);
/* Two million fast calls (some 26 MB of commands: the ring fills and is
* submitted three times), with layer-encoded calls among them, ending in