Tutorial 102: The OpenGL Family
What you’ll learn
- How
OPENGLES2,OPENGLES3,OPENGL33,WEBGL1andWEBGL2relate to one shared implementation. - Exactly which features
OPENGLES2andWEBGL1lose, and which answers are decided at runtime by your driver. - Why your own
ShaderEffectGLSL is not translated between profiles, even though CNA's own shaders are. - The sibling checkouts a GL build needs, and why
EASYGLis no longer a renderer name.
Before you start — Tutorial 20: Building and Running Your Game, because the profile is a CMake configure-time choice.
Five of CNA's 46 renderer identities are OpenGL profiles that share a single internal implementation called EasyGL, which lives in the ../easy-gl sibling library. Selecting any of the five compiles the same source tree and sets two compile definitions: CNA_RENDERER_EASYGL, the implementation identity, and CNA_GL_PROFILE_<NAME>, which selects the profile.
EASYGL is not a renderer name. “EasyGL” names the internal implementation and the sibling library. It is not one of the 46 accepted CNA_GRAPHICS_RENDERER values, and it has no alias — a configure that passes it fails with Unknown graphics renderer. Pick one of the five profiles instead.
The five profiles
| Value | GL flavour | Where it builds |
|---|---|---|
OPENGLES3 | OpenGL ES 3.0, GLSL ES 3.00 | Not Emscripten. The Linux default, and CNA's day-to-day development renderer. |
OPENGL33 | Desktop OpenGL 3.3 core, GLSL 3.30 core | Not Emscripten. |
OPENGLES2 | OpenGL ES 2.0, GLSL ES 1.00 | Not Emscripten. Warns at configure time on non-Linux hosts. |
WEBGL2 | WebGL 2 (GLES 3.0), GLSL ES 3.00 | Emscripten only. The Emscripten default. |
WEBGL1 | WebGL 1 (GLES 2.0), GLSL ES 1.00 | Emscripten only. |
The gates are hard. Configuring OPENGLES2, OPENGLES3 or OPENGL33 under Emscripten is a FATAL_ERROR telling you to use WEBGL1/WEBGL2; configuring either WebGL profile natively is a FATAL_ERROR pointing the other way.
The sibling checkouts
All five need ../easy-gl checked out next to your cna/ directory, and easy-gl itself expects its own ../meta-gl sibling. These are separate repositories, not git submodules — git submodule update --init will not fetch them, and a missing ../easy-gl/CMakeLists.txt is a FATAL_ERROR that says so explicitly.
Because the Linux default renderer is OPENGLES3, this means an ordinary Linux build of CNA needs three siblings present before it will configure at all: ../sharp-runtime, ../easy-gl and ../meta-gl.
cd /path/to/workspace
git clone https://github.com/openeggbert/cna.git
git clone https://github.com/openeggbert/sharp-runtime.git
git clone https://github.com/openeggbert/easy-gl.git
git clone https://github.com/openeggbert/meta-gl.git
cd cna
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
cmake --build build -j3
The profiles are not cosmetic
OPENGLES2 and WEBGL1 genuinely lose multiple render targets, occlusion queries, Texture3D, instancing and multi-stream vertex input. This is not a relabelled context — the underlying API has no draw-buffers MRT, no query objects, no 3D textures and no attribute-divisor entry points at all.
Here is what the renderer actually reports, profile by profile. “Driver” means the answer is computed at runtime from the live GL context, not baked in at compile time.
| Capability | OPENGLES3 / OPENGL33 / WEBGL2 | OPENGLES2 / WEBGL1 |
|---|---|---|
MultipleRenderTargets | Yes | No |
OcclusionQuery | Yes | No |
Texture3D | Yes | No |
Instancing | Yes | No |
MultiStreamVertexInput | Yes | No |
MultiSampleAntiAliasing | Driver — from GL_MAX_SAMPLES | No on OPENGLES2; driver on WEBGL1 |
AnisotropicFiltering | Driver — the GL_EXT_texture_filter_anisotropic extension, on every profile | |
WireFrame | Yes | Driver on OPENGLES2 — needs GL_OES_element_index_uint |
Two of these deserve a note. MSAA is a live query on the ES 3 / GL 3.3 / WebGL 2 profiles — the renderer reads GL_MAX_SAMPLES and reports whether it exceeds 1, so the same binary can answer differently on two machines. On OPENGLES2 the answer is an unconditional false, because GLES 2.0 has no multisample renderbuffers or blit and GL_MAX_SAMPLES is undefined there.
And wireframe is emulated. There is no polygon-mode API on GLES or WebGL, so EasyGL re-expands triangles into line primitives. That works on every profile — but the re-expanded indices are 32-bit, and unsigned-int element indices are an extension on ES 2.0, so OPENGLES2 reports what the runtime actually provides rather than assuming it.
This is exactly the asymmetry Tutorial 101 describes, from the other direction: the GL family is one of the renderers whose answers are worth reading, because they are authored and in several cases measured.
Shader source is not portable across the profiles
CNA authors every one of its own built-in GL shaders once, in GLSL ES 3.00, and adapts it at first use:
- On
OPENGL33, the#version 300 esline becomes#version 330 coreand the followingprecisionline is dropped, because desktop core GLSL does not accept it. The shader body is otherwise identical. - On
OPENGLES2andWEBGL1, the whole body is transformed to GLSL ES 1.00:#version 100,in/outbecomeattribute/varying,texture()becomestexture2D()ortextureCube()depending on the sampler's declared type,FragColorbecomesgl_FragColor, andlayout(location = N)qualifiers are stripped and re-applied as explicit attribute-location bindings before linking.
Your shaders do not get this treatment. GLSL you hand to a ShaderEffect is passed straight to the GL compiler, verbatim. A shader that compiles under OPENGLES3 will not compile under WEBGL1, and a shader written for OPENGL33 will not compile under OPENGLES3. If you ship more than one GL profile, you ship more than one shader variant — or you write your own header rewriter.
Integer vertex attributes are the hard case. uvec4 and ivecN have no GLSL ES 1.00 equivalent at all, so a shader that uses them simply cannot run under OPENGLES2 or WEBGL1. CNA's own skinning shaders sidestep this by encoding bone indices as floats. See Tutorial 52 for the ShaderEffect API itself.
Profile differences reach further than syntax, too. Desktop core profiles require GL_VERTEX_PROGRAM_POINT_SIZE to be enabled before a vertex shader's gl_PointSize output has any effect, while GLES and WebGL honour it automatically. CNA enables it for you on OPENGL33; the lesson is that “it works on ES 3, so it works on GL 3.3” is not a safe assumption.
Choosing a profile
| Situation | Profile |
|---|---|
| Desktop Linux, no special requirement | OPENGLES3 — the default, and the profile CNA develops against |
| Desktop, and you need desktop-GL debugging tools or a core-profile context | OPENGL33 |
| Browser | WEBGL2 — the Emscripten default, and feature-equivalent to OPENGLES3 |
| Browser, and the target genuinely cannot get a WebGL 2 context | WEBGL1, having removed every use of MRT, occlusion queries, Texture3D and instancing first |
| Old mobile or embedded GPU | OPENGLES2, with the same feature audit as WEBGL1 |
The practical way to plan a downgrade to OPENGLES2/WEBGL1 is to build the ES 3 profile first, get the game working, then grep your code for the five lost features rather than discovering them one exception at a time. Tutorial 62, Tutorial 61 and Tutorial 60 cover three of them.
Two limits the whole family shares
Non-Color surface formats do not reach the GPU. The shared Texture layer admits only SurfaceFormat::Color for every renderer except Skia, and the GL family inherits that boundary. If you need a wider texture format today, Tutorial 106 explains where it is available.
Cube faces inside a multiple-render-target set are unimplemented. Plain 2D MRT works; binding a RenderTargetCube face as one slot of an MRT set does not. The GL family shares that gap with seven other otherwise-capable 3D renderers.
Build commands
# Desktop, OpenGL ES 3.0 (the Linux default -- this is what you get with no flag)
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
# Desktop OpenGL 3.3 core
cmake -S . -B build-gl33 -DCNA_GRAPHICS_RENDERER=OPENGL33
# WebGL 2 (the Emscripten default)
emcmake cmake -S . -B build-web -DCNA_GRAPHICS_RENDERER=WEBGL2
# WebGL 1 -- fewer features, see the table above
emcmake cmake -S . -B build-web1 -DCNA_GRAPHICS_RENDERER=WEBGL1
cmake --build build -j3
The equivalent per-renderer option form (-DCNA_RENDERER_OPENGLES3=ON) also works; exactly one such option must be ON, and mixing the two forms in one configure is a mistake.