firefoxbindingwebglvbovao

ARRAY_BUFFER lost after VAO binded to null in WebGL


I created a VAO and bound the VBOs for the VAO to remember those bindings:

bufferV = gl.createBuffer ();
bufferI = gl.createBuffer ();
vertexArray = gl.createVertexArray ();
gl.bindVertexArray (vertexArray);
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, bufferI);
gl.bindBuffer (gl.ARRAY_BUFFER, bufferV);
gl.bindVertexArray (null);

In the animation loop, I rebind the VAO to rebind the VBOs and load some vertices and indices into them before rendering (I already created the shaders, the program, and activated the program):

gl.bindVertexArray (vertexArray);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (vertices), gl.STATIC_DRAW);
gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array (indices), gl.STATIC_DRAW);
// Call to gl.drawElements () here
gl.bindVertexArray (null);

And then, because I want to do this (don't ask me why), at the end of the animation loop, I unbind the VBOs:

gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer (gl.ARRAY_BUFFER, null);

A strange thing occurs. After the first loop, WebGL reports:

WebGL warning: bufferData: Buffer for `target` is null.

I checked. It means that after the first loop, binding the VAO does NOT restore the ARRAY_BUFFER VBO binding. But it still restores the ELEMENT_ARRAY_BUFFER VBO binding. May somebody explain why? I tested on Firefox 90.0.2 for Windows.

EXAMPLE TO HELP PEOPLE DISCOVERING HOW THE VAO STATE WORKS REGARDING THE BINDINGS TO ARRAY_BUFFER AND ELEMENT_ARRAY_BUFFER

Thanks to the answers to my question, I'm please to give this little standalone example which shows how this VAO state stuff works.

var program, vs, fs, bufferXY, bufferRGB, bufferI, vao, location, buffer;
var vertices = [    // x, y
    0.0, 1.0,
    1.0, -1.0,
    -1.0, -1.0
];
var colors = [  // r, g, b
     1.0, 0.0, 0.0,
      0.0, 1.0, 0.0,
       0.0, 0.0, 1.0
];
var indices = [
    0, 1, 2
];
var nbIndicesPerPrimitive=3, nbPrimitives=1;

// We create two shaders and a program.

vs = gl.createShader (gl.VERTEX_SHADER);
gl.shaderSource (vs, "#version 300 es\nin lowp vec2 A_xy;in lowp vec3 A_rgb;out lowp vec4 V_rgba;void main (void) { gl_Position = vec4 (A_xy, 0.0, 1.0); V_rgba = vec4 (A_rgb, 1.0); }");
gl.compileShader (vs);
if (!gl.getShaderParameter (vs, gl.COMPILE_STATUS))
    console.log (gl.getShaderInfoLog (vs));
fs = gl.createShader (gl.FRAGMENT_SHADER);
gl.shaderSource (fs, "#version 300 es\nin lowp vec4 V_rgba;out lowp vec4 rgba;void main (void) { rgba = V_rgba; }");
gl.compileShader (fs);
if (!gl.getShaderParameter (fs, gl.COMPILE_STATUS))
    console.log (gl.getShaderInfoLog (fs));
program = gl.createProgram ();
gl.attachShader (program, vs);
gl.attachShader (program, fs);
gl.linkProgram (program);
gl.useProgram (program);

// We create some VBOs and a VAO.

bufferXY = gl.createBuffer ();
bufferRGB = gl.createBuffer ();
bufferI = gl.createBuffer ();
vao = gl.createVertexArray ();

// We bind bufferXY to ARRAY_BUFFER. We can do it before the VAO is bound, because the ARRAY_BUFFER binding is global state. This means that whatever VBO is bound to ARRAY_BUFFER when vertexAttribPointer () is called, it will be memorized in the VAO state for the attribute which location is passed to this function.

gl.bindBuffer (gl.ARRAY_BUFFER, bufferXY);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (vertices), gl.STATIC_DRAW);

// We bind the VAO so that it starts memorizing calls to a set of functions. For WebGL 1, those are bindBuffer () when binding a VBO to ELEMENT_ARRAY_BUFFER, vertexAttribPointer (), enableVertexAttribute () and disableVertexAttribute (). WebGL 2 adds some functions to this set: vertexAttribIPointer (), vertexAttribI4[u]i[v] () and vertexAttribDivisor ().

gl.bindVertexArray (vao);

// We bind bufferI to ELEMENT_ARRAY_BUFFER. The VAO memorizes this binding.

gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, bufferI);
gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array (indices), gl.STATIC_DRAW);

// We configure attribute A_xy. The VAO memorizes this configuration, including the fact that bufferXY is bound to ARRAY_BUFFER when vertexAttribPointer () is called. So the VAO memorizes that A_xy should be read from bufferXY, 2 floats at a time, starting from offset 0, jumping 2 floats after one read.

location = gl.getAttribLocation (program, "A_xy");
gl.vertexAttribPointer (location, 2, gl.FLOAT, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray (location);

// We configure attribute A_rgb. For this, we must bind bufferRGB to ARRAY_BUFFER. The VAO memorizes this configuration, including the fact that bufferRGB is bound to ARRAY_BUFFER when vertexAttribPointer () is called. So the VBO memorizes that A_rgb should be read from bufferRGB, 3 floats at a time, starting from offset 0, jumping 3 floats after one read. Notice that binding bufferRGB to ARRAY_BUFFER does not change anything to what the VAO memorized about A_xyz previously.

gl.bindBuffer (gl.ARRAY_BUFFER, bufferRGB);
gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (colors), gl.STATIC_DRAW);
location = gl.getAttribLocation (program, "A_rgb");
gl.vertexAttribPointer (location, 3, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.enableVertexAttribArray (location);

// We unbind the VAO so that it stops memorizing.

gl.bindVertexArray (null);

// We bind null to ARRAY_BUFFER and to ELEMENT_ARRAY_BUFFER. This will show that all the bindings to ARRAY_BUFFER and ELEMENT_ARRAY_BUFFER will be remembered when the VAO is bound again before rendering.

gl.bindBuffer (gl.ARRAY_BUFFER, null);
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);

// We start the rendering process.

gl.clearColor (0.0, 0.0, 0.0, 1.0);
gl.clear (gl.COLOR_BUFFER_BIT);

// We bind the VAO so that it restores its state, among which the bindings of bufferI to ELEMENT_ARRAY_BUFFER, and the bindings of bufferXY to ARRAY_BUFFER for attribute A_xy and of bufferRGB to ARRAY_BUFFER for attribute A_rgb.

gl.bindVertexArray (vao);
gl.drawElements (gl.TRIANGLES, nbIndicesPerPrimitive * nbPrimitives, gl.UNSIGNED_SHORT, 0);
gl.bindVertexArray (null);

// We clean everything.

gl.useProgram (null);
gl.deleteProgram (program);
gl.deleteShader (vs);
gl.deleteShader (fs);
gl.deleteVertexArray (vao);
gl.deleteBuffer (bufferXY);
gl.deleteBuffer (bufferRGB);
gl.deleteBuffer (bufferI);

Solution

  • The ARRAY_BUFFER is a global state. However, the ELEMENT_ARRAY_BUFFER buffer binding is stored in the Vertex Array Object. Therefor you need to bind the Vertex Array Object when you want to change the ELEMENT_ARRAY_BUFFER binding:

    gl.bindVertexArray (vertexArray);
    gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);
    

    Each attribute which is stated in the VAOs state vector may refer to a different ARRAY_BUFFER. This reference is stored when gl.vertexAttribPointer is called. Then the buffer which is currently bound to the ARRAY_BUFFER target, is associated to the specified attribute index and the the buffer object reference is stored in the state vector of the currently bound VAO.
    However the index buffer is a state of the VAO. A VAO can only refer to 1 index buffer. If a buffer is bound to the ELEMENT_ARRAY_BUFFER target, the buffer reference is assigned to the currently bound Vertex Array Object.