EasyGL: five GL profiles, one implementation, and the other GL-named renderers
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Read from the EasyGL, OpenGL4 and PortableGL sources and example registrations at 009d40f5; no GL or browser test was executed, and the oracle cross-comparison figures are CNA's own dated records from an earlier corpus size.
Five public renderer identities — OPENGLES2, OPENGLES3, OPENGL33, WEBGL1 and WEBGL2 — are served by one implementation family, EasyGL, while two further identities with "GL" in their story, OPENGL4 and PORTABLEGL, are separate families that share little or nothing of it. This page explains what the five profiles share and where they genuinely differ at snapshot 009d40f5, how far evidence from one profile carries to another, how to read an EasyGL test baseline or screenshot, what a comparison with the XNA oracle can and cannot attribute, and how to choose among all the GL-named identities. It is for anyone choosing a GL profile, triaging a GL bug ("one profile or all five?") or reading GL evidence.
One implementation, five public contracts
All five identities build the single target cna_renderer_easygl in modules/renderers/easygl, on the sibling easy-gl library and its meta-gl loader; each identity has its own descriptor accessor and passes its own GlProfile to CreateGraphicsRendererForProfile at run time. EASYGL itself is not a selector: it is the name of the implementation and of the sibling library, and passing it to CNA_GRAPHICS_RENDERER is an unknown-name configure error. Because the profile is a run-time value, several GL identities can be linked into one binary, subject to the native-versus-Emscripten partition.
The shared family owns texture and buffer resources, SpriteBatch, the stock-effect programs, render targets, state translation and context-loss recovery. Profile data selects the context request, the GLSL dialect, the format routes and the feature limits, and every such decision goes through the predicates in GlProfile.hpp (UsesGlslEs100, IsDesktopCoreProfile, UsesEs2ApiGeneration, RequiresBaseVertexPointerRebase). Sharing reduces duplication but does not erase API differences: the ES 2.0 generation lacks facilities the others have, and a browser context is lost, restored and presented differently from a desktop one. The source tour of the family is EasyGL renderer internals.
The five profiles
| Identity | Context and GLSL | Role and boundary |
|---|---|---|
OPENGLES2 | Native GLES 2.0; GLSL ES 1.00 | The narrowest programmable profile. Its ES 2.0-generation limits (one sample, one render target, no indexed colour masks, no instancing or multi-stream input, no Texture3D) are pinned even when the driver grants a newer context; packed 16-bit, normalized-byte, 10-bit and float formats are refused, so formats and instancing need explicit capability checks |
OPENGLES3 | Native GLES 3.0; GLSL ES 3.00 | The Linux default and the principal native evidence route: the EasyGL test suite registers only when an EasyGL native profile is the build's default renderer on a non-Windows host |
OPENGL33 | Desktop 3.3 core; GLSL 3.30 | Desktop core through the EasyGL abstraction; the only profile that draws base-vertex ranges natively instead of rebasing attribute pointers; still the EasyGL family, not OPENGL4 |
WEBGL1 | Browser WebGL 1; GLSL ES 1.00 | ES 2.0-shaped browser route with the OPENGLES2 limits and the browser's asynchronous context-loss lifecycle; Emscripten only |
WEBGL2 | Browser WebGL 2; GLSL ES 3.00 | The Emscripten default and the broader browser route. It is its own profile, not an alias of OPENGLES3: WebGL 2 is ES 3.0-shaped but has no indexed colour masks, no compute and no indirect draw |
Capabilities are asked of the live context, not inferred from the identity: an ES 3.0 request is routinely answered with ES 3.2 by Mesa, so compute (ES 3.1 or desktop 4.3) and indirect draw (ES 3.1 or desktop 4.0) are probed at run time and never offered under WebGL. The user-level comparison and build commands are in Renderers: the GL family and Tutorial 102: the OpenGL family.
Transfers, origins and state
Uploads and render-target readback use format-aware GL paths, and some public contracts are served from CPU copies instead: an ordinary Texture2D keeps a shared CPU shadow for context-loss recovery, compressed textures keep their exact DXT block streams for readback, and vertex and index buffers answer GetData from their shadows on every renderer (see The renderer contract: buffers). Evidence must say which of the two it observed: a CPU-shadow round trip is not proof of a driver readback, and a render-target readback is not proof that an ordinary texture's GPU copy is right.
Coordinate conventions are converted once, at the boundary that owns them. GL stores render targets bottom-up, so readback reverses rows to XNA's top-down order and sampling a render target applies the uRtFlipV orientation flags declared in the shared stock-shader corpus; viewport and scissor are flipped against the active target's height when they are applied; the logical-to-drawable presentation transform lives in the shared GlPresentationSurfaceState.
Redundant native state is filtered where a repeat is expensive or observable: sampler parameters are shadowed per slot and compared bit for bit, the GL viewport is shadowed per context generation, and the native polygon mode is applied only on change; the shadows are cleared on context loss so a restored context is fully re-programmed. A cache like this is only proven by a test in which a state change visibly alters pixels — a counter that the call happened cannot show that the driver received the intended value.
Effects
Stock effects are one GLSL ES 3.00 corpus, GlStockShaderSources.hpp, adapted at run time to #version 330 core, to GLSL ES 1.00, or left as ES 3.00 — and, at this snapshot, shared with OPENGL4, which rewrites it to #version 410 core. A stock-effect fix therefore changes six identities in two families. A custom ShaderEffect takes the dialect the selected profile documents (GLSL ES on the ES and WebGL profiles, desktop GLSL on OPENGL33); it is not portable source. Compiled XNA Effect Framework bytecode is a separate route owned by the CNA_EASYGL_COMPILED_EFFECTS build option (default OFF, one switch for all five identities), not something choosing a GL identity implies; Compiled XNA effects keeps the formats apart.
How far evidence carries between profiles
The family has broad shared behavioural and pixel evidence — SpriteBatch, stock effects, buffers, state, render targets and the shared cross-renderer parity fixtures registered as EasyGL_Parity_*. A result on one profile transfers to another only when the executed code and the required feature are shared. Base-vertex draws are a concrete counter-example: OPENGL33 uses glDrawElementsBaseVertex while every other profile rebases attribute pointers, so an indexed-draw result on one side says nothing about the other. The ES 2.0 generation's pinned limits are another. Browser claims additionally need a browser run: native EasyGL results do not exercise the host-owned Emscripten loop, WebGL context loss or the browser's presentation, and the native test suite registers no WebGL identity at all.
Reading an EasyGL test baseline
Do not carry a fixed "number of known EasyGL failures" from one revision to another. Registrations change with the configuration (the suite registers only for a native EasyGL default renderer, and multi-renderer builds register none of it for a secondary EasyGL identity), historical fixtures get replaced — the old two-attachment MRT test, whose expectation could not tell an inactive attachment from a single-output shader, is now EasyGL_MRT_TwoAttachments running a one-to-four-output ShaderEffect contract — and comments can lag code: the registration of EasyGL_GraphicsDevice_ReferenceStencil is still annotated as a documented known failure while EasyGLRenderer::SetReferenceStencil now re-issues the stencil function to apply the reference independently; whether that test passes was not established here. Failures that belong to the sibling easy-gl project's own tests, or to networking and audio tests in a large corpus, are evidence about those components, not about this renderer. A baseline means something only together with the exact commit, the configured registrations (ctest -N -R EasyGL), the display and driver, and the test filter; GPU test architecture describes the private-display runner.
Screenshot evidence needs a display
Unlike the SOFTWARE renderer, which renders with no display at all, EasyGL needs a window and a GL context, so a native screenshot needs a display server. The EasyGL examples build an ad-hoc, deliberately unregistered program, cna_xvfb_screenshot_demo_easygl from xvfb_screenshot_demo.cpp, which renders the shared screenshot scene and saves it with SaveBackBufferScreenshotEXT (an examples helper, not a library API) — the route used with xvfb-run and Mesa's llvmpipe. The target exists only inside the EasyGL example block, which CMake configures when SDL3 is available (CNA_ENABLE_SDL is not off), CNA_BUILD_EXAMPLES and CNA_BUILD_TESTS are on, the host is neither Windows nor Emscripten and the build's default renderer identity is OPENGLES2, OPENGLES3 or OPENGL33; a build whose default is another identity, such as a Vulkan-default multi-renderer build, does not contain it. The registered pixel test for the SpriteBatch rotate-around-origin scene is EasyGL_SpriteBatch_Rotation_Golden (easygl_spritebatch_rotation_golden_test.cpp). An earlier recorded campaign reported that its retained 400×300 captures of that scene from EasyGL and from SDL_RENDERER were byte-identical; that is cross-renderer agreement for one fixture at that earlier revision, not a general parity result, and it is not reproduced here as a current screenshot.
What a comparison with the XNA oracle can attribute
CNA's own divergence report (d3d9-divergence-report.md, CNA prose from the 31-scene era of the oracle corpus) records a one-off run of EasyGL against the real-XNA references: 10 of 31 scenes matched exactly and 21 diverged. It was deliberately not made a permanent gate, and the reasoning is the reusable part: the DIRECTX9 comparison runs both sides through the same DXVK Direct3D 9 implementation, so a pixel difference there is a CNA difference, whereas EasyGL on Mesa against references produced through DXVK mixes rendering differences with ordinary driver differences. The report separated the divergences into seventeen silhouette-edge-only rasterisation-convention differences (inherent to any two different rasterisers), two imperceptible one-or-two-step rounding differences over whole primitives, and two real defects: a negative FogEnd scene rendered solid black, and an environment-map Fresnel term that did not interpolate like XNA's per-vertex value. Both behaviours now come from the shared stock shaders — fog from FNA's CPU-prepared view-space fog vector, Fresnel evaluated per vertex as in XNA's EnvironmentMapEffect — and apply to every stock variant, not only the scenes that exposed them; those two scenes were not re-measured for this page. The current automatic gate on this family is EasyGL_XnaLineCoverage, two line scenes at tolerance 0; the corpus, its host and the later whole-corpus figures are on How renderers are verified.
OPENGL4 and PORTABLEGL are not EasyGL
OPENGL4 is an independent family: its own directory and target, its own gl4_-prefixed loader against the system GL library, a 4.1 core context that is verified after creation and refused by name if lower or a compatibility profile, and no easy-gl or meta-gl dependency. It is not "OPENGL33 with a higher number", and the public names encode neither shared code nor an upgrade path. At this snapshot it does share two things with EasyGL on purpose — the stock-shader corpus and the presentation transform — so an XNA-semantic stock-shader fix must be tested on both, while a context, loader or resource bug in one is not evidence about the other. Its stock program is chosen from the effect state and the declaration is checked against that program's inputs before any VAO is touched; a consumed element whose format cannot be converted is refused (RequireDeclarationFitsStockProgram) rather than falling into a coloured draw, while an input the declaration omits stays unbound and reads the GL default (0, 0, 0, 1), so descriptions of a hybrid, stride-driven dispatch with a coloured tail describe an older revision. Tests of custom layouts should still verify pixels or bindings. The family is traced on OpenGL4 renderer internals and taught in Tutorial 130.
PORTABLEGL sits on a different axis: CNA fetches a pinned revision of the header-only PortableGL library and runs an OpenGL 3.x-shaped pipeline entirely on the CPU. Its shaders are C vertex and fragment callbacks registered with pglCreateProgram, not GLSL, and it creates no GPU resource of any kind. Its implemented scope, stated in PortableGLRenderer.hpp, is the unlit coloured (stride 16) and textured (stride 20) BasicEffect routes, a textured SpriteBatch, and real blend, depth-stencil (including two-sided stencil and the standalone reference), rasterizer, viewport and scissor state. Lighting, fog, alpha test, skinning, dual texture, environment mapping, PBR, instancing and multi-stream input are refused deterministically, never approximated; render targets, cube and volume textures, occlusion queries, custom effects, MSAA, anisotropic filtering and mip mapping are not implemented and the capability answers say so. Because it defines the global gl* symbols itself, it cannot share a binary with any real-GL identity, OPENGL4 included. It is valuable for deterministic, device-free experiments; it is not a substitute for EasyGL conformance evidence, and agreement between the two compares independent rasterisation and shader machinery.
Choosing among the GL-named identities
| Need | Candidate | Principal caution |
|---|---|---|
| ES 2.0 or WebGL 1 hardware and drivers | OPENGLES2, WEBGL1 | ES 2.0-generation ceilings, pinned even on a newer context |
| ES 3 or WebGL 2 | OPENGLES3, WEBGL2 | Two distinct profiles: WebGL 2 has no indexed colour masks, compute or indirect draw, and native results do not prove browser behaviour |
| Desktop GL through the EasyGL family | OPENGL33 | EasyGL, not OPENGL4; the only native base-vertex profile |
| Desktop core GL 4.1+ with modern features | OPENGL4 | Separate family and loader; compute only on a 4.3+ context; validated by CNA's records on Linux only |
| OpenGL-shaped execution with no GPU | PORTABLEGL | No render targets, MRT, queries or custom effects; cannot be co-linked with real GL |
Across all of them the selector name is only the beginning of the contract. Record the implementation family, the shader language, the context source, the optional-function boundary and the verification environment alongside it — those five facts explain more real portability failures than the nominal GL version. The profile-level state, clear, target and query semantics of the EasyGL family are on EasyGL state and resource semantics.
Evidence and limits
Read from the EasyGL, OpenGL4 and PortableGL sources and the example registrations at 009d40f5; nothing was built or run. The divergence-report figures are CNA's own dated records from an earlier corpus size and were not reproduced; the byte-identical capture is described from that earlier record and is not a current result.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Architecture
- Graphics architecture: the two OpenGL families
- Internals
- EasyGL renderer internals · OpenGL4 renderer internals · Graphics backends: OPENGL4 and EasyGL
- Maintainer workflow
- Fix a renderer bug
- Tests and validation
- Test architecture: GPU tests