Tutorial 102: The OpenGL Family

CNA Tutorials  ·  Renderers

ℹ

What you’ll learn

  • How OPENGLES3, OPENGL33 and WEBGL2 relate to one shared implementation.
  • Which capability answers are decided at runtime by your driver.
  • How far your own ShaderEffect GLSL is adapted between profiles, and where it is not.
  • The sibling checkouts a GL build needs, and why EASYGL is not a renderer name.
  • What changes on macOS, and how to opt into compiled XNA effects on this family.

Before you start — Tutorial 20: Building and Running Your Game, because the profile is a CMake configure-time choice.

Three of CNA's 14 renderer identities are OpenGL profiles that share a single internal implementation called EasyGL, which lives in the ../easy-gl sibling library. In an ordinary single-renderer build, selecting any of the three compiles the same source tree and sets two compile definitions: CNA_RENDERER_EASYGL, the implementation identity, and CNA_GL_PROFILE_<NAME>, which names the default profile. The profile itself is a runtime value inside the renderer, which is why both native profiles can be linked into one multi-renderer build (never across the native/Emscripten divide).

⚠

EASYGL is not a renderer name. “EasyGL” names the internal implementation and the sibling library. It is not one of the 14 accepted CNA_GRAPHICS_RENDERER values, and it has no alias — a configure that passes it stops with an unknown graphics renderer error that lists the supported names. CMake compares the public names case-sensitively, so opengles3 is rejected the same way. Pick one of the three profiles instead.

The three profiles

ValueGL flavourWhere it builds
OPENGLES3OpenGL ES 3.0, GLSL ES 3.00Not Emscripten. The Linux default, and the renderer CNA's general-test and platform CI lanes select. Warns at configure time on non-Linux hosts; macOS has no native OpenGL ES, so use OPENGL33 there.
OPENGL33Desktop OpenGL 3.3 core, GLSL 3.30 coreNot Emscripten. On macOS it runs on Apple’s OpenGL 4.1 core context.
WEBGL2WebGL 2 (GLES 3.0), GLSL ES 3.00Emscripten only. The Emscripten default.

The gates are hard. Configuring OPENGLES3 or OPENGL33 under Emscripten is a FATAL_ERROR telling you to use WEBGL2; configuring WEBGL2 natively is a FATAL_ERROR pointing the other way.

The sibling checkouts

All three 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. For this snapshot CNA, sharp-runtime and meta-gl are on their apple/m4-stabilization branches and easy-gl on its default develop branch.

The GL profiles take their context from the SDL3 platform, CNA’s one windowing platform (SDL reaches X11, Wayland, Windows, macOS and Android through its own video drivers); the windowless Terminal and Headless platforms have no GL context service.

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 -b apple/m4-stabilization https://github.com/libcna/cna.git
git clone -b apple/m4-stabilization https://github.com/libcna/sharp-runtime.git
git clone https://github.com/libcna/easy-gl.git      # default branch: develop
git clone -b apple/m4-stabilization https://github.com/libcna/meta-gl.git

cd cna
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
cmake --build build --parallel

What the renderer reports

All three profiles belong to the ES 3 generation and share one feature set; what differs is decided by the live context. “Driver” means the answer is computed at runtime from that context, not baked in at compile time.

CapabilityOPENGLES3 / OPENGL33 / WEBGL2
MultipleRenderTargets, OcclusionQuery, Texture3D, Instancing, MultiStreamVertexInputYes (MRT and occlusion queries under HiDef; occlusion counts are exact on OPENGL33, a 0-or-1 flag on OPENGLES3 and WEBGL2)
MultiSampleAntiAliasingDriver — from GL_MAX_SAMPLES
AnisotropicFilteringDriver — the GL_EXT_texture_filter_anisotropic extension, on every profile
WireFrameYes on OPENGL33 (native glPolygonMode); otherwise Driver — GL_NV_polygon_mode on native ES, WEBGL_polygon_mode on WebGL
FloatRenderTargets, HalfFloatRenderTargetsDriver — the renderer checks that the float format can really be created as a target
ComputeShaders, IndirectDrawDriver — the runtime context decides: compute needs ES 3.1 or desktop GL 4.3, indirect draw ES 3.1 or GL 4.0, and WebGL has neither in any version. These profiles ask for an ES 3.0 / GL 3.3 context, so expect false unless the driver grants a newer one
CompiledEffectsBuild option — false unless configured with -DCNA_EASYGL_COMPILED_EFFECTS=ON (see below)

Three of these deserve a note. MSAA is a live query — the renderer reads GL_MAX_SAMPLES and reports whether it exceeds 1, so the same binary can answer differently on two machines.

Wireframe needs a real polygon-mode API. Earlier versions of this page said EasyGL re-expands triangles into line primitives. In this snapshot it does not, with two narrow exceptions for stock-effect routes that unchanged XNA samples use: a true answer means the context exposes a genuine polygon mode, which preserves culling, clipping, depth bias, MSAA and stencil across every triangle route, and GL_LINES expansion cannot promise that. Where the context has none — most native ES contexts without GL_NV_polygon_mode, and WebGL without WEBGL_polygon_mode — a wireframe triangle draw is refused with a System::NotSupportedException rather than approximated. Query the capability and select FillMode::Solid when it is false.

Compute and indirect draw follow the context you actually got. The renderer asks for an ES 3.0 context and Mesa routinely hands back 3.2, so the same OPENGLES3 binary can report compute on one machine and not on another. The detailed capability profile reports the same facts as separate features and limits.

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.

OPENGL33 on macOS

macOS offers no OpenGL ES, but OPENGL33 runs on Apple’s OpenGL 4.1 core context (implemented on Metal). CNA’s Apple campaign ran the profile’s full test tree on a physical Mac mini M4 with compiled effects on — 11,916 tests, no failures, with the console locked so on-screen presentation was not observed — and fixed what Apple’s driver exposed: GLSL ES sources are rewritten to a desktop header when the context lacks GL_ARB_ES3_compatibility, compiled effects take MojoShader’s GLSL ES 3.00 route when the core context refuses GLSL 1.20 (chosen by a probe compile, not by platform), and lines keep Direct3D 9’s pixel-centre offset on a driver with four subpixel bits. Two driver differences remain: Apple’s GL stores 24-bit depth as 32-bit float, so a constant DepthBias is exact only for depths in [0.5, 1), and it clamps an over-cap anisotropy when filtering rather than when the value is set. For a native Apple renderer see Tutorial 109: Metal.

Shader source across the profiles

CNA authors every one of its own built-in GL shaders once, in GLSL ES 3.00. On OPENGLES3 and WEBGL2 they run as written. On OPENGL33, where the context does not accept ES 3.00 directly, the #version 300 es line becomes a desktop core version line and the following precision line is blanked (keeping the line count, so compiler messages name the right line); #version 310 es/320 es sources become desktop 430/450 core, capped at what the context supports.

⚠

Your shaders get only the header rewrite. GLSL you hand to a ShaderEffect is passed to the GL compiler with its body untouched. An ES 3.00 source runs on all three profiles (on OPENGL33 its header is rewritten where the context needs it), but a source written as #version 330 core will not compile under OPENGLES3 or WEBGL2. If you write desktop GLSL, you ship more than one shader variant.

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. See Tutorial 52 for the ShaderEffect API itself.

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
macOS with OpenGLOPENGL33 (or the native METAL renderer)
BrowserWEBGL2 — the Emscripten default, and feature-equivalent to OPENGLES3

Limits the whole family shares

Surface formats are classified per profile. Color, Alpha8 and Dxt1/Dxt3/Dxt5 work on every profile — DXT blocks are decoded on the CPU when the driver has no S3TC support. The packed 16-bit formats, NormalizedByte2/NormalizedByte4, Rgba1010102 and the float and half-float formats are also supported, and Rg32/Rgba64 depend on a 16-bit normalized-texture extension in the live context. The seven CNA-extension formats are left to the framework's own rule. Ask GraphicsDevice::SupportsSurfaceFormatAsRenderTargetEXT() or the profile's per-format usage before creating a target in an unusual format.

Cube faces in a multiple-render-target set are implemented. Binding a RenderTargetCube face as one slot of an MRT set works, and the same cube may occupy several slots as different faces.

Compiled XNA effects (opt-in)

By default the GL family reports CompiledEffects as false. Configure with -DCNA_EASYGL_COMPILED_EFFECTS=ON to build support for XNA/FNA Direct3D 9 Effect Framework bytecode; it is off by default because it pulls in a fetched dependency (MojoShader, taken from the pinned FNA3D checkout) that the renderer does not otherwise need. It goes through MojoShader’s own GLSL adapters, in parallel to EasyGL’s own stock shaders, and applies to all three identities; on a desktop core context that refuses GLSL 1.20 (macOS) MojoShader’s GLSL ES 3.00 output is used instead. A compiled effect whose vertex shader samples a texture, or that binds a 3D or cube sampler, is refused explicitly rather than mishandled. This is separate from CustomEffects, which covers the hand-written GLSL ShaderEffect path above.

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 (also the GL route on macOS)
cmake -S . -B build-gl33 -DCNA_GRAPHICS_RENDERER=OPENGL33

# WebGL 2 (the Emscripten default)
emcmake cmake -S . -B build-web -DCNA_GRAPHICS_RENDERER=WEBGL2

cmake --build build --parallel

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