GPU Pipeline
Fixed Function Pipeline — OLD

Programmable Pipeline
Tessellation Shader and Geometry Shader are optional.

Fragment Shader
The job of the fragment shader is very clear, it receives one fragment for a particular pixel, and it just is to output a color value. Here is an example fragment shader written in GLSL. Technically, our modern fragment shaders are capable of outputting more than one color value, so you can be rendering more than one image at a time, you can do crazy things with GPUs today. You don’t have to render just one image simultaneously at the same time you could output multiple images. So here the location = 0 means it is going to be my output number zero, so if I am outputting into multiple render targets, this is going to go to the first render target, that is render target zero. But if you don’t specify this, it will by default be the output zero.

Vertex Shader
The vertex shader takes a single vertex attribute and output a single vertex data in the canonical view volume. The transformation is done by the model/view/projection matrices. It’s going to convert my 3d position to a 4d vector by converting it to the homogeneous coordinates by adding the fourth coordinate which is going to be 1.
gl_Position is a pre-defined variable, it is what the vertex shader is going to output. And then the rasterizer will receive that position and it will be able to render.

Compile Shaders
GLuint program = glCreateProgram();
// Compile vertex shader
char* vsSource = ReadFromFile("shader.vert");
GLuint vs = glCreateShader();
glShaderSource(vs, 1, vsSource, nullptr);
glCompileShader(vs);
glAttachShader(program, vs);
delete[] vsSource;
// Compile fragment shader
char* fsSource = ReadFromFile("shader.frag");
GLuint fs = glCreateShader();
glShaderSource(fs, 1, fsSource, nullptr);
glCompileShader(fs);
glAttachShader(program, fs);
delete[] fsSource;
// Link to Program
glLinkProgram(program);
Primitives

Immediate Mode — OLD
It’s an old way of drawing things, not supported in modern versions of OpenGL but lives inside OpenGL. So immediate mode related functions are included inside OpenGL but you’re not supposed to use them because they may work with modern OpenGL context.
In the old days, I could send one vertex at a time to my GPU, but this is not very efficient. For sending every vertex, I am calling an OpenGL function. This is super inefficient. Imagine that I am drawing a million triangles that mean three million vertices. So that would mean three million OpenGL calls for just sending a vertex, too many calls. Also every time I call that function, that function is running on my CPU, it’s taking data from my CPU and sending it to my GPU, so if you don’t have an integrated CPU GPU system, that means I am taking data from my main memory or from my CPU local storage, and sending it through the PCI express bus to my GPU so that my GPU can get that data. And every time I am sending one triangle at a time through this bus, that was very inefficient.

To make things much more efficient, even the old versions of OpenGL supported this concept. Just create a buffer and send the entire buffer to the GPU so instead of sending one vertex at a time, I can create an array that contains all of my vertices positions, and send them in just one call. That existed and it got refined. quite a bit in the modern versions of OpenGL.
Modern Mode
So in modern OpenGL, we don’t use this immediate mode anymore. We have what we call a vertex buffer object. So our job inside our CPU code will be to create a vertex buffer object, and that vertex buffer object I’m creating is going to first live inside my main CPU memory, and then I am going to send it to my GPU and store it somewhere on the GPU memory, so from that point on, it will live on that optimized GPU memory, so when I say draw this above the contents of this buffer now, my GPU will get that data from its own memory so it’s going to be a lot more efficient.

Vertex Buffer Object
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
GL_STATIC_DRAW means that I am not planning to change the contents of that buffer very frequently, it’s just an optimization flag I am sending to OpenGL or my GPU so that it can decide what to do with this buffer and use it more efficiently.
Next, we need to tell OpenGL how to interpret the vertex buffer data.
GLuint pos = glGetAttribLocation(program, "pos");
glEnableVertexAttribArray(pos);
glVertexAttributePointer(pos, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);
Vertex Array Object
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
Rendering
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program);
glDrawArrays(GL_POINTS, 0, num_vertices);
glfwSwapBuffers();
Summary
- Initialization
- Create VAOs
- Create VBOs
- Compile shaders
- For each VAO
- Assign VBOs to vertex attributes
- Rendering
- For each VAO
- Call glDrawArrays
- For each VAO
[Note: Contents come from Professor Cem Yuksel’s Interactive Graphics course, check out his website for more details]