Tutorial 102: The OpenGL Family

CNA Tutorials  ·  Renderers

What you’ll learn

  • How OPENGLES2, OPENGLES3, OPENGL33, WEBGL1 and WEBGL2 relate to one shared implementation.
  • Exactly which features OPENGLES2 and WEBGL1 lose, and which answers are decided at runtime by your driver.
  • Why your own ShaderEffect GLSL is not translated between profiles, even though CNA's own shaders are.
  • The sibling checkouts a GL build needs, and why EASYGL is no longer a renderer name.

Before you startTutorial 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

ValueGL flavourWhere it builds
OPENGLES3OpenGL ES 3.0, GLSL ES 3.00Not Emscripten. The Linux default, and CNA's day-to-day development renderer.
OPENGL33Desktop OpenGL 3.3 core, GLSL 3.30 coreNot Emscripten.
OPENGLES2OpenGL ES 2.0, GLSL ES 1.00Not Emscripten. Warns at configure time on non-Linux hosts.
WEBGL2WebGL 2 (GLES 3.0), GLSL ES 3.00Emscripten only. The Emscripten default.
WEBGL1WebGL 1 (GLES 2.0), GLSL ES 1.00Emscripten 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 submodulesgit 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.

CapabilityOPENGLES3 / OPENGL33 / WEBGL2OPENGLES2 / WEBGL1
MultipleRenderTargetsYesNo
OcclusionQueryYesNo
Texture3DYesNo
InstancingYesNo
MultiStreamVertexInputYesNo
MultiSampleAntiAliasingDriver — from GL_MAX_SAMPLESNo on OPENGLES2; driver on WEBGL1
AnisotropicFilteringDriver — the GL_EXT_texture_filter_anisotropic extension, on every profile
WireFrameYesDriver 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 es line becomes #version 330 core and the following precision line is dropped, because desktop core GLSL does not accept it. The shader body is otherwise identical.
  • On OPENGLES2 and WEBGL1, the whole body is transformed to GLSL ES 1.00: #version 100, in/out become attribute/varying, texture() becomes texture2D() or textureCube() depending on the sampler's declared type, FragColor becomes gl_FragColor, and layout(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

SituationProfile
Desktop Linux, no special requirementOPENGLES3 — the default, and the profile CNA develops against
Desktop, and you need desktop-GL debugging tools or a core-profile contextOPENGL33
BrowserWEBGL2 — the Emscripten default, and feature-equivalent to OPENGLES3
Browser, and the target genuinely cannot get a WebGL 2 contextWEBGL1, having removed every use of MRT, occlusion queries, Texture3D and instancing first
Old mobile or embedded GPUOPENGLES2, 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.

Where to go next