Last active
March 23, 2017 00:43
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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, ¤t_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, ¤t_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