Search code examples
webglwebgl2webgl-extensions

Debugging in WebGL


I am learning WebGL and I can feel that my speed is so slow because I am having a hard time debugging my code. Is there any extension or tool with help of which I can know the value of buffer, attribpointer, matrixes, etc.

I googled and learned about chrome extension spector.js but this does not work with me. I think it supposes to show me frames or context but when I click it shows nothing.



enter image description here



When I click red button after a few seconds it shows: No frames detected. Try moving the camera or implementing requestAnimationFrame.


Solution

  • Yes, WebGL is hard to debug and I'm not sure anything will make it a whole lot easier. Most bugs are not something a debugger can find that easily. Certain bugs like un-renderable textures or buffers on the correct size already get reported by the browser. Other bugs though are usually math bugs, logic bugs, or data bugs. For example there is no easy way to step through a WebGL shader.

    In any case, if you want to use spector you need to structure your code to be spector friendly. Spector is looking for frames based on requestAnimationFrame.

    So, let's take this example which is the last example from this page.

    The code has a main function that looks like this

    
    function main() {
      // Get A WebGL context
      /** @type {HTMLCanvasElement} */
      var canvas = document.querySelector("#canvas");
      var gl = canvas.getContext("webgl");
      if (!gl) {
        return;
      }
    
    
      // setup GLSL program
      var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
      ...
    }
    
    main();
    

    I changed it to this. I renamed main to init and made it so I pass in the gl context.

    function init(gl) {
      // setup GLSL program
      var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
    
      ...
    }
    

    Then I made a new main that looks like this

    
    function main() {
      // Get A WebGL context
      /** @type {HTMLCanvasElement} */
      var canvas = document.querySelector("#canvas");
      var gl = canvas.getContext("webgl");
      if (!gl) {
        return;
      }
    
      const startElem = document.querySelector('button');
      startElem.addEventListener('click', start, {once: true});
    
      function start() {
        // run the initialization in rAF since spector only captures inside rAF events
        requestAnimationFrame(() => {
          init(gl);
        });
        // make so more frames so spector has something to look at.
        // Note: a normal webgl app would have a rAF loop: https://webglfundamentals.org/webgl/lessons/webgl-animation.html
        requestAnimationFrame(() => {});
        requestAnimationFrame(() => {});
        requestAnimationFrame(() => {});
        requestAnimationFrame(() => {});
        requestAnimationFrame(() => {});
      }
    }
    
    main();
    

    And I added a button to my html

    <button type="button">start</button>
    <canvas id="canvas"></canvas>
    

    The code is the way it is because we need to get a webgl context first or else spector will not notice the canvas (there will be nothing to select). After when turn to turn on spector, and only after that click the start button to run our code. We need to execute our code in a requestAnimationFrame because that is what spector is looking for. It only records WebGL functions between frames.

    enter image description here

    Whether or not it will help you find any bugs though is another matter.

    note that, if you are on Mac, Safari also has a WebGL debugger built in but just like spector it's only designed for frames. It requires you to draw something each frame so this worked

      function start() {
        // I'm not sure running the init code in a rAF is important in Safari but it worked
        requestAnimationFrame(() => {
          init(gl);
        });
        // by default safari tries to capture 3 frames so let's give it some frames
        // Note: a normal webgl app would have a rAF loop: https://webglfundamentals.org/webgl/lessons/webgl-animation.html
        requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
        requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
        requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
        requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
        requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
      }
    

    enter image description here

    Another thing you can do is use a helper to help find errors.

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

    You can either download it or you can just include it via the link above. Example (open the javascript console to see the error)

    const gl = document.createElement('canvas').getContext('webgl');
    
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.vertexAttribPointer(0, 1, gl.BYE, false, 0, 0);
    <script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin></script>

    The helper has an API you can read about in its docs that lets you label objects and turn on/off various checks.


    Update (2024)

    spector can be used as a library. Download it and then you can call it directly and tell it start capturing

    https://github.com/BabylonJS/Spector.js?tab=readme-ov-file#use-as-a-script-reference