Architecture

meta-gl is designed around a single principle: replace unsafe OpenGL integer parameters with strongly-typed C++ types, with zero extra cost.

Design Goals

Stack Overview

┌─────────────────────────────────────────┐
│         Host Application                │
│  (creates window + GL context)          │
└──────────────────┬──────────────────────┘
                   │ GetProcAddress
┌──────────────────▼──────────────────────┐
│           easy-gl  (easygl::)           │
│   OOP/RAII: Texture, Buffer, Program    │
│   owns and manages GL object lifetimes  │
└──────────────────┬──────────────────────┘
                   │ calls metagl::gl*
┌──────────────────▼──────────────────────┐
│           meta-gl  (metagl::)           │
│   ● enum class wrappers                 │
│   ● function pointer table              │
│   ● context / capability detection      │
│   ● optional debug logging              │
└──────────────────┬──────────────────────┘
                   │ real GL calls
┌──────────────────▼──────────────────────┐
│     OpenGL ES driver / WebGL runtime    │
└─────────────────────────────────────────┘

Namespace Layout

SymbolLocationPurpose
metagl::gl*Functions.hppWrapper functions, one per GL call
metagl::BufferTarget, etc.Enums.hppType-safe GL enum domains
metagl::GLuint, etc.Types.hppRe-exported GL primitive types
metagl::Initialize()Loader.hppFunction pointer table init
metagl::GetContextInfo()Context.hppContext status and version
metagl::GetCapabilities()Capabilities.hppRuntime ES/WebGL capabilities
metagl::debug::*Debug.hppCall logging (opt-in compile flag)
metagl::to_string()EnumNames.hppHuman-readable enum names for logging

Enum Strategy

Each OpenGL parameter domain maps to its own enum class backed by GLenum. This means the compiler refuses to accept a BufferTarget where a TextureTarget is expected, and vice versa — even though both are uint32_t at runtime.

// Raw OpenGL — both compile, second is a bug:
glBindBuffer(GL_ARRAY_BUFFER, vbo);          // correct
glBindBuffer(GL_TEXTURE_2D, vbo);            // silent bug!

// meta-gl — second is a compile error:
metagl::glBindBuffer(metagl::BufferTarget::Array, vbo);        // correct
metagl::glBindBuffer(metagl::TextureTarget::Texture2D, vbo);   // ERROR

For bitfield parameters (like glClear), meta-gl defines operator| on the enum class:

metagl::glClear(metagl::ClearBufferBit::Color | metagl::ClearBufferBit::Depth);
//              ↑ returns ClearBufferBit, not a raw integer

Lightweight Handle Types

Raw GLuint is used for everything in OpenGL — textures, buffers, shaders, programs. meta-gl wraps them in minimal named structs so they cannot be accidentally swapped:

struct TextureId { GLuint value{}; };
struct BufferId  { GLuint value{}; };
struct ProgramId { GLuint value{}; };
struct ShaderId  { GLuint value{}; };

These structs own nothing. They are plain data. RAII lifetimes belong in easy-gl.

Function Pointer Loading

meta-gl does not link directly against any GL library. Instead, it stores a table of void* function pointers loaded at runtime via the host-provided GetProcAddress callback. This allows the same binary to run on OpenGL ES 2.0 through 3.2 and WebGL, gracefully skipping unavailable functions.

// Internal (simplified) — actual table is in Functions.cpp:
static PFNGLBINDBUFFERPROC       _glBindBuffer       = nullptr;
static PFNGLCREATESHADERPPROC    _glCreateShader     = nullptr;
// ... ~400 pointers total

bool metagl::Initialize(GlGetProcAddressFn loader) {
    _glBindBuffer    = (PFNGLBINDBUFFERPROC)   loader("glBindBuffer");
    _glCreateShader  = (PFNGLCREATESHADERPPROC) loader("glCreateShader");
    // ...
    return _glBindBuffer != nullptr; // core set check
}

C++20 Features Used

FeatureWhere usedPurpose
enum classEnums.hppType-safe GL parameter categories
std::spanFunctions.hpp, examplesBuffer data views without raw pointer+size
constexpr ifDebug.hppZero-cost branch on template type
Concepts (requires)Debug.hppOptional to_string() detection
Fold expressionsDebug.hpp fmt_argsVariadic argument formatting
[[nodiscard]]Context.hpp, Capabilities.hpp, Loader.hppPrevent ignoring important return values
noexceptContext.hppContext state queries are non-throwing
std::string_viewLoader.hpp, Debug.hppNon-owning string parameters

Not used: C++ modules, std::expected, std::ranges pipelines, heavy SFINAE, consteval, C++26 features. The design deliberately avoids anything that would hurt portability or compiler error readability.

What meta-gl Is NOT

Refactoring Rules

When contributing to meta-gl, follow these constraints:

  1. Read CLAUDE.md before making changes.
  2. Keep patches small and reviewable — one concept or enum group at a time.
  3. Do not redesign the whole project in one patch.
  4. Do not introduce RAII or OOP resource ownership into meta-gl.
  5. Do not add modules or experimental C++26 features.
  6. Build after every non-trivial change.
  7. Show the diff before continuing with more refactoring.