Skip to content

Commit 793198b

Browse files
webgl: populate uniformLocsById on first glUniform (#26846)
PR created with AI assistance. `$webglGetUniformLocation` reads `program.uniformLocsById` to resolve a uniform location integer to a `WebGLUniformLocation`. `uniformLocsById` is reset to `0` by `glLinkProgram` and lazily allocated by `$webglPrepareUniformLocationsBeforeFirstUse`, but that prepare call only ran on the `glGetUniformLocation()` path. As a result, calling `glUniform*()` on a freshly linked program before any `glGetUniformLocation()` — which is the intended usage with `-sGL_EXPLICIT_UNIFORM_LOCATION` and `explicit layout(location=N)` — silently no-opped: `$webglGetUniformLocation` read `uniformLocsById[location]` off the integer `0`, returned `undefined`, and `GLctx.uniform*()` dropped the call with no error. Fix: call `$webglPrepareUniformLocationsBeforeFirstUse(p)` at the top of `$webglGetUniformLocation`, inside the `GL_TRACK_ERRORS` guard. The prepare call is idempotent (guards on `!uniformLocsById`), so the steady-state overhead is a single property read. Adds `browser.test_webgl_uniform_before_get_location` with two parameterizations (default and `-sGL_ASSERTIONS`). Fixes: #26672
1 parent 4aefc6e commit 793198b

3 files changed

Lines changed: 106 additions & 2 deletions

File tree

src/lib/libwebgl.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,9 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
603603

604604
#if GL_ASSERTIONS
605605
validateGLObjectID: (objectHandleArray, objectID, callerFunctionName, objectReadableType) => {
606-
if (objectID != 0) {
606+
// `objectHandleArray` may be uninitialized when GL uniforms are lazily initialized, and `glUniform*` is called
607+
// for the first time before uniforms have been populated. So ignore this validation if the handle array is not present.
608+
if (objectID != 0 && objectHandleArray) {
607609
if (objectHandleArray[objectID] === null) {
608610
err(`${callerFunctionName} called with an already deleted ${objectReadableType} ID ${objectID}!`);
609611
} else if (!(objectID in objectHandleArray)) {
@@ -2171,6 +2173,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
21712173

21722174
// Returns the WebGLUniformLocation object corresponding to the location index
21732175
// integer on the currently active shader in this GL context.
2176+
$webglGetUniformLocation__deps: ['$webglPrepareUniformLocationsBeforeFirstUse'],
21742177
$webglGetUniformLocation: (location) => {
21752178
var p = GLctx.currentProgram;
21762179

@@ -2182,6 +2185,14 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
21822185

21832186
#if GL_TRACK_ERRORS
21842187
if (p) {
2188+
#endif
2189+
#if GL_EXPLICIT_UNIFORM_LOCATION
2190+
// Ensure `uniformLocsById`/`uniformArrayNamesById` are populated. Without
2191+
// this, calling `glUniform*()` on a freshly linked program before any
2192+
// `glGetUniformLocation()` silently no-ops: `glLinkProgram` resets
2193+
// `uniformLocsById` to 0 and only `$webglPrepareUniformLocationsBeforeFirstUse`
2194+
// refills it. The call below is idempotent (guards on `!uniformLocsById`).
2195+
webglPrepareUniformLocationsBeforeFirstUse(p);
21852196
#endif
21862197
var webglLoc = p.uniformLocsById[location];
21872198
// p.uniformLocsById[location] stores either an integer, or a
@@ -3865,7 +3876,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
38653876
GLctx.bufferSubData(0x8893 /*GL_ELEMENT_ARRAY_BUFFER*/,
38663877
0,
38673878
HEAPU8.subarray(indices, indices + size));
3868-
3879+
38693880
// Calculating vertex count if shader's attribute data is on client side
38703881
if (count > 0) {
38713882
for (var i = 0; i < GL.currentContext.maxVertexAttribs; ++i) {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 $webglGetUniformLocation silently returning undefined
7+
// when called from a glUniform*() setter on a freshly linked program before
8+
// any glGetUniformLocation() has run.
9+
//
10+
// glLinkProgram resets program.uniformLocsById to 0; only
11+
// $webglPrepareUniformLocationsBeforeFirstUse populates it. That prepare call
12+
// used to live only on the glGetUniformLocation() path, so a program author
13+
// using -sGL_EXPLICIT_UNIFORM_LOCATION and addressing uniforms by their
14+
// layout(location=N) value (i.e. without ever calling glGetUniformLocation)
15+
// would see glUniform*() silently no-op: GLctx.uniform*(undefined, ...) is
16+
// dropped by WebGL with no error.
17+
18+
#include <assert.h>
19+
#include <stdio.h>
20+
#include <GLES3/gl3.h>
21+
#include <emscripten/html5.h>
22+
23+
int main() {
24+
EmscriptenWebGLContextAttributes attr;
25+
emscripten_webgl_init_context_attributes(&attr);
26+
attr.majorVersion = 2;
27+
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
28+
assert(ctx);
29+
emscripten_webgl_make_context_current(ctx);
30+
31+
const char* vs = "#version 300 es\n"
32+
"layout(location = 6) uniform vec3 u_val;\n"
33+
"void main() { gl_Position = vec4(u_val, 1.0); }\n";
34+
35+
const char* fs = "#version 300 es\n"
36+
"precision mediump float;\n"
37+
"out vec4 o;\n"
38+
"void main() { o = vec4(1.0); }\n";
39+
40+
GLuint v = glCreateShader(GL_VERTEX_SHADER);
41+
glShaderSource(v, 1, &vs, NULL);
42+
glCompileShader(v);
43+
44+
GLuint f = glCreateShader(GL_FRAGMENT_SHADER);
45+
glShaderSource(f, 1, &fs, NULL);
46+
glCompileShader(f);
47+
48+
GLuint p = glCreateProgram();
49+
glAttachShader(p, v);
50+
glAttachShader(p, f);
51+
glLinkProgram(p);
52+
53+
GLint linked = 0;
54+
glGetProgramiv(p, GL_LINK_STATUS, &linked);
55+
assert(linked && "Program link failed");
56+
57+
glUseProgram(p);
58+
59+
// Set a uniform using its explicit layout(location=6) WITHOUT first calling
60+
// glGetUniformLocation. Before the fix, this silently no-ops because
61+
// $webglGetUniformLocation reads from program.uniformLocsById which is still
62+
// 0 from glLinkProgram, returns undefined, and GLctx.uniform3fv ignores it.
63+
const float val[] = {1.0f, 2.0f, 3.0f};
64+
glUniform3fv(/* location */ 6, 1, val);
65+
assert(glGetError() == GL_NO_ERROR);
66+
67+
float rb[3] = {-1.f, -1.f, -1.f};
68+
glGetUniformfv(p, /* location */ 6, rb);
69+
assert(glGetError() == GL_NO_ERROR);
70+
assert(rb[0] == 1.0f && rb[1] == 2.0f && rb[2] == 3.0f);
71+
72+
// Subsequent set should also work (was already working before the fix,
73+
// because the readback's prepare populated uniformLocsById as a side effect).
74+
const float val2[] = {4.0f, 5.0f, 6.0f};
75+
glUniform3fv(/* location */ 6, 1, val2);
76+
assert(glGetError() == GL_NO_ERROR);
77+
78+
float rb2[3] = {-1.f, -1.f, -1.f};
79+
glGetUniformfv(p, /* location */ 6, rb2);
80+
assert(glGetError() == GL_NO_ERROR);
81+
assert(rb2[0] == 4.0f && rb2[1] == 5.0f && rb2[2] == 6.0f);
82+
83+
printf("Test passed!\n");
84+
return 0;
85+
}

test/test_browser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,14 @@ def test_webgl_parallel_shader_compile(self):
12351235
def test_webgl_explicit_uniform_location(self):
12361236
self.btest_exit('webgl_explicit_uniform_location.c', cflags=['-sGL_EXPLICIT_UNIFORM_LOCATION', '-sMIN_WEBGL_VERSION=2'])
12371237

1238+
@parameterized({
1239+
'': ([],),
1240+
'assertions': (['-sGL_ASSERTIONS'],),
1241+
})
1242+
@requires_webgl2
1243+
def test_webgl_uniform_before_get_location(self, args):
1244+
self.btest_exit('webgl_uniform_before_get_location.c', cflags=args + ['-sGL_EXPLICIT_UNIFORM_LOCATION', '-sMIN_WEBGL_VERSION=2'])
1245+
12381246
@requires_graphics_hardware
12391247
def test_webgl_sampler_layout_binding(self):
12401248
self.btest_exit('webgl_sampler_layout_binding.c', cflags=['-sGL_EXPLICIT_UNIFORM_BINDING'])

0 commit comments

Comments
 (0)