Attributes are global state (*). If they were implemented in JavaScript they would look something like this
// pseudo code
gl = {
ARRAY_BUFFER: null,
vertexArray: {
attributes: [
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ? },
],
ELEMENT_ARRAY_BUFFER: null,
},
}
As you can see above there are 8
attributes and they are global state.
When you call gl.enableVertexAttribArray(location)
or gl.disableVertexAttribArray
you can think of it like this
// pseudo code
gl.enableVertexAttribArray = function(location) {
gl.vertexArray.attributes[location].enable = true;
};
gl.disableVertexAttribArray = function(location) {
gl.vertexArray.attributes[location].enable = false;
};
In other words location
directly refers to the index of an attribute.
Similarly gl.vertexAttribPointer
would be implemented something like this
// pseudo code
gl.vertexAttribPointer = function(location, size, type, normalize, stride, offset) {
var attrib = gl.vertexArray.attributes[location];
attrib.size = size;
attrib.type = type;
attrib.normalize = normalize;
attrib.stride = stride ? stride : sizeof(type) * size;
attrib.offset = offset;
attrib.buffer = gl.ARRAY_BUFFER; // !!!! <-----
};
Notice that attrib.buffer
is set to whatever the current gl.ARRAY_BUFFER
is set to. gl.ARRAY_BUFFER
is set by calling gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer)
.
So, next up we have vertex shaders. In vertex shader you declare attributes. Example
attribute vec4 position;
attribute vec2 texcoord;
attribute vec3 normal;
...
void main() {
...
}
When you link a vertex shader with a fragment shader by calling gl.linkProgram(someProgram)
WebGL (the driver/GPU/browser) decide on their own which index/location to use for each attribute. You have no idea which ones they're going to pick. It's up the the browser/driver/GPU. So, you have to ask it which attribute did you use for position
, texcoord
and normal
?. You do this by calling gl.getAttribLocation
var positionLoc = gl.getAttribLocation(program, "position");
var texcoordLoc = gl.getAttribLocation(program, "texcoord");
var normalLoc = gl.getAttribLocation(program, "normal");
Let's say positionLoc = 5
. That means when the vertex shader executes (when you call gl.drawArrays
or gl.drawElements
) the vertex shader expects you to have setup attribute 5 with the correct type
, size
, offset
, stride
, buffer
etc.
Note that BEFORE you link the program you can choose the locations by calling gl.bindAttribLoction(program, location, nameOfAttribute)
. Example:
// Tell `gl.linkProgram` to assign `position` to use attribute #7
gl.bindAttribLocation(program, 7, "position");
Missing from the description above is that each attribute also has a default value. It is left out above because it is uncommon to use it.
attributes: [
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
value: [0, 0, 0, 1], },
{ enable: ?, type: ?, size: ?, normalize: ?, stride: ?, offset: ?, buffer: ?
value: [0, 0, 0, 1], },
..
You can set the value with the various gl.vertexAttribXXX
functions. The value
is used when enable
is false
. When enable
is true data for the attribute is pulled from the assigned buffer
.
WebGL has an extension, OES_vertex_array_object
In the diagram above OES_vertex_array_object
lets you create and replace the vertexArray
. In other words
var vao = ext.createVertexArrayOES();
creates the object you see attached to gl.vertexArray
in the pseudo code above. Calling ext.bindVertexArrayOES(vao)
assign your created vertex array object as the current vertex array.
// pseudo code
ext.bindVertexArrayOES = function(vao) {
gl.vertexArray = vao;
}
This lets you set all of the attributes and ELEMENT_ARRAY_BUFFER
in the current VAO so that when you want to draw it's one call to ext.bindVertexArrayOES
where as without the extension it would be up to one call to both gl.bindBuffer
gl.vertexAttribPointer
(and possibly gl.enableVertexAttribArray
) per attribute.