Skip to content

Commit ca13dbc

Browse files
webgl: fix glGetUniform*v ignoring program param (#26845)
Fixes #26844. PR created with AI assistance. `$emscriptenWebGLGetUniform` resolved its uniform location via `$webglGetUniformLocation`, which uses `GLctx.currentProgram`. Per the GLES spec, `glGetUniform*v` takes the program explicitly and does not require `glUseProgram`. Calling it without a current program returned undefined and `GLctx.getUniform` threw a `TypeError`. Additionally, under `-sGL_ASSERTIONS`, the location validation ran before the integer-`GLuint` -> `WebGLProgram` swap, dereferencing a property of a `Number` and crashing. Fix both: run assertions in the correct order around the swap and prepare call, and resolve the location against the explicit program inline. Adds a parameterized regression test.
1 parent 112eaf3 commit ca13dbc

4 files changed

Lines changed: 116 additions & 16 deletions

File tree

src/lib/libhtml5_webgl.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,14 +514,14 @@ var LibraryHtml5WebGL = {
514514
writeGLArray(GLctx.getVertexAttrib(index, param), dst, dstLength, dstType),
515515

516516
emscripten_webgl_get_uniform_d__proxy: 'sync_on_current_webgl_context_thread',
517-
emscripten_webgl_get_uniform_d__deps: ['$webglGetUniformLocation'],
517+
emscripten_webgl_get_uniform_d__deps: ['$webglGetProgramUniformLocation'],
518518
emscripten_webgl_get_uniform_d: (program, location) =>
519-
GLctx.getUniform(GL.programs[program], webglGetUniformLocation(location)),
519+
GLctx.getUniform(GL.programs[program], webglGetProgramUniformLocation(GL.programs[program], location)),
520520

521521
emscripten_webgl_get_uniform_v__proxy: 'sync_on_current_webgl_context_thread',
522-
emscripten_webgl_get_uniform_v__deps: ['$writeGLArray', '$webglGetUniformLocation'],
522+
emscripten_webgl_get_uniform_v__deps: ['$writeGLArray', '$webglGetProgramUniformLocation'],
523523
emscripten_webgl_get_uniform_v: (program, location, dst, dstLength, dstType) =>
524-
writeGLArray(GLctx.getUniform(GL.programs[program], webglGetUniformLocation(location)), dst, dstLength, dstType),
524+
writeGLArray(GLctx.getUniform(GL.programs[program], webglGetProgramUniformLocation(GL.programs[program], location)), dst, dstLength, dstType),
525525

526526
emscripten_webgl_get_parameter_v__proxy: 'sync_on_current_webgl_context_thread',
527527
emscripten_webgl_get_parameter_v__deps: ['$writeGLArray'],

src/lib/libwebgl.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,7 +2121,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
21212121
// This function intentionally assigns `HEAP32[x] = someBoolean;` Don't let
21222122
// Closure mind about that.
21232123
$emscriptenWebGLGetUniform__docs: '/** @suppress{checkTypes} */',
2124-
$emscriptenWebGLGetUniform__deps: ['$webglGetUniformLocation', '$webglPrepareUniformLocationsBeforeFirstUse'],
2124+
$emscriptenWebGLGetUniform__deps: ['$webglGetProgramUniformLocation', '$webglPrepareUniformLocationsBeforeFirstUse'],
21252125
$emscriptenWebGLGetUniform: (program, location, params, type) => {
21262126
if (!params) {
21272127
// GLES2 specification does not specify how to behave if params is a null
@@ -2139,7 +2139,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
21392139
#endif
21402140
program = GL.programs[program];
21412141
webglPrepareUniformLocationsBeforeFirstUse(program);
2142-
var data = GLctx.getUniform(program, webglGetUniformLocation(location));
2142+
var data = GLctx.getUniform(program, webglGetProgramUniformLocation(program, location));
21432143
if (typeof data == 'number' || typeof data == 'boolean') {
21442144
switch (type) {
21452145
case {{{ cDefs.EM_FUNC_SIG_PARAM_I }}}: {{{ makeSetValue('params', '0', 'data', 'i32') }}}; break;
@@ -2173,34 +2173,32 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
21732173

21742174
// Returns the WebGLUniformLocation object corresponding to the location index
21752175
// integer on the currently active shader in this GL context.
2176-
$webglGetUniformLocation__deps: ['$webglPrepareUniformLocationsBeforeFirstUse'],
2177-
$webglGetUniformLocation: (location) => {
2178-
var p = GLctx.currentProgram;
2179-
2176+
$webglGetProgramUniformLocation__deps: ['$webglPrepareUniformLocationsBeforeFirstUse'],
2177+
$webglGetProgramUniformLocation: (program, location) => {
21802178
#if !GL_TRACK_ERRORS && ASSERTIONS
21812179
// In -sGL_TRACK_ERRORS=0 build mode do not allow calling glUniform*()
21822180
// without an active GL program.
2183-
assert(p, 'Attempted to call glUniform*() without an active GL program set! (build with -sGL_TRACK_ERRORS for standards-conformant behavior)');
2181+
assert(program, 'When building with !GL_TRACK_ERRORS, program cannot be null, in a call to webglGetProgramUniformLocation()');
21842182
#endif
21852183

21862184
#if GL_TRACK_ERRORS
2187-
if (p) {
2185+
if (program) {
21882186
#endif
21892187
#if GL_EXPLICIT_UNIFORM_LOCATION
21902188
// Ensure `uniformLocsById`/`uniformArrayNamesById` are populated. Without
21912189
// this, calling `glUniform*()` on a freshly linked program before any
21922190
// `glGetUniformLocation()` silently no-ops: `glLinkProgram` resets
21932191
// `uniformLocsById` to 0 and only `$webglPrepareUniformLocationsBeforeFirstUse`
21942192
// refills it. The call below is idempotent (guards on `!uniformLocsById`).
2195-
webglPrepareUniformLocationsBeforeFirstUse(p);
2193+
webglPrepareUniformLocationsBeforeFirstUse(program);
21962194
#endif
2197-
var webglLoc = p.uniformLocsById[location];
2198-
// p.uniformLocsById[location] stores either an integer, or a
2195+
var webglLoc = program.uniformLocsById[location];
2196+
// program.uniformLocsById[location] stores either an integer, or a
21992197
// WebGLUniformLocation.
22002198
// If an integer, we have not yet bound the location, so do it now. The
22012199
// integer value specifies the array index we should bind to.
22022200
if (typeof webglLoc == 'number') {
2203-
p.uniformLocsById[location] = webglLoc = GLctx.getUniformLocation(p, p.uniformArrayNamesById[location] + (webglLoc > 0 ? `[${webglLoc}]` : ''));
2201+
program.uniformLocsById[location] = webglLoc = GLctx.getUniformLocation(program, program.uniformArrayNamesById[location] + (webglLoc > 0 ? `[${webglLoc}]` : ''));
22042202
}
22052203
// Else an already cached WebGLUniformLocation, return it.
22062204
return webglLoc;
@@ -2211,6 +2209,17 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
22112209
#endif
22122210
},
22132211

2212+
$webglGetUniformLocation__deps: ['$webglGetProgramUniformLocation'],
2213+
$webglGetUniformLocation: (location) => {
2214+
#if !GL_TRACK_ERRORS && ASSERTIONS
2215+
// In -sGL_TRACK_ERRORS=0 build mode do not allow calling glUniform*()
2216+
// without an active GL program.
2217+
assert(GLctx.currentProgram, 'Attempted to call glUniform*()/webglGetUniformLocation() without an active GL program set! (build with -sGL_TRACK_ERRORS for standards-conformant behavior)');
2218+
#endif
2219+
2220+
return webglGetProgramUniformLocation(GLctx.currentProgram, location);
2221+
},
2222+
22142223
$webglPrepareUniformLocationsBeforeFirstUse__deps: ['$webglGetLeftBracePos'],
22152224
$webglPrepareUniformLocationsBeforeFirstUse: (program) => {
22162225
var uniformLocsById = program.uniformLocsById, // Maps GLuint -> WebGLUniformLocation
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2026 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
// Regression test for two bugs in $emscriptenWebGLGetUniform (libwebgl.js):
7+
//
8+
// 1. glGetUniform*v resolved its uniform location against
9+
// `GLctx.currentProgram` instead of the explicit `program` parameter, so
10+
// calling glGetUniform*v without a prior glUseProgram(program) threw
11+
// "parameter 2 is not of type 'WebGLUniformLocation'" from
12+
// GLctx.getUniform. Per the GLES spec, glGetUniform*v takes the program
13+
// explicitly and does not require it to be bound.
14+
//
15+
// 2. Under -sGL_ASSERTIONS, the location validation ran before the integer
16+
// ID -> WebGLProgram lookup, dereferencing a property of a number and
17+
// throwing "Cannot read properties of undefined (reading '<location>')".
18+
19+
#include <assert.h>
20+
#include <stdio.h>
21+
#include <GLES3/gl3.h>
22+
#include <emscripten/html5.h>
23+
24+
int main() {
25+
EmscriptenWebGLContextAttributes attr;
26+
emscripten_webgl_init_context_attributes(&attr);
27+
attr.majorVersion = 2;
28+
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
29+
assert(ctx);
30+
emscripten_webgl_make_context_current(ctx);
31+
32+
const char* vs = "#version 300 es\nvoid main(){gl_Position=vec4(0);}";
33+
const char* fs = "#version 300 es\n"
34+
"precision mediump float;\n"
35+
"uniform float u;\n"
36+
"out vec4 o;\n"
37+
"void main(){ o = vec4(u); }";
38+
39+
GLuint v = glCreateShader(GL_VERTEX_SHADER);
40+
glShaderSource(v, 1, &vs, NULL);
41+
glCompileShader(v);
42+
43+
GLuint f = glCreateShader(GL_FRAGMENT_SHADER);
44+
glShaderSource(f, 1, &fs, NULL);
45+
glCompileShader(f);
46+
47+
GLuint p = glCreateProgram();
48+
glAttachShader(p, v);
49+
glAttachShader(p, f);
50+
glLinkProgram(p);
51+
52+
GLint linked = 0;
53+
glGetProgramiv(p, GL_LINK_STATUS, &linked);
54+
assert(linked && "Program link failed");
55+
56+
GLint loc = glGetUniformLocation(p, "u");
57+
assert(loc >= 0);
58+
59+
glUseProgram(p);
60+
glUniform1f(loc, 1.5f);
61+
assert(glGetError() == GL_NO_ERROR);
62+
63+
// Case A: program currently bound. Without the fix, this crashes under
64+
// -sGL_ASSERTIONS=1 because validateGLObjectID(program.uniformLocsById, ...)
65+
// runs before the integer -> WebGLProgram swap.
66+
float outA = -1.f;
67+
glGetUniformfv(p, loc, &outA);
68+
assert(glGetError() == GL_NO_ERROR);
69+
assert(outA == 1.5f);
70+
71+
// Case B: no program currently bound. Without the fix, glGetUniform*v
72+
// resolves `location` against GLctx.currentProgram (null), the inner GL
73+
// call receives `undefined` for the WebGLUniformLocation, and WebGL throws
74+
// a TypeError.
75+
glUseProgram(0);
76+
float outB = -1.f;
77+
glGetUniformfv(p, loc, &outB);
78+
assert(glGetError() == GL_NO_ERROR);
79+
assert(outB == 1.5f);
80+
81+
printf("Test passed!\n");
82+
return 0;
83+
}

test/test_browser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,14 @@ def test_webgl_explicit_uniform_location(self):
12431243
def test_webgl_uniform_before_get_location(self, args):
12441244
self.btest_exit('webgl_uniform_before_get_location.c', cflags=args + ['-sGL_EXPLICIT_UNIFORM_LOCATION', '-sMIN_WEBGL_VERSION=2'])
12451245

1246+
@parameterized({
1247+
'': ([],),
1248+
'assertions': (['-sGL_ASSERTIONS'],),
1249+
})
1250+
@requires_webgl2
1251+
def test_webgl_get_uniform_no_active_program(self, args):
1252+
self.btest_exit('webgl_get_uniform_no_active_program.c', cflags=args + ['-sMIN_WEBGL_VERSION=2'])
1253+
12461254
@requires_graphics_hardware
12471255
def test_webgl_sampler_layout_binding(self):
12481256
self.btest_exit('webgl_sampler_layout_binding.c', cflags=['-sGL_EXPLICIT_UNIFORM_BINDING'])

0 commit comments

Comments
 (0)