57

I'm not entirely clear on the scope of enabling vertex attrib arrays. I've got several different shader programs with differing numbers of vertex attributes. Are glEnableVertexAttribArray calls local to a shader program, or global?

Right now I'm enabling vertex attrib arrays when I create the shader program, and never disabling them, and all seems to work, but it seems like I'm possibly supposed to enable/disable them right before/after draw calls. Is there an impact to this?

(I'm in WebGL, as it happens, so we're really talking about gl.enableVertexAttribArray and gl.disableVertexAttribArray. I'll note also that the orange book, OpenGL Shading Language, is quite uninformative about these calls.)

gman
  • 83,286
  • 25
  • 191
  • 301
Grumdrig
  • 14,990
  • 11
  • 53
  • 68

4 Answers4

46

The state of which Vertex Attribute Arrays are enabled can be either bound to a Vertex Array Object (VAO), or be global.

If you're using VAOs, then you should not disable attribute arrays, as they are encapsulated in the VAO.

However for global vertex attribute array enabled state you should disable them, because if they're left enabled OpenGL will try to read from arrays, which may be bound to a invalid pointer, which may either crash your program if the pointer is to client address space, or raise a OpenGL error if it points out of the limits of a bound Vertex Buffer Object.

datenwolf
  • 149,702
  • 12
  • 167
  • 273
  • 1
    Thanks to the pointer to VAOs! In the case you're not using them then, the danger is that if shader A has two vertex attribute arrays, and shader B has one, switching from A to B, the B program will still access A's second array. Do I have that right? – Grumdrig Sep 15 '12 at 04:40
  • WebGL doesn't allow "global arrays" or more properly called "client side arrays". You won't crash because WebGL explicitly doesn't allow them. – gman Sep 15 '12 at 09:18
  • @gman: I know, but OP asked about OpenGL-ES and WebGL and I indended my answer being applicable to both. And although it won't crash it will still raise an error condition. – datenwolf Sep 15 '12 at 10:25
  • To set the record straight, I tagged the question [opengl] in addition to [webgl], because I didn't know the answer would be different for the two, I guess, and it was edited to [opengl-es] by @genpfault. But more info is better. – Grumdrig Sep 15 '12 at 21:50
  • 8
    @Grumdrig: The problem is a different one. Say with shader 1 you got attributes A and B, with each having 500 vertices. Then you switch to shader 2, having only attribute A, but this time you draw 1000 vertices. If you'd not disable attribute B, OpenGL tried to read 1000 vertices from the buffer for B, which, if not unbound, changed or disabled would be a out of bounds access of the only 500 attributes in B. – datenwolf Sep 15 '12 at 22:38
  • @gman: That seems odd. WebGL only supports VAO through an extension (`OES_vertex_array_object`). So I would assume that in the absence of that extension, global arrays are the only way to render anything. If WebGL does not allow them, what is left? – Dreamer Aug 27 '14 at 08:25
  • 2
    @Dreamer: gman is not referring to "Vertex Array Objects" but "Vertex Buffer Objects", which yes, are a requirement in WebGL. Unfortunately his nomenclauture is off. What he means is, that WebGL doesn't support client side vertex array, i.e. passing a client address space to the glVertexAttribPointer functions. In WebGL and OpenGL-3.3 and later core profiles you must place vertex data in server side Vertex Buffer Objects. That's what he meant. – datenwolf Aug 27 '14 at 08:37
15

WebGL is not the same as OpenGL.

In WebGL leaving arrays enabled is explicitly allowed as long as there is a buffer attached to the attribute and (a) if it's used it's large enough to satisfy the draw call or (b) it's not used.

Unlike OpenGL ES 2.0, WebGL doesn't allow client side arrays.

Proof:

const gl = document.querySelector("canvas").getContext("webgl");

const vsUses2Attributes = `
attribute vec4 position;
attribute vec4 color;
  
varying vec4 v_color;

void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
  v_color = color;
}
`;
const vsUses1Attribute = `
attribute vec4 position;
  
varying vec4 v_color;
  
void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
  v_color = vec4(0,1,1,1);
}
`
const fs = `
precision mediump float;
varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}
`;

const program2Attribs = twgl.createProgram(gl, [vsUses2Attributes, fs]);
const program1Attrib  = twgl.createProgram(gl, [vsUses1Attribute, fs]);

function createBuffer(data) {
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  return buf;
}

const buffer3Points = createBuffer([
  -0.7, 0.5,
   0.0, 0.5,
   0.7, 0.5,
]);
const buffer3Colors = createBuffer([
  1, 0, 0, 1,
  0, 1, 0, 1,
  0, 0, 1, 1,
]);

const buffer9Points = createBuffer([
  -0.8, -0.5,
  -0.6, -0.5,
  -0.4, -0.5,
  -0.2, -0.5,
   0.0, -0.5,
   0.2, -0.5,
   0.4, -0.5,
   0.6, -0.5,
   0.8, -0.5,
]);

// set up 2 attributes
{
  const posLoc = gl.getAttribLocation(program2Attribs, 'position');
  gl.enableVertexAttribArray(posLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

  const colorLoc = gl.getAttribLocation(program2Attribs, 'color');
  gl.enableVertexAttribArray(colorLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Colors);
  gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
}

// draw
gl.useProgram(program2Attribs);
gl.drawArrays(gl.POINTS, 0, 3);

// setup 1 attribute (don't disable the second attribute
{
  const posLoc = gl.getAttribLocation(program1Attrib, 'position');
  gl.enableVertexAttribArray(posLoc);
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer9Points);
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
}

// draw
gl.useProgram(program1Attrib);
gl.drawArrays(gl.POINTS, 0, 9);
const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it draws 3 points (3 vertices, 2 attributes)<br/>
2nd it draws 9 points (9 vertices, 1 attribute)<br/>
It does NOT call gl.disableVertexAttrib so on the second draw call one of the attributes is still enabled. It is pointing to a buffer with only 3 vertices in it even though 9 vertices will be drawn. There are no errors.
</p>
<canvas></canvas>

Another example, just enable all the attributes, then draw with a shader that uses no attributes (no error) and also draw with a shader that uses 1 attribute (again no error), no need to call gl.disbleVertexAttribArray

const gl = document.querySelector("canvas").getContext("webgl");

const vsUses1Attributes = `
attribute vec4 position;

void main() {
  gl_Position = position;
  gl_PointSize = 20.0;
}
`;
const vsUses0Attributes = `
void main() {
  gl_Position = vec4(0, 0, 0, 1);
  gl_PointSize = 20.0;
}
`
const fs = `
precision mediump float;
void main() {
  gl_FragColor = vec4(1, 0, 0, 1);
}
`;

const program0Attribs = twgl.createProgram(gl, [vsUses0Attributes, fs]);
const program1Attrib  = twgl.createProgram(gl, [vsUses1Attributes, fs]);

function createBuffer(data) {
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  return buf;
}

const buffer3Points = createBuffer([
  -0.7, 0.5,
   0.0, 0.5,
   0.7, 0.5,
]);

const buffer0Points = createBuffer([]);

// enable all the attributes and bind a buffer to them
const maxAttrib = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
for (let i = 0; i < maxAttrib; ++i) {
  gl.enableVertexAttribArray(i);
  gl.vertexAttribPointer(i, 2, gl.FLOAT, false, 0, 0);
}

gl.useProgram(program0Attribs);
gl.drawArrays(gl.POINTS, 0, 1);

gl.useProgram(program1Attrib);
const posLoc = gl.getAttribLocation(program1Attrib, 'position');
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3Points);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, 3);

const err = gl.getError();
console.log(err ? `ERROR: ${twgl.glEnumToString(gl, err)}` : 'no WebGL errors');
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<p>
1st it enables all attributes<br/>
2nd it draws 1 point that needs no attributes (no error)<br/>
3rd it draws 3 points that need 1 attribute (no error)<br/>
It does NOT call gl.disableVertexAttribArray on any of the attributes so they are all still enabled.  There are no errors.
</p>
<canvas></canvas>
gman
  • 83,286
  • 25
  • 191
  • 301
  • 1
    Good to know! Will it use resources to leave the attribute enabled though? (I'm thinking the GPU will be doing extra calculations, perhaps, though I'm not sure I see how it would be.) – Grumdrig Sep 15 '12 at 21:47
  • where is gl.enableVertexAttribArray in your code ? – Philippe Oceangermanique Jan 18 '21 at 10:31
  • ...it take a time looking in the /twgljs.org/docs/ – Philippe Oceangermanique Jan 18 '21 at 10:48
  • what is client-side arrays ? i watched https://www.khronos.org/registry/webgl/specs/1.0/#6.2, but i did not understand – Philippe Oceangermanique Jan 20 '21 at 13:12
  • i dont understand why there should be an error ? ok one of the attributes is still enabled..but it is not used ! – Philippe Oceangermanique Jan 21 '21 at 07:29
  • 1
    There should not be an error and my example is proof there are no errors. The other 2 answers here claim their will be. They are wrong. Please downvote them appropriately. As for the meaning of "client side arrays", it means vertex data that is in CPU memory (the client), not GPU memory (the server). GPU memory is represented by buffers. See [the spec](https://www.khronos.org/registry/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf), search for "client". In OpenGL ES you can have the system supply data to attributes directly from CPU memory (client side arrays). WebGL disallows client side arrays – gman Jan 21 '21 at 07:40
  • 363 / 5000 Résultats de traduction I tested a shader including only a constant values vertex attributes. const posLoc1 = gl.getAttribLocation (program1Attrib, 'position'); gl.disableVertexAttribArray (posLoc1); gl.vertexAttrib4fv (posLoc1, [0.5,0.5,0., 1.]) ;. I put this code in my copy of webgl-state-diagram. But the value of this attribute does not appear in the Vertex Arrays window (the "value" column – Philippe Oceangermanique Jan 24 '21 at 09:44
  • S.O. is the wrong place to be asking questions about the state diagram but in any case it's only designed to show state for the included samples. The included samples don't use `gl.vertexAttrib` so there is no support for it. – gman Jan 24 '21 at 11:15
  • i download webgl-state-diagram.html and add some samples . So i added a sample with a vertexAttrib4fv. And i watch step by step, but the step gl.vertexAttrib4fv(posLoc1,[0.5,0.5,0.,1.]); does not appear in the vertex Array window. (if i put two : const posLoc1 = gl.getAttribLocation(program1Attrib, 'position1); gl.disableVertexAttribArray(posLoc1); gl.vertexAttrib4fv(posLoc1,[0.3,0.2,0.,1.]); const posLoc2 = gl.getAttribLocation(program1Attrib, 'position2'); gl.disableVertexAttribArray(posLoc2); gl.vertexAttrib4fv(posLoc2,[0.5,0.5,0.,1.]); In the vertex Array window, there is only posLoc1 – Philippe Oceangermanique Jan 24 '21 at 19:04
  • where is the place for asking questions about the state diagram ? – Philippe Oceangermanique Jan 26 '21 at 20:15
  • 1
    [here?](https://github.com/gfxfundamentals/webgl-fundamentals/issues) – gman Jan 27 '21 at 01:07
3

For webGL I'm going to go with yes, it is important to call gl.disableVertexAttribArray.

Chrome was giving me this warning:

WebGL: INVALID_OPERATION: drawElements: attribs not setup correctly

This was happening when the program changed to one using less than the maximum number of attributes. Obviously the solution was to disable the unused attributes before drawing.

If all your programs use the same number of attributes, you may well get away with calling gl.enableVertexAttribArray once on initialization. Otherwise you'll need to manage them when you change programs.

Adria
  • 7,508
  • 4
  • 34
  • 25
  • I was also facing another error: `INVALID TEXTURE`, solved the same way, by disabling unused attributes. – deblocker Sep 20 '17 at 08:06
  • Calling gl.enableVertexAttribArray () just once on initialization is efficient, but to the people learning WebGL: *make sure* you call it on initialization. It can be easy to forget. If nothing shows up on the screen, check if you called it or not (this would be part of the checklist for debugging). – RhetoricalRuvim Nov 07 '17 at 02:45
  • This answer is just plain wrong. Whatever issue you had it was not related to calling `disableVertexAttribArray`. I've posted proof in my answer that you do not need to disable them. I'm not saying you didn't have an issue. Obviously you did. And I'm not saying disabling the array didn't fix the issue for you. Rather I'm saying you didn't actually understand the real issue. – gman Aug 27 '19 at 08:23
-2

Think of it as attributes are local to a VAO and not a shader program. the VBOs are in GPU memory.

Now consider that, in WebGL, there is a default VAO that WebGL uses by default. (it can also be a VAO created by the programmer, the same concept applies). This VAO contains a target called ARRAY_BUFFER to which any VBO in the GPU memory can bound. This VAO also contains and attribute array with a fixed number of attribute slots (number depends on implementation and platform, here lets say 8 which is the minimum required by the Webgl specification). Also, this VAO will have ELEMENT_ARRAY_BUFFER target to which any index data buffer can be bound.

Now, when you create a shader program, it will have the attributes you specify. Webgl will assign one of the possible attribute slot "numbers" to all of the attributes specified in the program when you link the shader program. now attributes will use the corresponding attribute slots in the VAO to access the data bound to the ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER targets in the VAO. Now, when you use functions gl.enableVertexAttribArray(location) and gl.vertexAttribPointer(location,....) you are not changing any characteristics of the attributes in the shader program (they simply have a attribute number which refers to one of the attribute slots in the VAO that they will use to access data). What you are actually doing is modifying the state of the attribute slot in the VAO using its location number. SO then for the attributes in the program to be able to access the data, its corresponding attribute slot in the VAO must be enabled (gl.enableVertexAttribArray()). And we have to configure the attribute slot so it can read the data from the buffer bound to the ARRAY_BUFFER correctly (gl.vertexAttribPointer()). one a VBO is set for a slot it wont change, even if we unbind it from the target the attribute slot csn still red from the VBO as long as it is there in GPU memory. Also, there must be some buffer bound to the targets of the VAO (gl.bindBuffer()). So gl.enableVertexAttribArray(location) will enable the attribute slot specified by 'location' in the current VAO. gl.disableVertexAttribArray(location) will disable it. It has nothing to do with the shader program though. Even if you uses a different shader program, these attribute slots's state wont be affected.

So, if two different shader programs use the same attribute slots, there wont be any error because the corresponding attribute slots in the VAO is already active. but the data from the targets might be read incorrectly if the attributes are required interpret the data differently in the two shader programs. Now consider, if the two shader programs use different attribute slots, then you might enable the second shaders program's required attribute slots and think that your program should work. But the already enabled attribute slots (which were enabled by the previous shader program) will still be enabled but wont be used. This causes an error.

So when changing shader programs, we must ensure that the enabled attribute slots in the VAO that wont be used by this shader program must be disabled. Although we might now specify any VAOs explicitly, Webgl works like this by default.

One way is to maintain a list of enabled attributes on the javascript side and disable all enabled attribute slots when switching program while still using the same VAO. Another way to deal with this problem is to create custom VAOs that is accessed by one shader program only. but it is less efficient. Yet another way is to binding attribute locations to fixed slots before the shader program is linked using gl.bindAttribLocation().

  • 1
    This is wrong. There is no need to disable attributes. See my answer as proof. Further, your answer is also wrong. A VAO does not contain the ARRAY_BUFFER target. A VAO only contains the ELEMENT_ARRAY_BUFFER target. The ARRAY_BUFFER target is global. – gman Jan 18 '21 at 13:37