WebGL lint is a script you can throw into your WebGL project to check for common WebGL errors.
-
Calls
getError
after every function and throws if there was an error. -
Checks that no arguments to any functions are
undefined
.Pass
0
orfalse
ornull
where you mean0
orfalse
ornull
. -
Checks that no numbers or values in arrays of numbers are
NaN
. -
Checks that all non-sampler uniforms are set. (see configuration below)
-
Checks that uniform matrices are not all zero.
-
Warns if you try to access an undefined uniform.
-
Checks for out of range access issues and will tell you which attribute/s are out of range
-
Checks that shaders compile. On failure prints the shader and tries to highlight errors.
-
Checks that programs link. On failure prints the attached shaders.
-
If there is a WebGL error it tries to provide more info about why
-
for framebuffer feedback it will tell you which textures assigned to which uniforms and which attachments
-
for other errors it will try print extra info where possible.
-
it lets you name webgl objects so those names can be shown in the error messages.
-
Open the JavaScript console. You'll see the first example prints fewer errors and less info where as the second prints much more info.
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin></script>
or
import 'https://greggman.github.io/webgl-lint/webgl-lint.js';
WebGL Lint throw
s a JavaScript exception when there is an issue so if you are
using try
/catch
to catch errors you might need to print the exceptions
inside your catch block. You can also turn on "pause on exception" on your
JavaScript debugger.
Throwing seemed more a appropriate than just printing an error because if you
get an error you should fix it! I tried the script out with all the
three.js examples. It found 1 real bug and several half bugs.
By half bugs I mean there were several examples that functioned but were actually
passing NaN
or null
in the wrong places for a few frames or they were not setting
uniforms that probably should have been set. Arguably it's
better to fix those so that you can continue to use the helper to find real
errors. In any case most of examples ran without error so you can do it
too! 😉
WebGL Lint adds a special extension GMAN_debug_helper
with these functions
tagObject(obj: WebGLObject, name: string): void
- see naming belowuntagObject(obj: WebGLObject): void
- see naming belowgetTagForObject(obj: WebGLObject): string
- see naming belowsetConfiguration(settings): void
- see configuration belowdisable(): void
- turns off the checkinggetAndResetRedundantCallInfo(): RedundantCallInfo
- see below
You don't need to configure anything to use in general but there are some settings for special needs.
-
maxDrawCalls
(default: 1000)Turns off the checking after this many draw calls. Set to 0 to check forever.
-
failUnsetUniforms
: (default: true)Checks that you set uniforms except for samplers and fails if you didn't. It's a common error to forget to set a uniform or to mis-spell the name of a uniform and therefore not set the real one. The common exception is samplers because uniforms default to 0 so not setting a sampler means use texture unit 0 so samplers are not checked.
Of course maybe you're not initializing some uniforms on purpose so you can turn off this check. I'd recommend setting them so you get the benefit of this check finding errors.
Note: uniform blocks are not checked directly. They are checked by WebGL itself in the sense that if you fail to provide uniform buffers for your uniform blocks you'll get an error but there is no easy way to check that you set them.
-
failUnsetSamplerUniforms
: (default: false)See above why sampler uniforms are not checked by default. You can force them to be checked by this setting.
-
failZeroMatrixUniforms
: (default: true)Checks that a uniform matrix is not all zeros. It's a common source of errors to forget to set a matrix to the identity and it seems uncommon to have an all zero matrix. If you have a reason a matrix needs to be all zeros you may want to turn this off.
-
failUnrenderableTextures
: (default: true)Unrenderable textures are not an error in WebGL, they just don't render. WebGL itself usually print's a warning but it's usually fairly cryptic just telling you an unrenderable texture exists but not much else.
Examples of unrenderable textures are non-power of 2 textures in WebGL1 with filtering set to need mips and wrap not set to
CLAMP_TO_EDGE
or in both WebGL and WebGL2 would be mips of different internal formats or the wrong size. -
failUndefinedUniforms
: (default: false)WebGL by default returns
null
when you callgl.getUniformLocation
for a uniform that does not exist. It then silently ignores callinggl.uniformXXX
if the location isnull
. This is great when you're editing a shader in that if you remove a uniform from the shader your code that is still setting the old uniform will keep working.For example if you are debugging and you go to the bottom of your fragment shader and add
gl_FragColor = vec4(1, 0, 0, 1);
all the uniforms in your fragment shader will be optimized out. If WebGL suddenly issues errors trying to set those it would be much more frustrating to debug. Conversely though, if you have a typo, for example you want to look up the location of'u_color'
and you typegl.getUniformLocation(prg, 'uColor')
you'll get no error and it will likely take you a while to find your typo.So, by default webgl-lint only prints a warning for undefined uniforms. You can make throw by setting
failUndefinedUniforms
totrue
. -
failBadShadersAndPrograms
: (default: true)Most WebGL programs expect all shaders to compile and all programs to link but often programmers don't check for errors. While it's likely they'd get an error about a bad program further in their code, at that point it's likely too late to tell them it's because the program didn't compile or link. Instead the message will just be something like "no valid program in use".
If you're working on a project that expects shaders to fail to compile and/or programs to link you can set this to
false
. -
warnUndefinedUniforms
: (default: true)See
failUndefinedUniforms
. Setting this to false turns off warnings about undefined uniforms. -
ignoreUniforms
: (default: [])Lets you configure certain uniforms not to be checked. This way you can turn off checking for certain uniforms if they don't obey the rules above and still keep the rules on for other uniforms. This configuration is additive. In other words
ext.setConfiguration({ignoreUniforms: ['foo', 'bar']}); ext.setConfiguration({ignoreUniforms: ['baz']});
Ignores uniforms called 'foo', 'bar', and 'baz'.
-
throwOnError
: (default: true)The default is to throw an exception on error. This has several benefits.
-
It encourages you to fix the bug.
-
You'll get a stack trace which you can drill down to find the bug.
-
If you use "pause on exception" in your browser's dev tools you'll get a live stack trace where you can explore all the local variables and state of your program.
But, there might be times when you can't avoid the error, say you're running a 3rd party library that gets errors. You should go politely ask them to fix the bug or better, fix it yourself and send them a pull request. In any case, if you just want it to print an error instead of throw then you can set
throwOnError
to false. -
-
makeDefaultTags
: (default: true)If true, all objects get a default tag, Example
*UNTAGGED:Buffer1
,*UNTAGGED:Buffer2
etc. This is a minor convenience to have something to distinguish one object from another though it's highly recommended you tag your objects. (See naming).The only reason to turn this off is if you're creating and deleting lots of objects and you want to make sure tags are not leaking memory since tags are never deleted automatically. (See "naming).
There 2 ways to configure
-
Via the extension and JavaScript.
Example:
const gl = someCanvas.getContext('webgl'); const ext = gl.getExtension('GMAN_debug_helper'); if (ext) { ext.setConfiguration({ maxDrawCalls: 2000, failUnsetSamplerUniforms: true, }); }
-
Via an HTML dataset attribute
Example:
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" data-gman-debug-helper=' { "maxDrawCalls": 2000, "failUnsetSamplerUniforms": true } '> </script>
Note: (1) the setting string must be valid JSON. (2) any tag will do,
<div>
,<span>
, etc. as the script just applies all tags it finds withquerySelectorAll('[data-gman-debug-helper]')
and applies the options in the order found.
Using the extension you can name your objects. This way when an error is printed the names will be inserted where appropriate.
const ext = gl.getExtension('GMAN_debug_helper');
const tex = gl.createTexture();
ext.tagObject(tex, 'background-tex');
Now if you get an error related to tex
you might get an told it's related to 'background-tex'
instead of just that you got an error.
4 suggestions for using naming
-
make some helpers
const ext = gl.getExtension('GMAN_debug_helper'); const tagObject = ext ? ext.tagObject.bind(ext) : () => ();
now you can just unconditionally tag things and if the extension does not exist it will just be a no-op.
const tex = gl.createTexture(); tagObject(tex, 'checkerboard');
-
wrap the creations functions
const ext = gl.getExtension('GMAN_debug_helper'); if (ext) { Object.keys(gl.__proto__) .filter(name => name.startsWith('create')) .forEach(name => { const origFn = gl[name]; if (origFn) { gl[name] = function(...args) { const obj = origFn.call(this, ...args); if (obj) { ext.tagObject(obj, args[args.length - 1] || '*unknown*'); } return obj; } } }); }
Which you use like this
const shader = gl.createShader(gl.VERTEX_SHADER, 'phongVertexShader'); const tex = gl.createTexture('tree-texture');
and they'll still work in normal WebGL as it will ignore the extra parameter.
-
Same as above but not wrapped
const ext = gl.getExtension('GMAN_debug_helper'); const api = Object.fromEntries( Object.keys(gl.__proto__) .filter(name => name.startsWith('create')) .map(name => { const func = (ext && gl[name]) ? function(...args) { const obj = gl[name](...args); if (obj) { ext.tagObject(obj, args[args.length - 1] || '*unknown*'); } return obj; } : function(...args) { return gl[name](...args); }; return [name, func]; }));
Which you use like this
const shader = api.createShader(gl.VERTEX_SHADER, 'phongVertexShader'); const tex = api.createTexture('tree-texture');
If you're allergic to hacking native APIs this is better but you have to remember to use
api.createXXX
instead ofgl.createXXX
-
Use your own API.
Lots of people have wrapped WebGL themselves with things like
class Texture
andclass Framebuffer
or other functions. Those would be a good place to integrate tagging.
As a simple example, naming buffers after the attributes they'll be used with (eg. 'position', 'normal'), naming textures by the URL of the img where they get their data. Naming vertex array objects by the model ('tree', 'car', 'house'), naming framebuffers by their usage ('shadow-depth', 'post-processing'), naming programs by what they do ('phong-shading', 'sky-box')...
You can also untag an object with
ext.untagObject(someObj);
Arguably for debugging you probably don't want to untag. The problem with
untagging is if you untag and then have a bug where you reuse an untagged
object, for example using an object you deleted, there will be no tag for the
object, so your error will just say something like "error: tried to use
Texture(unknown)" instead of "error: tried to use Texture(yourTag)". At the same
time, if you're running WebGL-lint indefinitely (see maxDrawCalls
) and
creating and deleting lots of objects, for example sync objects, there is a tiny
bit of memory involved keeping the label of each one so manually untagging
should mean you are not leaking memory. Honestly I wouldn't worry about
untagging but it's here just in case you've got special needs. Also see
makeDefaultTags
above.
The extension also includes getTagForObject
if you want to look up
what string you tagged an object with
const buf = gl.createBuffer();
ext.tagObject(buf, 'normals');
console.log(ext.getTagForObject(buf)); // prints 'normals'
An example of a redundant call is calling gl.useProgram
with the same program
or calling gl.vertexAttribPointer
with the same parameters and the same buffer.
You can get a count to date of the redundant state setting WebGL-Lint has detected
by calling ext.getAndResetRedundantCallInfo()
Example:
function render() {
// .. do stuff with webgl ..
const info = ext.getAndResetRedundantCallInfo();
console.log(JSON.stringify(info));
requestAnimationFrame(render);
}
Alternatively you can try adding this script to your page (instead of webgl-lint.js
) and it will
attempt to print redundant call info for you.
<script type="module" src="https://greggman.github.io/webgl-lint/webgl-lint-check-redundant-state-setting.js"></script>
or
import 'https://greggman.github.io/webgl-lint/webgl-lint-check-redundant-state-setting.js';
Note: WebGL-Lint does not check every possible redundant set setting. At the moment it checks
bindBuffer
bindFramebuffer
bindRenderbuffer
bindSampler
bindTexture
bindVertexArray
enable
disable
vertexAttribPointer
vertexAttribIPointer
It's not important to avoid 100% of redundant state setting. Rather, you can use this feature to see if you have too much redundant state setting. For example if you're making 2000 draw calls and you see 500-5000 redundant state setting counts then you should probably look into adding some state tracking in your own code or organizing the way calls happen so state is not set. It's not so much that setting state more than once is bad, it's rather than it's just proof your code is doing extra work. If it's a lot of extra work then you probably want to look into it. If it's only a small amount of extra work then don't worry about it.
https://github.com/greggman/webgl-lint/issues
You can run the tests with the un-merged code with http://localhost:8080/test/?src=true
. You can also filter the tests with grep=
as in
http://localhost:8080/test/?grep=shader
or both
http://localhost:8080/test/?src=true&grep=shader
.