MetaGL is a low-level, type-safe C++23 wrapper around the programmable OpenGL family.
It targets:
- OpenGL ES 2.0–3.2
- the common programmable subset of desktop OpenGL 3.3+
- WebGL through Emscripten
MetaGL does not create windows, does not create OpenGL contexts, and is not a rendering engine.
Its job is much smaller and lower-level: provide a clean, type-safe C++ interface to OpenGL itself.
Github: https://github.com/libcna/meta-gl
Website: https://metagl.libcna.com
Why does MetaGL exist?
The traditional OpenGL API is fundamentally a C API.
Operations commonly look like this:
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glCreateShader(GL_VERTEX_SHADER);
Many logically unrelated values are represented by the same primitive GLenum type.
From the C++ compiler's point of view, a buffer target, texture target, shader type, blend factor, and many other OpenGL concepts can all look like integers.
MetaGL gives these concepts distinct C++ types.
Instead of exposing raw OpenGL constants throughout application code, it provides typed enums such as:
metagl::BufferTarget
metagl::ShaderType
metagl::ClearBufferBit
and strongly typed resource handles such as:
metagl::BufferId
metagl::ShaderId
metagl::VertexArrayId
The resulting code remains very close to OpenGL:
metagl::glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
metagl::glClear(
metagl::ClearBufferBit::Color |
metagl::ClearBufferBit::Depth);
metagl::ShaderId shader =
metagl::glCreateShader(metagl::ShaderType::Vertex);
MetaGL does not try to hide what OpenGL is doing.
It tries to make it harder to use OpenGL incorrectly.
It is still OpenGL
This distinction is important.
MetaGL is not an abstraction like CNA's Renderer API.
It does not try to provide one API that can later become Vulkan, Direct3D, Metal, or software rendering.
And it is not a higher-level graphics library like EasyGL.
Calls such as:
metagl::glBindBuffer(...)
metagl::glDrawElements(...)
metagl::glTexImage2D(...)
still correspond closely to real OpenGL operations.
You still need to understand OpenGL state, shaders, buffers, textures, framebuffers, vertex arrays, and draw calls.
MetaGL is therefore useful when I want OpenGL itself, but with a better C++ interface around it.
Runtime function loading
Modern OpenGL applications cannot simply assume that every OpenGL function exists as an ordinary linked symbol.
MetaGL contains its own runtime function loading.
The host application provides a GetProcAddress-style callback:
bool ok = metagl::Initialize(my_get_proc_address);
That callback can come from whatever platform integration the application uses.
For example:
SDL_GL_GetProcAddress
glfwGetProcAddress
wglGetProcAddress
glXGetProcAddress
emscripten_webgl_get_proc_address
MetaGL then loads the OpenGL entry points it needs.
This means MetaGL itself is not tied to SDL, GLFW, Win32, X11, or another windowing system.
MetaGL does not own the window
The architecture deliberately looks like this:
Host application
│
├── creates window
├── creates GL context
└── supplies GetProcAddress
│
▼
EasyGL
│
▼
MetaGL
│
▼
OpenGL / OpenGL ES driver
The host owns:
- window creation
- OpenGL context creation
- context activation
- event processing
- buffer swapping and presentation
MetaGL owns the low-level OpenGL API boundary.
That separation is similar to a principle used elsewhere in CNA: windowing and rendering should not unnecessarily depend on each other.
MetaGL and EasyGL are different layers
MetaGL and EasyGL may initially look like two libraries solving the same problem.
They are not.
MetaGL
MetaGL is the low-level layer.
It provides:
- runtime OpenGL function loading
- typed OpenGL enums
- typed handles
- thin
metagl::gl*wrappers - context and capability information
- extension detection
- context-loss handling
- optional debugging and validation
EasyGL
EasyGL sits above MetaGL.
It provides higher-level C++ objects such as:
easygl::Device
easygl::Shader
easygl::Program
easygl::Buffer
easygl::VertexArray
easygl::Texture
Instead of manually managing an OpenGL buffer through low-level calls, EasyGL can represent it as a C++ object.
The relationship is approximately:
CNA EasyGL renderer
↓
EasyGL
↓
MetaGL
↓
OpenGL / OpenGL ES / WebGL
EasyGL therefore builds a more convenient graphics API.
MetaGL provides the OpenGL foundation underneath it.
One API across GLES and desktop OpenGL
Another purpose of MetaGL is handling the common programmable OpenGL family in one place.
The project supports OpenGL ES 2.0 through 3.2 and the common programmable subset of desktop OpenGL 3.3 and newer. Legacy OpenGL and OpenGL ES 1 are intentionally outside its scope.
There are small differences between desktop OpenGL and OpenGL ES that MetaGL can normalize.
For example, desktop OpenGL exposes:
glDepthRange
glClearDepth
using double, while OpenGL ES uses the *f variants.
MetaGL adapts those differences while preserving one consistent API for its caller.
The enum surface remains GLES-oriented, while desktop compatibility focuses on the functionality shared with the programmable OpenGL model.
WebGL support
The same architecture also works with Emscripten.
A web application creates its WebGL context and initializes MetaGL with Emscripten's OpenGL function loader.
MetaGL also provides support for browser context loss and restoration.
This matters because WebGL contexts can disappear and later be recreated by the browser.
MetaGL can track those events and reload its function table when a context is restored.
That gives the stack:
CNA
↓
EasyGL
↓
MetaGL
↓
Emscripten OpenGL layer
↓
WebGL
The same lower-level library can therefore participate in desktop OpenGL, OpenGL ES, and browser WebGL configurations.
Runtime capabilities
OpenGL version numbers alone are often not enough to know whether a feature is actually available.
MetaGL therefore tracks runtime context information and capabilities.
It can answer questions such as:
metagl::GetContextInfo();
metagl::HasExtension(...);
metagl::IsFunctionAvailable(...);
metagl::IsAngle();
ANGLE-backed contexts can also be detected.
This is especially useful because an ANGLE context may ultimately translate OpenGL ES into Direct3D, Vulkan, Metal, or another native API.
MetaGL still treats actual extension and function availability as the source of truth rather than making assumptions based only on the backend name.
Strict input validation
MetaGL also deliberately validates inputs before they reach the driver.
For example, it checks situations such as:
- integer conversion overflow
- invalid spans
- incomplete matrix data
- unsupported bit combinations
- arguments that cannot be safely represented by the corresponding GL type
These checks remain active in Release builds.
If an operation would violate the MetaGL contract, the library terminates rather than forwarding undefined or invalid data into the OpenGL driver.
This is intentionally stricter than treating every OpenGL call as an unchecked C function.
Debugging
MetaGL can optionally compile in per-call debugging.
With debug logging enabled, OpenGL operations can be recorded and checked for GL errors.
This is useful when investigating renderer problems because the log exists at a lower level than EasyGL or CNA.
The layers can therefore be debugged separately:
CNA problem?
↓
EasyGL problem?
↓
MetaGL call problem?
↓
OpenGL driver problem?
That separation helps determine where an error actually originates.
The API is already substantial
MetaGL currently contains 358 OpenGL wrapper functions.
Its tests also verify the mandatory function sets associated with OpenGL ES 2.0, 3.0, 3.1, and 3.2.
Testing can run against mocked function loading without requiring a GPU, while an optional GPU test creates a real headless EGL context and exercises MetaGL against an actual OpenGL implementation.
The project also tests its installed CMake package so that MetaGL is usable as an independent library rather than only as a source-tree dependency of EasyGL.
MetaGL is not CNA-specific
Although MetaGL belongs to the CNA ecosystem and is the foundation underneath EasyGL, it is designed as an independent library.
A normal C++ OpenGL application can use MetaGL without CNA.
For example:
My C++ application
↓
MetaGL
↓
OpenGL
or:
My engine
↓
EasyGL
↓
MetaGL
↓
OpenGL ES
CNA is simply one important consumer of that stack.
This separation is deliberate. A useful low-level OpenGL library should not need to know anything about Game, GraphicsDevice, XNA, or CNA's renderer architecture.
Why not call OpenGL directly?
It is absolutely possible to do so.
MetaGL is not required in order to use OpenGL.
Its value is the additional structure it puts around the raw API:
raw OpenGL
integer-like enums
raw GL object IDs
manual function loading
platform-specific loader choices
minimal C++ type checking
MetaGL
strong enum classes
typed GL handles
one runtime loader
capability inspection
context lifecycle tracking
validation
optional debugging
while still remaining close enough to OpenGL that someone who knows OpenGL can immediately understand what the code is doing.
A small foundation with an important role
MetaGL sits quite low in the CNA software stack:
CNA / XNA API
↓
CNA EasyGL Renderer
↓
EasyGL
↓
MetaGL
↓
OpenGL / OpenGL ES / WebGL
↓
GPU driver
Each layer has a different responsibility.
CNA provides the XNA-compatible programming model.
EasyGL provides a convenient object-oriented graphics layer.
MetaGL provides the type-safe low-level interface to the OpenGL family.
And the operating system or browser ultimately provides the actual graphics implementation.
MetaGL is therefore intentionally not a large game framework or universal renderer abstraction.
It solves a smaller problem:
make modern OpenGL, OpenGL ES, and WebGL easier and safer to use from modern C++ without tying them to a particular windowing toolkit.