Skip to content

Instantly share code, notes, and snippets.

@komiga
Last active March 23, 2017 00:43
Show Gist options
  • Save komiga/830a92110581b2c25fe8fb7c70b6df1a to your computer and use it in GitHub Desktop.
Save komiga/830a92110581b2c25fe8fb7c70b6df1a to your computer and use it in GitHub Desktop.
A simplified look at what FFP OpenGL could do when implemented in terms of core OpenGL 3.3.
// A simplified look at what FFP OpenGL could do when implemented in terms of
// core OpenGL 3.3.
struct Position {
float x, y;
};
struct Color {
float r, g, b;
};
// In practice we would be concerned with padding in vertex data.
// Imagine there is none here and that sizeof(float) == 4.
struct Vertex {
Position position;
Color color;
// normals, texcoords, ...
};
struct Primitive {
GLenum mode;
Vertex[] vertices;
// vertex indices, lighting material, ...
uint num_vertices;
GLuint vbo;
GLuint vao;
};
// In practice this is more complex/generalized, as it has to include projection
// state (glPushMatrix, glPopMatrix, glRotate, glScale, glTranslate) and other
// interspersed state.
Primitive[] primitives_to_draw;
Primitive current_primitive = null;
Color current_color = {1.0f, 1.0f, 1.0f};
Vertex current_vertex = null;
finalize_vertex() {
if current_vertex {
current_vertex.color = current_color;
current_vertex = null;
}
}
glBegin(GLenum mode) {
assert(not current_primitive);
current_primitive = append(primitives_to_draw, new Primitive);
current_primitive.mode = mode;
}
glVertex2f(float x, float y) {
finalize_vertex();
current_vertex = append(current_primitive.vertices, new Vertex);
current_vertex.position = {x, y};
}
// This can be called anywhere
glColor3f(float r, float g, float b) {
current_color = {r, g, b};
}
glEnd() {
assert(current_primitive);
finalize_vertex();
// Create the VBO and store the primitive's data in it
glGenBuffers(1, &current_primitive.vbo);
glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo);
// Cache the number of vertices
current_primitive.num_vertices = number_of_items(current_primitive.vertices);
// Ensure primitive completeness (simplified)
// (e.g., segment_length(GL_QUADS) == 4)
assert(
current_primitive.num_vertices > 0 and
current_primitive.num_vertices modulo segment_length(current_primitive.mode) == 0
)
// NB: In practice, tons of tiny buffers is bad for the GPU and the driver,
// so we would ideally put many primitives within fewer large buffers to avoid
// resource exhaustion (from both this concern and resource churn).
glBufferData(
GL_ARRAY_BUFFER,
sizeof(Vertex) * current_primitive.num_vertices, // number of bytes
current_primitive.vertices, // data
GL_STATIC_DRAW // we're not going to mess with it
);
// Toss the CPU memory since we don't need it anymore
delete current_primitive.vertices;
// Create VAO to store attributes and any data buffers we need for the primitive
glGenVertexArrays(1, &current_primitive.vao);
glBindVertexArray(current_primitive.vao);
// NB: The "pointer" parameter of glVertexAttribPointer() is tremendously
// confusing due to old design and legacy entrenchment. It's really just an
// integer byte offset within the attached GL_ARRAY_BUFFER. It tells OpenGL
// where the data for the attribute begins within the buffer.
// It is also significant and useful because it allows us to separate data
// used in separate stages of the pipeline; for example, instead of storing
// vertices one after another (AOS, Array of Structures):
/// [xy_1,rgb_1, xy_2,rgb_2, xy_3,rgb_3, xy_4,rgb_4, ..., xy_n,rgb_n]
// We can store their individual attributes in separate sub-arrays of the
// buffer (SOA, Structure of Arrays):
// [xy_1,xy_2,xy_3,xy_4, ..., xy_n, rgb_1,rgb_2,rgb_3,rgb_4, ..., rgb_n]
// Anyways, that's just a long way of saying it lets a graphics programmer
// reduce cache misses or further optimize a pipeline, just as one might do
// on the CPU side in, e.g., High Performance Computing (HPC).
// In practice we might use a fewer number of larger buffers, packed with
// multiple primitives, to avoid creating and destroying all of these
// resources every frame. We could also use a single VAO, since GL2 vertex
// data is the same for everything, and rebind it to the appropriate
// buffer(s) as we draw.
uint offset = 0;
// NB: The currently-bound GL_ARRAY_BUFFER is attached to the attributes as
// we define them. It is a common misconception that a buffer is attached to
// the VAO itself. In fact, you could bind a different VBO to each attribute.
// Define attribute 0 to position (x, y) within current_primitive.vbo
glEnableVertexAttribArray(0);
glVertexAttribPointer(
0, // location
2, // x, y
GL_FLOAT, // component type
GL_FALSE, // no normalization (our values are all floating-point)
sizeof(Vertex), // stride: how many bytes until the next position value
offset // where this value begins in memory (at the very beginning)
);
// Advance the offset by the number of bytes in the first attribute
offset += sizeof(float) * 2;
// Define attribute 1 to color (r, g, b) within current_primitive.vbo
glEnableVertexAttribArray(1);
glVertexAttribPointer(
1,
3, // r, g, b
GL_FLOAT,
GL_FALSE,
sizeof(Vertex), // the distance to the next value is the same
offset // color immediately follows position in memory
);
// Cleanup
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
current_vertex = null;
current_primitive = null;
}
glFlush() {
// Use our basic shader
glUseProgram(basic_shader);
foreach primitive in primitives_to_draw {
// This brings in our vertex attributes, which are already attached to
// our VBO, so we don't need to bind it.
glBindVertexArray(primitive.vao);
// In practice we could be dealing with an index buffer
// (GL_ELEMENT_ARRAY_BUFFER), where we would want to use
// glDrawElements().
glDrawArrays(primitive.mode, 0, primitive.num_vertices);
}
// Cleanup
glUseProgram(0);
glBindVertexArray(0);
clear(primitives_to_draw);
}
// Basic vertex shader
#version 330 core
layout(location = 0) in vec2 pos;
layout(location = 1) in vec3 color;
out vec3 frag_input;
void main() {
// Here we would apply projection, which would've been
// gl_ModelViewProjectionMatrix for FFP OpenGL.
// Now we have to supply it as a uniform and do the calculations ourselves
// (if we need it).
gl_Position = vec4(pos, 0.0, 1.0);
frag_input = color;
}
// Basic fragment shader
#version 330 core
in vec3 frag_input;
layout(location = 0) out frag_result;
void main() {
// Pass right along to color buffer 0 (in our case, the internal backbuffer)
frag_result = frag_input;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment