How to determine if a material has failed to compile?

Hello! I’d like to detect if a ShaderMaterial has failed to compile on a platform so I can inform the user that their device has quirks that do not allow a material to render / programmatically use a more general fallback. However from what I can tell it’s not possible to try to compile a material and then determine whether it failed in code. Is there a way to do it?

Here’s what I’m trying:

const renderer = new THREE.WebGLRenderer();
const camera = new THREE.PerspectiveCamera();
const material = new THREE.ShaderMaterial( {
    vertexShader: /* glsl */`
        void main() {
            fail
        }
    `,
    fragmentShader: /* glsl */`
        void main() {
            fail            
        }
    `,
} );

const mesh = new THREE.Mesh( new THREE.BoxGeometry(), material );
renderer.compile( mesh, camera );

I’m aware that renderer.info.programs can provide whether the shader compiled in the diagnostics field but how do you correlate the material you just rendered with the current set of define with the related program in the array?

Thanks!

2 Likes

Did you ask gpt? Appologies if I sound naive but

const gl = canvas.getContext('webgl');

// Compile shader
const shader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(shader, 'your_shader_code_here');
gl.compileShader(shader);

// Check if compilation succeeded
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success) {
  const error = gl.getShaderInfoLog(shader);
  console.error(`Shader compilation failed: ${error}`);
}

gl.COMPILE_STATUS looks like the flag to go to

Thanks but this is effectively what three.js is doing internally. I need a way to let three.js compile the material and do a variety of the preprocessing in addition retaining the program handle to avoid double compilation times if it is successful.

1 Like

Here are other ways (all have a lot of disadvantages):

  1. Look at the Three.js source. If the result of shader compilation (or the shader program) is stored in some variable, and if you have access to it from outside, you can check it.
  2. Draw something in a 1x1 canvas and check the rendered image. If it is correct, the shader is fine.
  3. If shader failure causes throwing an error, use try...catch to catch any error and then check whether it is the specific shader compilation error.

I just spent some time digging into it, and got stumped at the same place you did: renderer.info.programs[x].diagnostics doesn’t really give you a way to trace back to which material failed compilation.

However, I did find that renderer.info.programs.length grows by 1 the first time a new WebGLProgram is created. If one is getting re-used, the length doesn’t go up. Maybe compiling one at a time is a roundabout way to find out when an error occurs?

  1. Run renderer.compile(mesh, cam),
  2. Check the programs array length, if it went up:
  3. Check for .diagnostics property on last entry of the array.
  4. If .diagnostics.runnable is false, then your latest shader compilation failed.

It’s the only thing I could think of with the digging I could do.

Update:

Does your case allow for custom material names? If so, you could use that! In the demo below, I assigned my-custom-shader as the material name, and then I can access it via WebGLProgram.name

const material = new THREE.ShaderMaterial( {
	vertexShader: `void main() {gl_Position = vec4(1.0);}`,
	fragmentShader: `void main() {gl_FragColor = vec4();}`, // Will fail
	type: "my-custom-shader"
} );

const camera = new THREE.Camera();
const mesh = new THREE.Mesh( new THREE.BoxGeometry(), material );
this.renderer.compile( mesh, camera );

const programs = this.renderer.info.programs;

for (let i = 0; i < programs.length; i++) {
	if(programs[i].diagnostics && programs[i].diagnostics.runnable == false) {
		console.log("The following shader failed:");
		console.log(programs[i].name);
	}
}
3 Likes

@PavelBoytchev - thanks but none of these solutions are viable.

@marquizzo - thanks I was hoping there was a way to find programs associated with the material from that list in case it had already been used but I’m guessing that’s just not the case atm. Unfortunately modifying the type will only work on the first render, as well. I’ll probably wind up going for for this with the caveat that it will only work if the material has not been rendered before.

cc @Mugen87 any thoughts on this? How would you feel about some kind of an API that provides the associated compilation reports for materials? Is there a simple way to do it? Currently afaict there’s no procedural way to tell if a shader has failed to compile. I’m trying to do this so I can detect if a device has a device-specific compilation quirk that will cause it to fail to render.

Would something like the following would be helpful? WebGLRenderer: Allow custom function for shader compilation error reporting. by mochimisu · Pull Request #25092 · mrdoob/three.js · GitHub

3 Likes

Unfortunately no. This doesn’t help if the material failed to compile once already and you’re trying to check if the material is renderable - though that suggestion is better and more clear than the current method of checking programs array has expanded if the material hasn’t been used yet.

The reality of it is that this will probably only be relevant on page load before anything has attempted to be rendered anyway. But three.js has an internal map from materials to these programs, anyway, so it should be possible to expose the errors associated with a material. Just figured it would be nice to make this more ergonomic and work in the general case.