Search code examples
javascript3dwebglmapbox-gl-jswebgl2

"GL_INVALID_OPERATION: Insufficient buffer size." after variable number of render calls


I'm using MapboxGL to render geojson as a polygon onto a map. The page loads and renders correctly but when I move the map around, I get this error after some number (usually between ~30-100) of render calls and the polygon disappears.

On chrome:

GL_INVALID_OPERATION: Insufficient buffer size. (Chrome)

On firefox:

WebGL warning: drawElementsInstanced: Index buffer too small. (FF)

I've managed to (I think) narrow it down to an issue with:

gl.drawElements(gl.TRIANGLES, tResult.triangleIndices.length, gl.UNSIGNED_SHORT, 0);

Using the Spector.js extension I saw that the second argument (count) in drawElements method varies each call (tResult.triangleIndices.length does not vary when I log it). This may be a red herring because it works for 30-100 renders.

I've tried various combinations of data types for the buffer type in bufferData() and drawElements() but no luck.

I've tried using lower poly meshes.

I've tried calling various gl.clear methods each render pass.

This probably isn't relevant but fwiw it's a vue-cli app so it's loading libraries from npm.

Here's the relevant code:

Init:

 [...setting up shaders and stuff]
 // link the two shaders into a WebGL program
 this.program = gl.createProgram();
 gl.attachShader(this.program, vertexShader);
 gl.attachShader(this.program, fragmentShader);
 gl.linkProgram(this.program);

 this.aPos = gl.getAttribLocation(this.program, 'a_pos');
 gl.clearColor(1.0, 1.0, 1.0, 1.0);  // Clear to black, fully opaque

// create and initialize a WebGLBuffer to store vertex and color data
 this.buffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tResult.triangleLocations), gl.STATIC_DRAW);

 this.verticesIndexBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer);
 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new 
   Uint16Array(tResult.triangleIndices), gl.STATIC_DRAW); 
 gl.clear(gl.COLOR_BUFFER_BIT);
 gl.enable(gl.CULL_FACE);
 gl.cullFace(gl.BACK);

Render:

render: function (gl, matrix) {

gl.useProgram(this.program);
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program, 'u_matrix'), false,matrix);

gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.enableVertexAttribArray(this.aPos);
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
gl.drawElements(gl.TRIANGLES, tResult.triangleIndices.length, gl.UNSIGNED_SHORT, 0);                   
}

Solution

  • The problem looks to me like you're not setting the ELEMENT_ARRAY_BUFFER when you render.

    See this webgl state diagram

    if you're not using vertex arrays (which you're not) then attributes and buffer bindings are global state. So if something changes that state between the time you initialize things and the time you render things (like mapbox rendering its stuff) then when you render you'll be using different buffers than you setup in init.

    Try adding

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.verticesIndexBuffer);
    

    Before calling drawElements

    Similarly the other state you setup like face culling may or may not last until render time if anything else is happening on the same webgl context.

    If that's not it you could try also webgl-lint for slightly informative error. Especially if you label your buffers