Course outline
WebGL Is a State Machine
In one line
WebGL calls don't take "what to draw with" as arguments. They change global settings (the bound buffer, the current program, attribute pointers), and every later call uses whatever is set. Forget one setting and the next draw silently uses the last one. VAOs bundle the per-object settings.
Think of it like this
A shared office printer. Whoever printed last left it on A3, landscape. You press Print without checking and your letter comes out wrong. No error: the printer did what its settings said.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- state machine
- A system whose behaviour depends on current settings that earlier commands changed.
- bind
- Make an object the current one for a role (
ARRAY_BUFFER,TEXTURE_2D), so later calls act on it. - VAO
- Vertex array object: saved attribute settings that one
bindVertexArrayrestores. - uniform
- A value that is the same for every vertex and pixel of a draw (a colour, a matrix); it lives in a program.
- INVALID_OPERATION
- Error 1282: the call isn't allowed in the current state.
Step by step
01Run the two passes
The first example draws twice: once with vertexAttribPointer missing for the second buffer, once complete. Missing: the left pixel is blue, the right empty.
02Ask WebGL what it believes
getParameter(ARRAY_BUFFER_BINDING) and getVertexAttrib(0, VERTEX_ATTRIB_ARRAY_BUFFER_BINDING) show what is bound and what attribute 0 reads. Querying state is how you debug these bugs.
03Count the calls
The second example wraps a few methods to count them, then draws three shapes without and with VAOs.
04Unbind after setup
While a VAO is bound, every attribute call changes that VAO. bindVertexArray(null) after setup stops one object's setup from editing another's.
Try it yourself
- 1
Point, then bind
In the first example's
draw(), move the secondvertexAttribPointerline abovegl.bindBuffer(gl.ARRAY_BUFFER, right). Both passes printleft blue | right empty, and the last line saysattribute 0 reads 'right': false: the order of two calls is the whole bug. - 2
Add a fourth shape
In the second example, change
[0, 1, 2]to[0, 1, 2, 3]. Without VAOs:33 calls(8 more: 2 binds, 2 enables, 2 pointers, a uniform, a draw). With VAOs:14 calls(3 more).
Code & diagrams
Both passes run in one go; the first forgets `vertexAttribPointer` for the second buffer.
Console
Run the scene to see its console.Expected console output
forgot vertexAttribPointer: left blue | right empty
with it: left orange | right blue
bound ARRAY_BUFFER is 'right': true
attribute 0 reads 'right': trueCounts, not timings: the same on every machine.
Console
Run the scene to see its console.Expected console output
without VAOs -> 25 calls: useProgram 1, bindBuffer 6, enableVertexAttribArray 6, vertexAttribPointer 6, uniform4f 3, drawArrays 3
with VAOs -> 11 calls: useProgram 1, bindVertexArray 4, uniform4f 3, drawArrays 3
errors: noneBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Select the program too late
Delete the top-level gl.useProgram(program); and add it at the end of draw() instead.
Myth vs fact
Myth
A WebGL bug always shows up as an error.
Fact
Many state bugs are legal calls with the wrong settings. getError() returns NO_ERROR for the forgotten pointer above.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
getError()andgetParameter()make the browser wait for the GPU process. Use them while debugging, never every frame; three.js tracks state instead of querying it. - ▸
VAOs are core in WebGL 2 (in WebGL 1 they were the
OES_vertex_array_objectextension). three.js keeps one per geometry and program pair (WebGLBindingStates), so a normal frame needs onebindVertexArrayper mesh (Topic 14.5).
Remember this
- 1
The problem this solves: wrong pictures with no error are the hardest WebGL bugs. They come from state left over by earlier code. Knowing which calls set state and which use it is how you find them, and why three.js tracks every setting itself.
- 2
Set, then use.
bindBuffer(ARRAY_BUFFER, b)draws nothing; it changes a setting.vertexAttribPointerreads that setting when you call it and stores "attribute 0 reads buffer b". Binding another buffer later doesn't change attribute 0. In the first example the blue triangle's buffer is bound but never pointed to, so it is drawn with the orange triangle's corners. - 3
Uniforms belong to the current program.
uniform4fsets a value in whateveruseProgramlast selected; with no program current, it fails withINVALID_OPERATION. - 4
VAOs. A vertex array object records, per attribute, which buffer it reads, the layout and whether it is enabled, plus the index buffer.
bindVertexArray(vao)restores all of it. For 3 shapes with 2 attributes each: 25 calls per frame without VAOs, 11 with them. - 5
Why three.js caches state. three.js keeps its own copy of the GL state (
WebGLState) and skips calls that set what is already set. Topic 14.5 shows what breaks when your code changes the real state behind its back.
Explain it without notes
What does it mean that WebGL is a state machine, and what kind of bug does it cause?
What does a VAO store, and why does it make drawing faster and safer?
Practice
Rewrite the first example's draw() with one VAO per triangle.
Write logState(gl), printing the bound array buffer, the current program and attribute 0's buffer.
Done when you can
You can explain bind-then-use with an example
You can find a forgotten-pointer bug by querying state
You set up VAOs once and unbind after setup
You select the program before setting its uniforms