What is the recommended way to get line number errors in ShaderMaterial glsl shader code?

Hello,

Goal: I want to be able to access the glsl shader source error messages when trying link a program for THREE.ShaderMaterial.

Reason: The error message can be parsed to get the line number that will allow me to highlight the syntax error in a browser editor.

I’m working on my own browser IDE for editing GLSL shader code. And I’m using THREE.ShaderMaterial applied to a GLTF object to preview the shader source changes in real time.

What I tried: I’m able to ‘intercept’ calls made with gl.attachShader

        gl.attachShader = function(program, shader) {

            interceptingAttachShader(shader);
            return originalAttachShader(program, shader);

        };

This is perfect for being able to see the full shader source being linked to a program. I thought I could apply the same intercepting technique to either hook into the console error call inside three.module.js

console.error('three.module.js:17432 THREE.WebGLProgram: shader error:...)

OR… hook into gl.linkProgram( program ); then read the error myself with webgl’s getProgramInfoLog.

However, it looks like caching is getting in the way…

The Challenge: If I change the GLSL source code in my browser editor, this causes a new THREE.ShaderMaterial to be generated with the updated GLSL source code. But if this source code has already been used… a new program will not be created. Instead the cached program is used. And when a chached program is used, my hooks into either console.error or gl.linkProgram won’t be called.

There are many different ways I can think of to get around this. But, I wanted to check with you to see if there is a simple way (the question I have below).

Question: Is there some event/function/property I can use in order to see when a program is changed (either cached or brand new)… every time I recreate the material with new THREE.ShaderMaterial?

Cheers!

Well, there is only Material.onBeforeCompile() which is executed when a material requires a program change. However, this does not mean that a shader compilation is triggered. It might be possible to use an existing shader program from the cache.

So you can’t use this callback for your use case. It seems you have to modify the renderer of three.js if you need more details about the shader compilation process.

Thank you Mugen87,

The approach I ended up taking is to “patch-in” to the existing function calls and caching errors:

  • patch into: gl.attachShader
  • patch into: console.error
  • cache any shader code that contains an error

By “patch-in” I mean, in my code I reassign these methods like this:

            const originalConsoleError = console.error.bind(gl);

            console.error = function(
                summary, getError, programParamCode, programParam, 
                programLogExample, programLog, vertexErrors, fragmentErrors
            ) {
            // interceptConsoleError is a function I created to intercept error messages
                interceptConsoleError(
                    summary, getError, programParamCode, programParam, 
                    programLogExample, programLog, vertexErrors, fragmentErrors
                );
                return originalConsoleError(
                    summary, getError, programParamCode, programParam, 
                    programLogExample, programLog, vertexErrors, fragmentErrors
                );
            };

So in the above code, any time console.error is called my own function, interceptConsoleError, gets triggered first.

That means that any time the three.module.js throws a shader error, I intercept all of the information printed to the console…

three.module.js:

console.error( 'THREE.WebGLProgram: shader error: ', gl ... );

Similarly, I’m also “patching” into gl.attachShader using the same technique:

            const originalAttachShader = gl.attachShader.bind(gl);
            gl.attachShader = function(program, shader) {
                interceptingAttachShader(shader);
                return originalAttachShader(program, shader);
            };

Any time three.module.js calls gl.attachShader, I will intercept the shader program. This allows me to see the full shader code (including the prepended shader chunks).

The final step is this. Any time I see any errors. I cache the shader code. So if the user writes an error, in the shader source code, then fixes the error, then finally causes the same error again, the error can be recalled from my own cache (since the error will not be re-thrown in the three.module.js).

I’m sure there are other approaches, but this one seems to be working, without having to change any three.js source code.

Thank you for your help Mugen87. And if anyone else out there is doing something like this, I hope this post helps.

Cheers!