EasyGL: three GL profiles, one implementation, and how far evidence carries
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 c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Read from the EasyGL sources and example registrations at c1c316b9; no GL or browser test was executed, and the oracle cross-comparison figures are CNA's own dated records from an earlier corpus size.
Three public renderer identities — OPENGLES3, OPENGL33 and WEBGL2 — are served by one implementation family, EasyGL. They are CNA's whole OpenGL family: OPENGL33, with a 3.3 core contract, is the one desktop identity. This page explains what the three profiles share and where they genuinely differ at snapshot c1c316b9, 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 the three. It is for anyone choosing a GL profile, triaging a GL bug ("one profile or all three?") or reading GL evidence.
One implementation, three public contracts
All three 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 (IsDesktopCoreProfile, HasTextureSwizzle, RequiresBaseVertexPointerRebase). Sharing reduces duplication but does not erase API differences: WebGL 2 lacks facilities the native profiles 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 three profiles
| Identity | Context and GLSL | Role and boundary |
|---|---|---|
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; CNA's desktop OpenGL identity, with a 3.3 core contract |
WEBGL2 | Browser WebGL 2; GLSL ES 3.00 | The Emscripten default and CNA's GL route in the browser, with the browser's asynchronous context-loss lifecycle. It is its own profile, not an alias of OPENGLES3: WebGL 2 is ES 3.0-shaped but has no indexed colour masks, no texture swizzle, 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. An OPENGL33 build whose driver grants a 4.3 or newer context therefore answers yes for compute: the answer is a property of the context, not of the identity. 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 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 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 for OPENGL33 or left as ES 3.00. A stock-effect fix therefore changes all three identities at once. A custom ShaderEffect takes the dialect the selected profile documents (GLSL ES on OPENGLES3 and WEBGL2, 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 three 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. Texture swizzle, present on the two native profiles and absent from WebGL 2, is 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 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.
Choosing among the three profiles
| Need | Candidate | Principal caution |
|---|---|---|
| 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 | OPENGL33 | A 3.3 core contract; the only native base-vertex profile; compute and indirect-draw answers depend on the context the driver grants |
Across all of them the selector name is only the beginning of the contract. Record the profile, 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 sources and the example registrations at c1c316b9; 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: identities and families
- Internals
- EasyGL renderer internals · Graphics backends
- Maintainer workflow
- Fix a renderer bug
- Tests and validation
- Test architecture: GPU tests