The fourth part of webgl-helper.js adds a loader that accepts source strings or <script> ids (a string containing main( is source), performs step 10, and returns the program's attribute locations and one setter per uniform, chosen from the type the linker reports. Three shorthands for the demos follow:
// Section 3.4: load a program from source strings or <script> ids and collect its variables
GLH.load = function (gl, vs, fs) {
const text = (s) => (/\bmain\s*\(/.test(s) ? s : document.getElementById(s).textContent);
const program = GLH.program(gl, text(vs), text(fs));
const attributes = {}, uniforms = {}, count = (p) => gl.getProgramParameter(program, p);
for (let i = 0; i < count(gl.ACTIVE_ATTRIBUTES); i++) {
const { name } = gl.getActiveAttrib(program, i);
attributes[name] = gl.getAttribLocation(program, name);
}
for (let i = 0; i < count(gl.ACTIVE_UNIFORMS); i++) {
const { name, type } = gl.getActiveUniform(program, i);
const location = gl.getUniformLocation(program, name); // null inside uniform blocks
if (location) uniforms[name.replace(/\[0\]$/, '')] = GLH.setter(gl, type, location);
}
gl.useProgram(program);
return { program, attributes, uniforms };
};
// the uniform*() method for a type from getActiveUniform(): FLOAT_VEC3 -> uniform3fv, ...
GLH.setter = function (gl, type, location) {
const W = WebGL2RenderingContext, name = Object.keys(W).find((k) => W[k] === type);
const n = name.match(/\d(x\d)?$/)?.[0] ?? '1'; // FLOAT_MAT2x3 -> 2x3; samplers -> 1
if (name.includes('_MAT')) return (m) => gl[`uniformMatrix${n}fv`](location, false, m);
const t = name.startsWith('FLOAT') ? 'f' : /^UNSIGNED_INT(_VEC|$)/.test(name) ? 'ui' : 'i';
return (v) => gl[`uniform${n}${t}v`](location, typeof v === 'number' ? [v] : v);
};
// shorthands for the chapter's demos: BookNest's six cover colors and a cleared WebGL 2 canvas
GLH.covers = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f']; // books 1-6
GLH.rgb = (hex) => [1, 3, 5].map((k) => parseInt(hex.slice(k, k + 2), 16) / 255);
GLH.context = function (id, color = [0.93, 0.91, 0.87, 1.0]) { // cleared to the shelf color
const gl = document.getElementById(id).getContext('webgl2');
gl.clearColor(...color); gl.clear(gl.COLOR_BUFFER_BIT); return gl;
};A page then writes uniforms.uTint([1, 0.9, 0.8]) or uniforms.uModel(matrix) whatever the type. Like every uniform*() call, a setter acts on the current program, so call gl.useProgram() first when you juggle several.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
pre { max-width: 600px; margin: 4px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd; font-size: 11px; }
</style>
<canvas id="wall" width="600" height="170"></canvas>
<pre id="out"></pre>
<script>
const GLH = {
program(gl, vsSource, fsSource) {
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vsSource], [gl.FRAGMENT_SHADER, fsSource]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src.trim()); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(program, s);
}
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
return program;
},
};
// Load a program from source strings or <script> ids and collect its variables.
GLH.load = function (gl, vs, fs) {
const text = (s) => (/\bmain\s*\(/.test(s) ? s : document.getElementById(s).textContent);
const program = GLH.program(gl, text(vs), text(fs));
const attributes = {}, uniforms = {}, count = (p) => gl.getProgramParameter(program, p);
for (let i = 0; i < count(gl.ACTIVE_ATTRIBUTES); i++) {
const { name } = gl.getActiveAttrib(program, i);
attributes[name] = gl.getAttribLocation(program, name);
}
for (let i = 0; i < count(gl.ACTIVE_UNIFORMS); i++) {
const { name, type } = gl.getActiveUniform(program, i);
const location = gl.getUniformLocation(program, name); // null inside uniform blocks
if (location) uniforms[name.replace(/\[0\]$/, '')] = GLH.setter(gl, type, location);
}
gl.useProgram(program);
return { program, attributes, uniforms };
};
// The uniform*() method for a type from getActiveUniform(): FLOAT_VEC3 -> uniform3fv, ...
GLH.setter = function (gl, type, location) {
const W = WebGL2RenderingContext, name = Object.keys(W).find((k) => W[k] === type);
const n = name.match(/\d(x\d)?$/)?.[0] ?? '1'; // FLOAT_MAT2x3 -> 2x3; samplers -> 1
if (name.includes('_MAT')) return (m) => gl[`uniformMatrix${n}fv`](location, false, m);
const t = name.startsWith('FLOAT') ? 'f' : /^UNSIGNED_INT(_VEC|$)/.test(name) ? 'ui' : 'i';
return (v) => gl[`uniform${n}${t}v`](location, typeof v === 'number' ? [v] : v);
};
GLH.covers = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
GLH.rgb = (hex) => [1, 3, 5].map((k) => parseInt(hex.slice(k, k + 2), 16) / 255);
// --- a page using the helper: no getUniformLocation() calls anywhere below ---
const gl = document.getElementById('wall').getContext('webgl2');
const { attributes, uniforms } = GLH.load(gl, `#version 300 es
in vec2 aCorner; uniform float uLift[6]; uniform mat2 uTilt; out vec2 vUV; flat out int vBook;
void main() {
vBook = gl_InstanceID; vUV = aCorner;
vec2 p = uTilt * ((aCorner - 0.5) * vec2(0.26, 1.5)) + vec2(-0.8 + float(vBook) * 0.32, uLift[vBook]);
gl_Position = vec4(p, 0.0, 1.0);
}`, `#version 300 es
precision mediump float;
in vec2 vUV; flat in int vBook; uniform vec3 uColors[6]; uniform int uSelected; out vec4 outColor;
void main() {
vec3 c = uColors[vBook] * mix(0.55, 1.0, smoothstep(0.07, 0.1, vUV.x)) * mix(0.75, 1.2, vUV.y);
if (vBook == uSelected && any(greaterThan(abs(vUV - 0.5), vec2(0.45)))) c = vec3(1.0, 0.84, 0.3);
outColor = vec4(c, 1.0);
}`);
document.getElementById('out').textContent =
'attributes: ' + JSON.stringify(attributes) + '\nuniform setters: ' + Object.keys(uniforms).join(', ');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 1,0, 1,1, 0,0, 1,1, 0,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(attributes.aCorner);
gl.vertexAttribPointer(attributes.aCorner, 2, gl.FLOAT, false, 0, 0);
uniforms.uColors(GLH.covers.flatMap(GLH.rgb)); // vec3[6] -> uniform3fv
uniforms.uTilt([1, 0, 0, 1]); // mat2 -> uniformMatrix2fv
gl.clearColor(0.93, 0.91, 0.87, 1);
function frame(ms) {
const t = ms * 0.002;
uniforms.uLift([0, 1, 2, 3, 4, 5].map(i => 0.05 * Math.sin(t + i))); // float[6] -> uniform1fv
uniforms.uSelected(Math.floor(ms / 1000) % 6); // int -> uniform1iv
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 6);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>