GLSL ES 1.00, the language of WebGL 1 and older tutorials, has four storage qualifiers. const marks a compile-time constant; attribute a per-vertex, float-typed vertex shader input (3.00's in); uniform a read-only value shared by the whole draw call; and varying a vertex shader output interpolated into the fragment shader's matching varying (3.00's out and in). WebGL 2 still compiles 1.00 shaders, but both shaders of a program must use the same version:
const vs100 = `attribute vec2 aPosition; // GLSL ES 1.00: no #version line
varying vec2 vUV;
void main() { vUV = aPosition * 0.5 + 0.5; gl_Position = vec4(aPosition, 0.0, 1.0); }`;
const fs100 = `precision mediump float;
uniform vec3 uTint;
varying vec2 vUV;
void main() { gl_FragColor = vec4(uTint * vUV.y, 1.0); }`;
const fs300 = `#version 300 es
precision mediump float;
in vec2 vUV;
out vec4 outColor;
void main() { outColor = vec4(vUV, 0.0, 1.0); }`;
const p = GLH.program(gl, vs100, fs100);
console.log('1.00 pair: attributes', gl.getProgramParameter(p, gl.ACTIVE_ATTRIBUTES),
'uniforms', gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS));
try { GLH.program(gl, vs100, fs300); } catch (e) { console.log(e.message); }Output
1.00 pair: attributes 1 uniforms 1 Program failed to link: Fragment shader version does not match other shader versions.
A WebGL 1 context rejects a 3.00 shader ("unsupported shader version"). A uniform declared in both stages is one variable and must match in type and precision.
<!doctype html>
<style>
body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
pre { max-width: 600px; margin: 6px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd;
font-size: 11px; white-space: pre-wrap; }
</style>
<canvas id="view" width="600" height="200"></canvas>
<pre id="out"></pre>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const log = text => document.getElementById('out').textContent += text + '\n';
function program(vsSource, fsSource) {
const p = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vsSource], [gl.FRAGMENT_SHADER, fsSource]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(p, s);
}
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) throw new Error('Link failed: ' + gl.getProgramInfoLog(p));
return p;
}
const vs100 = `attribute vec2 aPosition; // GLSL ES 1.00: no #version line
varying vec2 vUV; // written here, interpolated, read in the fragment shader
void main() { vUV = aPosition * 0.5 + 0.5; gl_Position = vec4(aPosition, 0.0, 1.0); }`;
const fs100 = `precision mediump float;
uniform vec3 uTint; // one value for the whole draw call
varying vec2 vUV;
void main() { gl_FragColor = vec4(uTint * (0.35 + 0.65 * vUV.y), 1.0); }`;
const fs300 = `#version 300 es
precision mediump float;
in vec2 vUV;
out vec4 outColor;
void main() { outColor = vec4(vUV, 0.0, 1.0); }`;
const p = program(vs100, fs100);
log(`1.00 pair links in WebGL 2: attributes ${gl.getProgramParameter(p, gl.ACTIVE_ATTRIBUTES)},` +
` uniforms ${gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS)}`);
try { program(vs100, fs300); } catch (e) { log('1.00 vertex + 3.00 fragment -> ' + e.message.trim()); }
// Draw three book covers with the same attribute data and a different uniform tint each.
gl.useProgram(p);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, 1,1, -1,-1, 1,1, -1,1]), gl.STATIC_DRAW);
const aPosition = gl.getAttribLocation(p, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
const uTint = gl.getUniformLocation(p, 'uTint');
gl.clearColor(0.97, 0.96, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
[[0.12, 0.37, 0.55], [0.87, 0.6, 0.06], [0.71, 0.27, 0.18]].forEach((tint, i) => {
gl.viewport(40 + i * 190, 15, 130, 170);
gl.uniform3fv(uTint, tint);
gl.drawArrays(gl.TRIANGLES, 0, 6);
});
</script>