Shader Logs

Querying Shader Compiler and Program Logs

WebGL never throws on a bad shader: compileShader() and linkProgram() fail silently until you ask with getShaderParameter(shader, gl.COMPILE_STATUS) or getProgramParameter(program, gl.LINK_STATUS), then read getShaderInfoLog() or getProgramInfoLog(). Compile messages read ERROR: 0:line: 'token' : text, so printing the offending source line beside each one saves counting. Link errors catch what no single shader can see, such as a vertex output and fragment input of different types:

Annotating compile errors with their source lines, and a link errorJavaScript
function explain(source) {
  try { GLH.shader(gl, gl.FRAGMENT_SHADER, source); } catch (e) {
    const lines = source.split('\n');
    for (const [, line, text] of e.message.matchAll(/ERROR: \d+:(\d+): (.*)/g)) {
      console.log(`line ${line}: ${lines[line - 1].trim()}\n  ${text}`);
    }
  }
}
explain(`#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(0.5, 0.5, 0.5, 1);
  outColor.a = 1;
}`);
try {
  GLH.program(gl, `#version 300 es
    out vec2 vUV; void main() { vUV = vec2(0.0); gl_Position = vec4(0.0); }`, `#version 300 es
    precision mediump float; in vec3 vUV; out vec4 c; void main() { c = vec4(vUV, 1.0); }`);
} catch (e) { console.log(e.message); }
Output
line 6: outColor.a = 1;
  'assign' : cannot convert from 'const int' to 'mediump float'
Program failed to link:
Types of varying 'vUV' differ between VERTEX and FRAGMENT shaders.
FRAGMENT varying vUV does not match any VERTEX varying

Line 5 compiles because constructors convert; line 6 fails because assignment never does. Checking status makes the driver finish compiling, so with many programs compile and link them all first, then check, or poll COMPLETION_STATUS_KHR from the KHR_parallel_shader_compile extension, which this Chrome 1 exposes.

A live shader editor that annotates compile and link errors with their source linesHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  .row { display: flex; gap: 10px; max-width: 600px; }
  textarea { flex: 1; height: 170px; font: 11px/1.4 monospace; border: 1px solid #ccc; padding: 6px; }
  canvas { width: 200px; height: 170px; flex: none; border: 1px solid #ccc; }
  pre { max-width: 600px; margin: 6px 0 0; padding: 6px 8px; background: #fff; border: 1px solid #ddd;
        font-size: 11px; white-space: pre-wrap; min-height: 60px; }
  .bad { color: #b5452f; } .good { color: #3f7d3a; }
</style>
<div class="row">
  <textarea id="src" spellcheck="false">#version 300 es
precision mediump float;
in vec2 vUV;
out vec4 outColor;
void main() {
  outColor = vec4(vUV, 0.5, 1);
  outColor.a = 1;
}</textarea>
  <canvas id="view" width="200" height="170"></canvas>
</div>
<pre id="log"></pre>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const logBox = document.getElementById('log');
const vsSource = `#version 300 es
in vec2 aPos; out vec2 vUV;
void main() { vUV = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;
// Shown whenever the edited shader fails: red warning stripes.
const errorShader = `#version 300 es
precision mediump float; out vec4 outColor;
void main() { float s = step(0.5, fract((gl_FragCoord.x + gl_FragCoord.y) / 24.0));
  outColor = vec4(mix(vec3(0.71, 0.27, 0.18), vec3(0.97, 0.9, 0.85), s), 1.0); }`;

function compileWithLog(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);                       // never throws: we must ask
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return { shader };
  const lines = source.split('\n');
  const notes = [...gl.getShaderInfoLog(shader).matchAll(/ERROR: \d+:(\d+): (.*)/g)]
    .map(([, line, text]) => `line ${line}: ${(lines[line - 1] || '').trim()}\n  ${text}`);
  return { error: notes.join('\n') };
}
function link(vs, fs) {
  const program = gl.createProgram();
  gl.attachShader(program, vs); gl.attachShader(program, fs);
  gl.bindAttribLocation(program, 0, 'aPos');
  gl.linkProgram(program);
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) return { program };
  return { error: 'Link error: ' + gl.getProgramInfoLog(program).trim() };
}

const vs = compileWithLog(gl.VERTEX_SHADER, vsSource).shader;
const fallback = link(vs, compileWithLog(gl.FRAGMENT_SHADER, errorShader).shader).program;
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);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

function run() {
  const fs = compileWithLog(gl.FRAGMENT_SHADER, document.getElementById('src').value);
  const linked = fs.shader ? link(vs, fs.shader) : fs;
  gl.useProgram(linked.program || fallback);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  logBox.className = linked.error ? 'bad' : 'good';
  logBox.textContent = linked.error || 'Compiled and linked. (Try: in vec3 vUV; to see a link error.)';
}
document.getElementById('src').addEventListener('input', run);
run();
</script>