meta-gl is the low-level, procedural, type-safe wrapper over OpenGL / OpenGL ES.
It should make raw OpenGL safer and clearer, but it must stay thin. It is not an engine layer and it must not own OpenGL resources through RAII classes.
Layering:
OpenGL / OpenGL ES
↓
meta-gl
- procedural API
- type-safe enums and lightweight wrapper types
- std::span for data views
- constexpr-if/type-trait dispatch for typed GL variants
- small concepts where they improve compile-time validation
↓
easy-gl
- OOP/RAII resource classes
- higher-level convenience API
- ownership and lifetime management
- Replace unsafe raw OpenGL parameters with strongly typed C++ wrappers where useful.
- Avoid accidental mixing of unrelated OpenGL enum categories.
- Keep the API close to OpenGL, predictable, and easy to map back to GL calls.
- Prefer zero-overhead abstractions.
- Keep patches small, buildable, and easy to review.
Target C++23 as the portable baseline.
Allowed and encouraged:
enum classstd::spanconstexpr if- simple
concepts - type traits
- tag dispatching where useful
- lightweight strong typedefs / small wrapper structs
Use carefully:
std::ranges, only when it stays simple and portableconsteval, only for small compile-time validation helpers
Do not introduce:
- C++ modules
- C++26-only features
std::expectedas the core low-level error model- heavy template metaprogramming
- complex range pipelines
- large framework abstractions
Use separate enum types for separate OpenGL domains.
Examples:
enum class TextureTarget : uint32_t;
enum class BufferTarget : uint32_t;
enum class ShaderType : uint32_t;
enum class TextureMinFilter : uint32_t;
enum class TextureMagFilter : uint32_t;
enum class TextureWrapMode : uint32_t;Do not use one generic enum for unrelated OpenGL concepts.
meta-gl functions should look like safe OpenGL functions, not like engine objects.
Good:
void bindTexture(TextureTarget target, TextureId texture);
void setTextureParameter(TextureTarget target, TextureMinFilter filter);
template<typename T>
void bufferData(BufferTarget target, std::span<const T> data, BufferUsage usage);Avoid:
class Texture;
class Buffer;
class ShaderProgram;Those belong in easy-gl.
Prefer explicit handle wrappers over raw GLuint where it improves safety.
Example:
struct TextureId { GLuint value{}; };
struct BufferId { GLuint value{}; };
struct ProgramId { GLuint value{}; };These types must not own resources. They are just typed identifiers.
Prefer:
template<typename T>
void bufferData(BufferTarget target, std::span<const T> data, BufferUsage usage);Avoid raw pointer + size pairs unless required for direct GL compatibility.
For APIs such as uniforms or vertex attributes, prefer one readable template dispatch instead of many copy-pasted overloads.
Example pattern:
template<typename T>
void uniform1(UniformLocation location, T value) {
if constexpr (std::same_as<T, float>) {
glUniform1f(location.value, value);
} else if constexpr (std::same_as<T, int>) {
glUniform1i(location.value, value);
} else if constexpr (std::same_as<T, unsigned int>) {
glUniform1ui(location.value, value);
} else {
static_assert(always_false<T>, "Unsupported uniform type");
}
}Small concepts are allowed for clear API boundaries.
Good:
template<typename T>
concept UniformScalar =
std::same_as<T, float> ||
std::same_as<T, int> ||
std::same_as<T, unsigned int>;Avoid large concept hierarchies that make compiler errors hard to understand.
meta-gl should not become a large error-handling framework.
Prefer:
- debug assertions
- optional debug validation helpers
- OpenGL debug callback support where appropriate
- simple return values for query-like operations
Avoid making every function return expected or custom result objects.
Keep dependencies minimal.
Do not add large external libraries for basic OpenGL wrapping.
meta-gl must not depend on easy-gl.
easy-gl depends on meta-gl.
meta-gl should expose safe low-level operations that easy-gl can use to implement OOP/RAII resource classes.
When modifying this project:
- Read this file first.
- Keep changes small and reviewable.
- Prefer one concept or enum group at a time.
- Do not redesign the whole project in one patch.
- Do not introduce RAII or OOP resource ownership into meta-gl.
- Do not add modules or experimental C++26 features.
- Build after changes when possible.
- Show the diff before continuing with more refactoring.
Good initial refactoring steps:
- Introduce typed OpenGL enum classes for one domain, such as texture targets.
- Replace a few raw
GLenumparameters with those enum classes. - Add
std::spanto buffer upload functions. - Add typed dispatch for one uniform function family.
- Add typed handle wrappers for textures, buffers, shaders, and programs.
Avoid large all-at-once rewrites.