perspective-dev/perspective · error · Error

Shader link error [ ]

Error message

Shader link error [${name}]: ${info}

What it means

After attaching compiled vertex and fragment shaders, getOrCreate calls gl.linkProgram and checks LINK_STATUS. Linking fails when the two shaders are incompatible (varying/uniform mismatches, missing main, too many uniforms/attributives for the GPU limits) even though each shader compiled individually. The registry deletes the program and both shaders before throwing, embedding the driver's program info log in the message.

Solutions

  1. Read the ${info} log — link errors name the mismatched varying/uniform; align declarations between the vertex and fragment shaders (same name, type, and precision).
  2. Check that all varyings written in the vertex shader are declared with identical type in the fragment shader (or vice versa).
  3. Reduce uniform count if the log mentions limits; consolidate uniforms into a uniform block (WebGL2) or pack values into vectors.
  4. Ensure both shaders use the same GLSL version and profile (both '#version 300 es' or both default).
  5. If the pair is generated, add an assertion/test that links the pair offline (e.g. via a headless GL context) in CI.

Example fix

// before: vertex shader emits a varying the fragment shader never declares
// vert: varying vec3 vNormal; ... vNormal = n;
// frag: (missing declaration)
// after
// vert: varying vec3 vNormal; ... vNormal = n;
// frag: varying vec3 vNormal; ...
Defensive patterns

Strategy: try-catch

Validate before calling

function validateShaderPair(vert: string, frag: string): string | null {
  const vs = new Set([...vert.matchAll(/varying\s+\w+\s+(\w+)/g)].map(m => m[1]));
  const fs = new Set([...frag.matchAll(/varying\s+\w+\s+(\w+)/g)].map(m => m[1]));
  for (const v of vs) if (!fs.has(v)) return `varying "${v}" written in vertex shader but not declared in fragment shader`;
  return null;
}

Type guard

function isProgramLinked(gl: WebGL2RenderingContext, program: WebGLProgram): boolean {
  return gl.getProgramParameter(program, gl.LINK_STATUS) === true;
}

Try / catch

let program: WebGLProgram;
try {
  program = registry.getOrCreate(name, vertSrc, fragSrc);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("Shader link error")) {
    console.error(`Link failure for "${name}":`, e.message);
    program = registry.getOrCreate(fallbackName, fallbackVert, fallbackFrag);
  } else throw e;
}

Prevention

When it happens

Trigger: getOrCreate (or precompile) with vertex/fragment shader pairs whose varyings do not match in name/type/count, an inactive or over-limit uniform set, a fragment shader lacking main(), or a driver resource limit being exceeded during gl.linkProgram.

Common situations: Editing one shader of a pair and forgetting to update the matching varying declarations; renaming a varying in the vertex shader only; exceeding max fragment uniforms on low-end mobile GPUs; mixing a WebGL1-style vertex shader with a '#version 300 es' fragment shader.

Related errors


AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09). Data as JSON: /api/errors/8f1474a5139d81b4. Report an issue: GitHub.

Appendix: source

Thrown at packages/viewer-charts/src/ts/webgl/shader-registry.ts:79

        gl.compileShader(frag);
        if (!gl.getShaderParameter(frag, gl.COMPILE_STATUS)) {
            const info = gl.getShaderInfoLog(frag);
            gl.deleteShader(vert);
            gl.deleteShader(frag);
            throw new Error(`Fragment shader compile error [${name}]: ${info}`);
        }

        program = gl.createProgram()!;
        gl.attachShader(program, vert);
        gl.attachShader(program, frag);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            const info = gl.getProgramInfoLog(program);
            gl.deleteProgram(program);
            gl.deleteShader(vert);
            gl.deleteShader(frag);
            throw new Error(`Shader link error [${name}]: ${info}`);
        }

        // Shaders can be deleted after linking
        gl.deleteShader(vert);
        gl.deleteShader(frag);

        this._programs.set(name, program);
        return program;
    }

    releaseAll(): void {
        for (const program of this._programs.values()) {
            this._gl.deleteProgram(program);
        }

        this._programs.clear();
    }
}

View on GitHub (pinned to 11c8238c0c)