# ==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]]