webgl-lint

WebGL Lint

WebGL lint is a script you can throw into your WebGL project to check for common WebGL errors.

Example

Open the JavaScript console. You’ll see the first example prints fewer errors and less info where as the second prints much more info.

Usage

<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin="anonymous"></script>

or

import 'https://greggman.github.io/webgl-lint/webgl-lint.js';

WebGL Lint throws 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! 😉

GMAN_debug_helper extension

WebGL Lint adds a special extension GMAN_debug_helper with these functions

Configuration

You don’t need to configure anything to use in general but there are some settings for special needs.

There 2 ways to configure

  1. 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,
      });
    }
    
  2. Via an HTML dataset attribute

    Example:

    <script
      src="https://github.greggman.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 with querySelectorAll('[data-gman-debug-helper]') and applies the options in the order found.

Naming your WebGL objects (buffers, textures, programs, etc..)

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

  1. 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');
    
  2. 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.

  3. 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 of gl.createXXX

  4. Use your own API.

    Lots of people have wrapped WebGL themselves with things like class Texture and class 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’)…

Just for symmetry 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'

Suggestions?

https://github.com/greggman/webgl-lint/issues

Development

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.

License

MIT