createProgram(), two attachShader() calls and linkProgram() join the stages into one executable. Linking checks what no single shader can: that both stages are present and compiled, that every fragment in has a matching vertex out, and that a uniform declared in both stages agrees in type and precision:
const compile = (type, src) => {
const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s;
};
const vs = compile(gl.VERTEX_SHADER, `#version 300 es
uniform highp float uScale; void main() { gl_Position = vec4(uScale); }`);
const fs = (body) => compile(gl.FRAGMENT_SHADER,
`#version 300 es\nprecision mediump float; out vec4 o; ${body}`);
for (const [label, ...shaders] of [['vertex only', vs],
['missing varying', vs, fs('in vec3 vNormal; void main() { o = vec4(vNormal, 1.0); }')],
['uniform precision', vs, fs('uniform float uScale; void main() { o = vec4(uScale); }')]]) {
const program = gl.createProgram();
for (const s of shaders) gl.attachShader(program, s);
gl.linkProgram(program);
console.log(`${label}:`, gl.getProgramParameter(program, gl.LINK_STATUS) ||
gl.getProgramInfoLog(program).trim());
}Output
vertex only: The program must contain objects to form both a vertex and fragment shader. missing varying: FRAGMENT varying vNormal does not match any VERTEX varying uniform precision: Precisions of uniform 'uScale' differ between VERTEX and FRAGMENT shaders.
A linked program keeps its code, so you may then detachShader() and deleteShader() its shaders. validateProgram() checks a program against the current state: a sampler2D and a samplerCube both left on texture unit 0 fail with "Two textures of different types use the same sampler location." Use it only while debugging.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.names { display: flex; max-width: 600px; }
.names div { flex: 1; padding: 4px; text-align: center; }
.names b { display: block; font-size: 12px; }
.bad { color: #b5452f; } .good { color: #3f7d3a; }
</style>
<canvas id="view" width="600" height="150"></canvas>
<div class="names" id="names"></div>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const compile = (type, src) => {
const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s;
};
// Every vertex shader here draws a full-viewport triangle.
const vsBody = `void main() {
gl_Position = vec4(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0, 0.0, 1.0) * uScale;
gl_Position.w = 1.0; vUV = gl_Position.xy * 0.5 + 0.5; }`;
const vs = compile(gl.VERTEX_SHADER, `#version 300 es
uniform highp float uScale; out vec2 vUV; ${vsBody}`);
const fs = (body) => compile(gl.FRAGMENT_SHADER,
`#version 300 es\nprecision mediump float; out vec4 o; ${body}`);
const cases = [
['vertex only', vs],
['missing varying', vs, fs('in vec3 vNormal; void main() { o = vec4(vNormal, 1.0); }')],
['uniform precision', vs, fs('uniform float uScale; void main() { o = vec4(uScale); }')],
['matching stages', vs, fs('in vec2 vUV; void main() { o = vec4(vec3(0.12, 0.37, 0.55) * (0.6 + 0.6 * vUV.y), 1.0); }')],
];
gl.bindVertexArray(gl.createVertexArray());
gl.enable(gl.SCISSOR_TEST);
let html = '';
cases.forEach(([label, ...shaders], i) => {
const program = gl.createProgram();
for (const s of shaders) gl.attachShader(program, s);
gl.linkProgram(program);
const ok = gl.getProgramParameter(program, gl.LINK_STATUS);
gl.viewport(i * 150 + 10, 10, 130, 130);
gl.scissor(i * 150 + 10, 10, 130, 130);
if (ok) {
gl.useProgram(program);
gl.uniform1f(gl.getUniformLocation(program, 'uScale'), 1.0);
gl.drawArrays(gl.TRIANGLES, 0, 3);
} else { // nothing to draw with: a pale red tile
gl.clearColor(0.93, 0.8, 0.76, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
const log = ok ? 'LINK_STATUS true' : gl.getProgramInfoLog(program).trim().split('\n')[0];
html += `<div><b>${label}</b><span class="${ok ? 'good' : 'bad'}">${log.replace(/</g, '<')}</span></div>`;
});
document.getElementById('names').innerHTML = html;
</script>