easy-gl is a C++20 RAII/OOP wrapper over OpenGL / OpenGL ES, built on top of meta-gl (a procedural type-safe OpenGL wrapper with typed handle structs, enum classes, and std::span helpers).
Main goal: Provide safe, convenient C++ resource classes that own and release OpenGL objects automatically, while delegating all low-level calls to meta-gl.
Current phase: Feature completion — all planned API tasks from PLAN.md have been implemented. Remaining work is configuration extensions (S1/S2), build hygiene (W1-W3), tests (U1/U2), and examples (V1-V3).
Key architectural decisions:
- All resource classes (
Buffer,Texture,Program, etc.) are non-copyable, move-only RAII. - Base class
detail::GenerationTrackedprovideshandle_,generation_,is_created(),native_handle(). - meta-gl typed handles (
BufferId{handle_},TextureId{handle_}, etc.) are wrapped at every call site. UniformLocation,AttribLocationare typed structs — must be wrapped explicitly.FramebufferAttachment::Color0removed; usemetagl::to_framebuffer_attachment(metagl::ColorAttachment::Color0).
Build: Clean — all targets build without errors or warnings.
Tests: Four smoke test binaries exist:
easy-gl-smoke-tests— init/device tests (no GL context required; native/host builds only, see below)easy-gl-resource-smoke-tests— resource object creation smoke tests (native/host builds only)easy-gl-context-lifecycle-tests— ResourceRegistry context-loss/restore cycle, plus GLES2/GLES3 tiered mock-loader coverageeasy-gl-webgl-tests— WebGL 1-shaped mock-loader coverage:ApiKind::WebGLclassification,GL_OES_vertex_array_objectgating, andUnsupportedFeatureException(not a crash) fromQuery/Sampler/TransformFeedback/Sync/ProgramPipeline/Texture::get_level_parameter*when the underlying entry point is missing
All tests use fake function-pointer loaders and require no real GL context, so they run identically under a native host build (cmake --preset default) and cross-compiled with the emscripten preset (cmake --preset emscripten && cmake --build --preset emscripten && ctest --preset emscripten, executed under Node). easy-gl-smoke-tests/easy-gl-resource-smoke-tests mock a desktop-OpenGL-shaped context that a real WebGL host can never report (meta-gl always classifies __EMSCRIPTEN__ builds as ApiKind::WebGL), so they are only built/registered for non-Emscripten configurations.
Library: libeasy-gl.a (static)
Example: hello-triangle-sdl — SDL2+OpenGL ES triangle demo (builds, requires display).
What does NOT work yet:
- No headless automated test execution against a real GL context (no EGL pbuffer setup in CI); all current tests use fake function-pointer loaders instead.
- No real-browser WebGL verification (
WEBGL_lose_context, actual canvas) — theemscriptenpreset only proves the code cross-compiles and the mock-loader-based logic holds under Node, not that it runs correctly against a real browser GL implementation. Config::log_callbackfield does not exist yet (S1/S2 not done).- No CMake install/package export (
find_package(easy-gl)not supported — W3). CXX_STANDARDis set to 23 in CMake but CLAUDE.md targets C++20 (W1).
New files added:
include/easygl/ScopedBind.hpp— RAII bind/unbind helperinclude/easygl/ScopedDebugGroup.hpp+src/ScopedDebugGroup.cpp— RAII GPU debug groupsinclude/easygl/UniformCache.hpp+src/UniformCache.cpp— cachedglGetUniformLocationinclude/easygl/ResourceRegistration.hpp+src/ResourceRegistration.cpp— RAII registry guardCMakePresets.json—default/release(host) andemscripten(WebAssembly,Debugbuild type soassert()-based test checks stay active under Node) configure/build/test presetstests/smoke/WebGLTests.cpp— WebGL-specific correctness coverage (see section 2)
WebGL correctness fixes (previously tracked in the now-removed webgl.md):
Device::initialize()now reuses meta-gl's ownGetContextInfo()/GetCapabilities()instead of duplicatingGL_VERSION-string parsing, and surfacesApiKind::WebGLplusCapabilities::is_webgl()/is_webgl1()/is_webgl2().Query,Sampler,TransformFeedback,Sync,ProgramPipeline::create(), andTexture::get_level_parameter()/get_level_parameterf()now checkmetagl::IsFunctionAvailable()and throwUnsupportedFeatureExceptioninstead of crashing on a null function pointer on contexts missing the underlying entry point (e.g. WebGL 1).ProgramPipeline.hppdocuments that separable shader programs have no WebGL equivalent at all (not even WebGL 2) — a permanent gap, not a version tier.VertexArrayObjectsupport below GLES/WebGL 3.0 now depends onGL_OES_vertex_array_objectbeing advertised (checked via the existing extension-based detection inCapabilities::detect_common_features()), rather than being silently assumed.- Rejected as out of scope (per user decision when reviewing
webgl.md): a real-browser/Emscripten-runtime smoke test beyond the mock-loader-basedeasy-gl-webgl-tests(see "What does NOT work yet" above).
Texture::bind()/active_bind() semantics (behavioral fix, not a meta-gl enum change):
Texture::bind(target)activates and uses texture unit 0 (TextureUnit::Texture0);Texture::active_bind(unit, target)activates and uses the given unit. Both leave the corresponding unit active afterwards. This restores behavior that had been lost in an earlier refactoring (bind()previously did not callglActiveTextureat all).- Documented directly on the two methods in
Texture.hpp, and covered by a dedicated regression test (test_texture_bind_and_active_bind_semanticsintests/smoke/SmokeResourceTests.cpp) that first activatesTexture1, then checksbind()switches back toTexture0, then checksactive_bind(Texture1, ...)usesTexture1. - The stale
texture.bind(unit)example inCLAUDE.mdwas corrected tobind(target)/active_bind(unit, target).
Major additions to existing files:
Device: generic state getters (get_boolean/float/integer/integer64), indexed blend/color/enable, advanced draw calls, debug message API, ES 3.1+/3.2+ methodsProgram:set_uniform_iv/uiv,set_program_uniform*(separable),get_uniform_fv/iv/uiv, introspection,load_binary,create_separable,set_parameterTexture: copy ops, buffer textures, compressed sub-image, parameter getters, set_parameter array variantsBuffer:get_parameter,get_parameter64,get_pointerVertexArray: separate vertex format (K1), constant attribs (L1), attribute getters (F4)Framebuffer:attach_texture,blit_to,invalidate_sub,set/get_parameter,get_attachment_parameterShader:load_binarySampler,Query,Sync,Renderbuffer: parameter getters added
Types.hpp additions: ProgramParameter, ProgramBinaryFormat, ShaderBinaryFormat, BufferParameter, RenderbufferParameter, FramebufferAttachmentParameter, FramebufferDefaultParameter, TextureLevelParameter, SyncParameter
No blocker — the build is clean, all planned API tasks are done.
The only remaining gap before the library is fully production-ready:
Config::log_callback(S1) is missing, soenable_debug_logging = truehas nowhere to send output.- CMake C++ standard mismatch:
CXX_STANDARD 23vs. documented target of C++20 (W1).
- incomplete —
Config::enable_debug_logging = truesilently does nothing (no callback hooked up). Fix: implement S1/S2. - incomplete —
Query::result_u64()uses 32-bitglGetQueryObjectuivinternally (meta-gl does not exposeglGetQueryObjectui64v). Returns values ≤ UINT32_MAX only. Suitable for frame counts; not for high-resolution GPU timers. - incomplete — No headless test runner. Smoke tests compile but require an EGL/display to actually execute GL calls.
- incomplete —
draw_elements_base_vertexand similar ES 3.2+ functions compile but will fail at runtime on ES 3.0 contexts (expected; no version guard). - incomplete — W3: no
find_package(easy-gl)support; downstream users must useadd_subdirectory. - needs verification —
Program::create_separablesetsGL_PROGRAM_SEPARABLEbefore linking; this is correct for ES 3.1+ but untested at runtime.
OpenGL / OpenGL ES
↓
meta-gl — procedural, typed, no ownership
↓
easy-gl — OOP, RAII, move-only ownership
Resource ownership model:
- Every GL object class holds
handle_(unsigned int) fromGenerationTracked. - Destructor calls
destroy()which calls the appropriateglDelete*. - Move constructor/assignment zero out the source handle to prevent double-delete.
reset_handle_no_gl()zeros without calling GL (used after context loss).
meta-gl integration rules:
- Always wrap
handle_in the typed struct at call sites:metagl::BufferId{handle_}. UniformLocation{location}andAttribLocation{index}must be explicit.- Never call raw
glXxx()if meta-gl already wraps it.
Context lifecycle:
ResourceRegistryimplementsmetagl::ContextListener.- On context loss:
OnContextLost()callsrelease_gl_handle_only()on all registered resources. - On context restore:
OnContextRestored()callsrecreate_gl_resource(). ResourceRegistration(T1) provides RAII auto-register/deregister.
Stable boundaries (do not break):
- Public header API — downstream projects depend on it.
GenerationTrackedbase — all resource classes inherit it.- meta-gl is the only low-level GL dependency; no raw GL includes in easy-gl headers.
# Configure (from repo root)
cmake -B build -DEASYGL_BUILD_TESTS=ON
# Build everything
cmake --build build
# Run smoke tests (no GL context needed for init tests)
./build/tests/easy-gl-smoke-tests
# Run hello-triangle example (requires display + SDL2 + OpenGL ES)
./build/examples/integration-placeholder/hello-triangle-sdl/hello-triangle-sdl
# Check build only (no run)
cmake --build build 2>&1 | grep -E "error:|warning:"-
S1 — Add
log_callbacktoConfig- Files:
include/easygl/Config.hpp - Add
std::function<void(std::string_view)> log_callback;field. - Verify:
cmake --build buildpasses cleanly.
- Files:
-
S2 — Add
auto_error_checktoConfig+ wire it inDevice- Files:
include/easygl/Config.hpp,src/Device.cpp - When
auto_error_check = true, callglGetError()after operations in debug builds and invokelog_callback. - Verify: build passes; optionally add a test.
- Files:
-
W1 — Fix CMake C++ standard
- File:
CMakeLists.txt - Change
CXX_STANDARD 23toCXX_STANDARD 20; addCXX_STANDARD_REQUIRED ON. - Verify:
cmake --build buildpasses.
- File:
-
W2 — Verify
EASYGL_BUILD_TESTSoption works cleanly- File:
CMakeLists.txt,tests/CMakeLists.txt - Ensure
cmake -B build -DEASYGL_BUILD_TESTS=OFFbuilds without the test targets. - Verify:
cmake --build build -DEASYGL_BUILD_TESTS=OFFproduces no test binaries.
- File:
-
W3 — Add
cmake/EasyGlInstall.cmakepackage export- Files:
CMakeLists.txt, newcmake/EasyGlInstall.cmake - Add
configure_package_config_file+write_basic_package_version_file. - Verify:
cmake --install build --prefix /tmp/easy-gl-installand check foreasy-glConfig.cmake.
- Files:
-
U1 — Add one real headless test using EGL pbuffer
- Files: new
tests/headless/HeadlessContext.hpp,tests/headless/BufferTest.cpp - Create EGL pbuffer context; test
Buffer::create_vertex_buffer+is_valid_gl_object(). - Verify:
./build/tests/easy-gl-headless-testspasses.
- Files: new
-
V1 — Add textured-quad example
- Files: new
examples/hello-textured-quad/ - Demonstrate
Texture::create_2d_rgba8,Sampler::create_linear_clamp,ScopedBind. - Verify: example builds and links.
- Files: new
-
V3 — Add UBO example
- Files: new
examples/hello-ubo/ - Demonstrate
BufferwithUniformBuffer,Program::uniform_block_index,set_uniform_block_binding. - Verify: example builds and links.
- Files: new
- Do not redesign or refactor existing resource classes — the API is stable.
- Do not add C++23-only features (modules,
std::expectedeverywhere) — target is C++20. - Do not change the meta-gl dependency version without checking easy-gl compatibility first.
- Do not add new resource classes (e.g.
Pipeline,AtomicCounter) without a clear use case. - Do not merge
developintomasteruntil S1/S2 and W1 are done. - Do not introduce a global error-handling philosophy — use the existing
Exceptionclass. - Do not mass-rename existing public API methods — downstream breakage.
Read NEXT.md first. Then inspect only the files needed for the first task listed in section 8.
Do not refactor unrelated code. Make one small, verified improvement. Run:
cmake --build build 2>&1 | grep -E "error:|warning:"
to verify the build is clean after your change. Update NEXT.md section 3 (Recent changes)
and section 2 (Current status) to reflect what you did. Then stop and report.