中文

OpenGL data pipeline: From CPU to GPU Memory

Introduction

When working with OpenGL, one of the most fundamental — yet often confusing — questions is:
“When I draw a triangle, where does my data actually go?”

Understanding this journey clarifies how data moves from the CPU to the GPU, and why certain API calls matter more than others. This article bridges that gap — walking through vertex data, buffer objects, draw calls, and shader execution — to reveal how OpenGL manages memory and transforms your data into rendered pixels.

1. The Foundation: Vertex Data and Buffer Objects

Alt text describing the image

1.1 What is a VBO (Vertex Buffer Object)

A VBO stores vertex attribute data — such as positions, normals, or texture coordinates — directly in GPU memory.

1.2 What is an EBO (Element Buffer Object)

An EBO (or Index Buffer) stores integer indices that reference vertices in a VBO.

1.3 What is a VAO (Vertex Array Object)

A VAO is a container that describes how vertex data is read.

You can think of a VAO as an “input layout blueprint” for your vertex shader.

And you can set up VBOs and VAO in different ways, depending on your needs. In the following section, I will show two common patterns.

So now we can understand that how VAO points to different VBOs and how the data is laid out in those buffers. The key here is the set up of attribute pointers by glVertexAttribPointer( index, size, type, normalized, stride, offset).

1.4 Setting Up Vertex Input — APIs and Layout Patterns

Key APIs:

2. Drawing: How Vertex Data Becomes Geometry

2.1 How glDrawArrays Works

2.2 How glDrawElements Works (Using EBO)

2.3 Relationship Between VAO, VBO, and EBO

Draw Call Data Source Uses EBO?
glDrawArrays Sequential vertex order ❌ No
glDrawElements Indexed vertex order ✅ Yes

Both use the same VAO, but only glDrawElements references the EBO during draw calls.

3. Shaders: From Source Code to GPU Execution

3.1 The Shader Creation Pipeline

On the CPU side, shader code is plain text written in GLSL.
The creation process looks like this:

GLuint vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &vertexShaderSource, NULL);
glCompileShader(vs);

GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, &fragmentShaderSource, NULL);
glCompileShader(fs);

GLuint program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);

Once linked, the program resides in GPU memory as compiled machine code.
From this point on, it’s ready for use during rendering.

3.2 Attribute, Varying, and Uniform Linking

4. Textures and GPU Resources: A Unified Memory Model

4.1 Textures as GPU Resources

glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
// Generate texture data on GPU
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
             GL_RGBA, GL_UNSIGNED_BYTE, imageData);
free(imageData); // Free CPU memory after upload

4.2 Unified Resource Lifecycle

Phase API Example Description
Create glGen* CPU handle only — no GPU allocation yet
Bind glBind* Changes driver state, tells GPU which object to operate on
Upload glBufferData, glTexImage2D Allocates and fills GPU memory
Delete glDelete* Frees the GPU resource

This pattern applies universally across buffers, textures, and shaders.

5. The Big Picture: The Journey of a Vertex

At CPU side, you prepare your data each frame, rendering typically follows this sequence:

cpp
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

for (Shader& shader : shaders) {
    glUseProgram(shader.id);
    shader.bindGlobalUniforms();  // camera, lighting, etc.

    for (Renderable& obj : shader.objects) {
        glBindVertexArray(obj.vao); // this is 'mesh' from game engine perspective
        obj.bindMaterialUniforms(); // this is 'material' from game engine perspective
        glDrawElements(GL_TRIANGLES, obj.indexCount, GL_UNSIGNED_INT, 0);
    }
}

And then on the GPU side, here’s what happens under the hood:

  1. CPU creates vertex arrays, textures, and shader programs.
  2. Data is uploaded to GPU memory via OpenGL API calls.
  3. VAO defines how to interpret that data and links to an optional EBO.
  4. A draw call triggers vertex fetching and shader execution.
  5. The vertex shader transforms data; the fragment shader computes color.
  6. The GPU rasterizer converts geometry to pixels and writes them to the framebuffer.

key Takeaway

References

*****

☕ Happy learning journey~ 🛠️