No Nonsense OpenGL

Complete newcomers to OpenGL may find a collection of strange and confusing tutorials online. They may be surprised to learn that what works on a desktop machine fails to work on a Raspberry Pi or an Android device. Here is a bit of a breakdown of OpenGL.

OpenGL and OpenGL ES

Firstly, OpenGL is actually a specification not an implementation. There are two broad specifications of OpenGL: OpenGL and OpenGL ES. OpenGL is for Desktop applications, and OpenGL ES ("Embedded Systems") is for embedded. For example, Raspberry Pi and Android devices are considered to be embedded, so here you find OpenGL ES. OpenGL ES is a subset of the desktop specification.

Immediate Mode

For some time OpenGL supported something called "immediate mode". If you see code like this you are looking at immediate mode:

glBegin(GL_POLYGON);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, -1.0f);
glVertex3f(-1.0f, 0.0f, -1.0f);
glVertex3f(-1.0f, 0.0f, 0.0f);
glEnd();

OpenGL versions before 2.0 only supported immediate mode style programming. With version 2.0 came the first support for GLSL, which is the name given to the shader language that modern OpenGL implementations use to render graphics. At OpenGL 3 immediate mode was dropped (immediate mode was dropped from OpenGL ES at version 2).

Learn GLSL shaders

Ignore tutorials using immediate mode and learn GLSL shaders. You are unlikely to find a lower version than OpenGL 2 on the desktop. Remember that OpenGL ES 2 was the first to drop immediate mode from the specification. This illustrates something else: Equal OpenGL and OpenGL ES version numbers do not mean you can expect the same functionality. It is impossible (or difficult) to write identical code that runs on OpenGL and OpenGL ES.

A very simplistic description of shaders (compared to immediate mode) is you load your raw vertex, normal and texture data over to the GPU, you compile and load small programs to run on the GPU, and then you "trigger" the GPU to run these programs over your data. The result is put into a chunk of memory in the GPU representing your screen pixels.

Mesa

You will see this name pop up from time to time but by and large you do not really need to know about it. Since OpenGL is actually just a specification, you need to have an implementation of that specification to work with. Mesa is the open-source implementation of the OpenGL specification. Mesa code maps OpenGL functions to hardware specific functionality (drivers). The proprietary Nvidia graphics card driver implements the OpenGL specification without Mesa. If a specific hardware card does not support some functionality, Mesa may implement that functionality in software. So you can use OpenGL on a physical machine that has no hardware accelerated graphics card and Mesa will provide a software implementation for you.

Ignore GLUT

Ignore GLUT and use GLFW instead. Both do approximately the same thing: OpenGL operations can only be done once you have an OpenGL "context" active. This might be a small window on Linux or Windows, a window in a web page (WebGL) or a full-screen context with no windowing system (EGL). GLUT and GLFW give you a way to get this context in a windowing system easily. Otherwise you have to do a bunch of tricky stuff manually.

GLSL versions

From OpenGL 3.3 onward, OpenGL versions and GLSL versions line up. Prior to that the versions are all over the place.

OpenGL Version      GLSL Version
2.0                 1.10
2.1                 1.20
3.0                 1.30
3.1                 1.40
3.2                 1.50
3.3                 3.30
4.0                 4.00
4.1                 4.10
4.2                 4.20
4.3                 4.30
4.4                 4.40
4.5                 4.50

They are specified in the GLSL source code without the decimal place, for example the GLSL version 1.50 directive looks like this:

#version 150

Furthermore, OpenGL ES GLSL versions are different from the desktop. OpenGL ES 2 has a GLSL version of 1.0:

OpenGL ES Version   GLSL Version    Directive
2.0                 1.00            #version 100
3.0                 3.00            #version 300 es
3.1                 3.10            #version 310 es

In my (limited) experience picking OpenGL 2 and OpenGL ES 2 offers the best compatibility. It works on Raspberry Pi, Android and Desktop with fairly small modifications. However OpenGL 2 is very old and lacking features. In my opinion there is no "clean" way to support different versions without simply having different source code (and shaders) for each version you want to target.

For now

OpenGL is a bit of a mess. The whole extensions thing means it can be really difficult to build something that works on a variety of platforms. If you pick the lowest common denominator you end up with bad looking graphics. You almost have to completely rewrite your rendering back-end for the different platforms you want to target. If you just want to write games then use Unity, Unreal or GoDot. Vulkan is supposed to be the next big thing, but damn it looks complex. It does seem that unless you want to do something really different you should just use a game engine.

It's a bit of a pity actually. You find OpenGL implementations all over the place. It is the closest thing to a fully cross platform graphics standard there is. If there were a really top-grade GUI library on top of OpenGL you could write an application that runs almost anywhere. As of writing this is just not a thing. Dear ImGui is pretty awesome, but it's scope is (rightly) narrow. What I want is something like Dear ImGui with NanoVG on top of OpenGL. Then I can freely mix GUI, vector graphics and 3D graphics in the same window. I believe they call this a web browser. In fact Skia looks like a pretty good vector art library on top of OpenGL and surprise surprise - it is the graphics library behind Chrome and Firefox.

Perhaps the best way for now is pretend your application is a game and implement it in a game engine.