Render a static mesh with a core-profile VAO in OpenGL 3.3+
Build a core-profile VAO for a static mesh once, then bind it for drawing. Covers prerequisites, VAO VBO EBO setup, attribute pointers, checks and recovery for OpenGL 3.3+.
11 Sept 2026, 05:48 UTC

Problem: a single bind for a static mesh without per-draw attribute setup
In core profile OpenGL, attribute pointers are not global. They are stored in a Vertex Array Object. If you call glVertexAttribPointer without a VAO bound, or you bind the wrong VAO, the state is written to the wrong object and the draw produces undefined attributes. The practical decision is to build one VAO per static mesh, capture VBO, EBO and attribute layout once, then bind only the VAO before glDrawElements.
Desired outcome
A renderable static mesh using a VAO, VBO and EBO where the attribute layout matches the active shader program. After setup you bind the VAO and program once per frame and issue a single draw call.
Prerequisites
An OpenGL 3.3 or newer core context current on the calling thread. A linked shader program with known attribute locations for position and optional normal and uv. CPU side vertex and index arrays in tightly packed format. A valid GL context is required; object creation must not happen on a thread without a current context.
Verify the context before creating objects. Query VERSION and confirm major version >=3 and core profile. VAO and glGenVertexArrays are core in OpenGL 3.0+ and are not available in OpenGL ES 2.0.
Setup procedure
Generate and bind the VAO first. All subsequent vertex array state is recorded in the currently bound VAO.
GLuint vao, vbo, ebo;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);Create the vertex buffer, upload immutable data and keep it bound while configuring attributes.
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertex_bytes, vertices, GL_STATIC_DRAW);Use GL_STATIC_DRAW for data that will not change. GL_DYNAMIC_DRAW is for frequently updated data. The hint affects driver placement.
Create the element buffer. The binding of GL_ELEMENT_ARRAY_BUFFER is stored in the VAO.
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_bytes, indices, GL_STATIC_DRAW);Describe each attribute with glVertexAttribPointer and enable it. The location must match the shader. Query it with glGetAttribLocation after linking, or use layout location in the shader.
GLint pos_loc = glGetAttribLocation(program, position);
glEnableVertexAttribArray(pos_loc);
glVertexAttribPointer(pos_loc, 3, GL_FLOAT, GL_FALSE, stride, offset);Repeat for normal and uv with their own locations, stride and offsets. The stride and offset must match the CPU layout exactly.
Unbind the VAO for safety to avoid accidental modification later.
glBindVertexArray(0);At draw time bind program and VAO, then draw.
glUseProgram(program);
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, 0);Expected checks
Call glGetError after object creation and attribute setup. Zero indicates no immediate GL error.
Inspect attribute state with glGetVertexAttribiv to confirm enabled state and that size and stride match shader inputs. Compare the stride you passed to glVertexAttribPointer with the actual struct size.
Perform a minimal draw to an offscreen framebuffer and check that the draw completes without errors. This confirms pipeline connectivity without relying on visible output.
Recovery options and limitations
Attribute pointer state is stored in the currently bound VAO. Binding the wrong VAO before glVertexAttribPointer silently configures the wrong object. Always bind the intended VAO before any pointer calls.
If the shader is relinked, attribute locations can change. Re-query locations and rebuild the VAO attribute setup.
Context loss requires recreation of VAO, VBO and EBO. Store CPU copies of vertices and indices for recreation.
If VAO is unavailable, fallback to compatibility profile attribute setup is possible but not portable to core. Core profile requires a VAO for any draw.
Validate data alignment and stride to avoid undefined attribute fetch. Mismatched stride or offset produces garbage attributes without a GL error.