1
votes

This question is specific to WebGL and assumes VAOs are not available.

I'm trying to make some little improvements to a 3D engine by limiting the number of low-level state changes. But it turns out I'm a bit confused about the proper way to use bindBuffer and vertexAttribPointer.

Let's say I want to draw 2 objects:

  • The first object make use of two buffers A and C with an element buffer E ;
  • the second object uses buffers B and C with the same element buffer E.

Buffers A and B use the same layout and are both referenced by location 0 while C is referenced by location 1.

Initially, ARRAY_BUFFER_BINDING points to null while ELEMENT_ARRAY_BUFFER_BINDING points to E.

The redundancy checker outputs the following with (A, B, C, E) = (3, 6, 5, 2):

WebGL Inspector trace

Which means that:

  1. bindBuffer(ELEMENT_ARRAY_BUFFER, [Buffer 2]) is unnecessary
  2. vertexAttribPointer(1, 2, FLOAT, false, 0, 0) could've been avoided

Since WebGL can directly read ELEMENT_ARRAY_BUFFER_BINDING to know where indices are stored, 1. makes sense to me.

However, 2. implies that the buffer layout is stored inside the VBO, which is wrong because Buffer A and B are not seen as redundant on lines 15 and 30. (Several frames were rendered already, so they should have kept their state)

I think I'm confused about how drawElements know what buffer to use and where/when buffer layouts are stored.

What is the optimal use of bindBuffer and vertexAttribPointer for this example case and why?

1

1 Answers

0
votes

Actually I think I find out by simply looking at the source of the redundancy checker.

There are 2 important things to know :

  • Buffer layouts are bound per location and not per VBO.
  • vertexAttribPointer will also assign the current buffer to the given location

Internally, WebGL retains 6 parameters per location :

VERTEX_ATTRIB_ARRAY_SIZE_X
VERTEX_ATTRIB_ARRAY_TYPE_X
VERTEX_ATTRIB_ARRAY_NORMALIZED_X
VERTEX_ATTRIB_ARRAY_STRIDE_X
VERTEX_ATTRIB_ARRAY_POINTER_X
VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_X

Here's what vertexAttribPointer does :

function vertexAttribPointer(indx, size, type, normalized, stride, offset) {
    this.stateCache["VERTEX_ATTRIB_ARRAY_SIZE_" + indx] = size;
    this.stateCache["VERTEX_ATTRIB_ARRAY_TYPE_" + indx] = type;
    this.stateCache["VERTEX_ATTRIB_ARRAY_NORMALIZED_" + indx] = normalized;
    this.stateCache["VERTEX_ATTRIB_ARRAY_STRIDE_" + indx] = stride;
    this.stateCache["VERTEX_ATTRIB_ARRAY_POINTER_" + indx] = offset;
    this.stateCache["VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_" + indx] = this.stateCache["ARRAY_BUFFER_BINDING"];
}

Finally, WebGL Inspector was true! State changes line 15 and 30 are necessary because the VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_0 is changing.

Here's the optimal trace :

bindBuffer(ARRAY_BUFFER, A)
vertexAttribPointer(0, 3, FLOAT, false, 0, 0)
drawElements(TRIANGLES, 768, UNSIGNED_BYTE, 0)
bindBuffer(ARRAY_BUFFER, B)
vertexAttribPointer(0, 3, FLOAT, false, 0, 0)
drawElements(TRIANGLES, 768, UNSIGNED_BYTE, 0)

(bindBuffer(ARRAY_BUFFER, C) is not needed anymore, since we aren't doing anything with it.)