# ==OpenGL==, learning log <p class="doc-sub">// status: seedling</p> OpenGL is the API I reach for when I want to get a triangle on screen in an afternoon. It's showing its age — the state machine is a historical accident and "modern" OpenGL still carries 1992's baggage — but the iteration time is unbeatable for prototyping. These are the bits I keep relearning. For the quiet-but-explicit alternative, see [[Vulkan - learning log]]. # Versions, profiles, and what you should actually use - **OpenGL 1.x / fixed-function** — `glBegin` / `glEnd`. Don't. Every OpenGL tutorial older than 2012 teaches this. Ignore them. - **OpenGL 3.3 core** — the earliest version where the code you write today looks reasonable. Widely supported. Good floor. - **OpenGL 4.5+** — direct state access (DSA), compute shaders, bindless textures, SPIR-V ingest. If your target supports it, use it. DSA alone is worth the upgrade. - **OpenGL ES 3.x / WebGL 2** — subset for mobile/web. Roughly GL 3.3-flavoured with some GL 4 features. On macOS, Apple froze OpenGL at 4.1 (no compute, no DSA) and deprecated it entirely in favour of Metal. If I'm targeting Mac, I reach for Vulkan via MoltenVK or just write Metal. # The pipeline in one paragraph Vertex data lives in a ==VBO== (Vertex Buffer Object). A ==VAO== (Vertex Array Object) remembers the attribute layout that binds those buffers to shader inputs. An optional ==EBO== (Element Buffer Object) holds indices. `glDrawElements` triggers the pipeline: vertex shader → optional tessellation → optional geometry shader → rasterizer → fragment shader → framebuffer. ```c GLuint vao, vbo; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); // Attribute 0: vec3 position at offset 0 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); glEnableVertexAttribArray(0); ``` With DSA (GL 4.5+) the same thing doesn't bind anything globally: ```c glCreateBuffers(1, &vbo); glNamedBufferStorage(vbo, sizeof(verts), verts, 0); glCreateVertexArrays(1, &vao); glVertexArrayVertexBuffer(vao, 0, vbo, 0, sizeof(Vertex)); glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(vao, 0, 0); glEnableVertexArrayAttrib(vao, 0); ``` # Shaders GLSL. Compiled at runtime (or ingested as SPIR-V in 4.6). The shader stages: - **Vertex** — one invocation per vertex; writes `gl_Position`. - **Tessellation control / evaluation** — optional; subdivide patches. - **Geometry** — optional; emit new primitives. Notoriously slow on many drivers, often better done via compute or instancing. - **Fragment** — one invocation per _sample_ (with MSAA) or pixel; writes colour and depth. - **Compute** — not part of the draw pipeline; see [[Compute shaders]]. Uniforms send data from CPU to shader. Three main flavours: - `glUniform*` — individual values per draw. Fine for small stuff. - **Uniform Buffer Object (UBO)** — for blocks of uniforms shared across draws. Watch the `std140` layout: `vec3` is padded to `vec4`, and this will bite you. - **Shader Storage Buffer Object (SSBO)** — larger, writeable from shaders, `std430` layout (less padding surprises). Compute-friendly. # Textures and framebuffers Textures are the other side of state. Samplers bind texture units to shader sampler uniforms. Mipmaps are usually worth generating (`glGenerateMipmap`) unless you built them externally. Anisotropic filtering is still an extension in spirit but ubiquitous. Render to texture = create a ==Framebuffer Object==, attach colour/depth textures, draw into it, then sample it from a later pass. That's the whole foundation of post-processing, shadow maps ([[Shadow mapping]]), and [[Deferred vs forward rendering|deferred shading]]. # The state machine, gently OpenGL is a giant global `State` struct. Every function either modifies it or reads from it. This is where most of the pain lives: - A stray `glBindTexture` from the previous frame breaks your next draw silently. - Depth test, blend mode, scissor test, cull face — all global, all persist until changed. - Debug contexts (`glDebugMessageCallback`) are the single best quality-of-life upgrade. Turn them on in dev. DSA (4.5+) fixes most of this: operations take the object explicitly, no bind-to-edit. Use it everywhere you can. # Extensions The extension mechanism is how the ecosystem experiments. `glad` or `GLEW` load them for you. Useful ones: - `GL_ARB_bindless_texture` — pass texture handles as uniforms instead of binding texture units. Massively reduces draw-call overhead. - `GL_NV_mesh_shader` / `GL_EXT_mesh_shader` — modern mesh/task shader model. - `GL_ARB_multi_draw_indirect` — batch thousands of draws into one CPU call. - `GL_KHR_debug` — the debug callback mentioned above. Check the extension before using it; the fallback path matters on Intel/old-driver setups. # A sensible starter stack - **GLFW** — windowing and input. Tiny, cross-platform, no politics. - **glad** — loader, generated for whatever GL version you target. - **GLM** — GLSL-ish math in C++. - **stb_image** — loading PNG/JPG/HDR. - **Dear ImGui** — debug UI. Ship without it, develop with it. - **RenderDoc** — frame debugger. Non-negotiable. # Things that tripped me up - **`std140` padding** — a `vec3 position;` is sized as `vec4`. If your C++ struct mirrors the UBO layout, pad it or use `std430` / per-field queries. - **Y axis** — OpenGL NDC has +Y up; most image formats have +Y down. Flip in the texture load or in the shader, not both. - **Depth range** — `[-1, 1]` by default. `glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE)` (4.5+) switches to `[0, 1]`, which is better for precision and matches Vulkan. - **sRGB** — textures are usually sRGB-encoded, shaders want linear. Ask the driver to decode on sample by using an `*_SRGB` internal format, and render to an sRGB framebuffer. Don't `pow(2.2)` by hand. - **Driver differences** — NVIDIA is permissive, AMD is stricter, Intel does its own thing. Test on all three before declaring anything "works". - **`glGetError` is slow** — use the debug callback, not a polling loop. # References - [Khronos OpenGL](https://www.khronos.org/opengl/) - [LearnOpenGL](https://learnopengl.com/) — the single best modern tutorial - [docs.gl](https://docs.gl/) — searchable reference for every entry point - [OpenGL Insights](https://openglinsights.com/) — collection of deeper articles --- Back to [[Index|Notes]] · see also [[Vulkan - learning log]] · [[Compute shaders]] · [[Deferred vs forward rendering]]