Graphics backends
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 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Descriptor values, gates, maturity and validation hooks were read from the source.
CNA declares 10 public renderer identities at this snapshot, implemented by 8 families under modules/renderers (plus one shared helper directory, common/mojoshader, added only when a selected family needs MojoShader). An identity in the registry is evidence of selectable wiring, not of feature completeness. This page is the family map for maintainers: where each family lives, what its descriptor asks of the platform, what gates its build, and which families have a source tour in the Development area.
Start with an implementation, not an identity name
A bug report names an identity; the code to read is a family. Three identities share EasyGL, so the mapping is not one-to-one. The identity-to-accessor table with C ABI values is generated on Selection axes: renderer identities; the tables below are the family-first view. “Window” is the descriptor's RendererWindowKind and the platform service it requests; maturity is CNA's own declaration in GraphicsRendererMaturity.hpp, not a measurement.
Families with an internals page
These five families have source tours that identify what the code at this snapshot executes and where its tests stop. A tour is not a parity certificate.
| Family | Identities (declared maturity) | Window and services | Build gate and dependency | What the page establishes |
|---|---|---|---|---|
EasyGL (easygl/) | OPENGLES3, OPENGL33 (Production); WEBGL2 (Supported) | OpenGL; GL context service; config chosen at context creation | ES/desktop profiles native only, the WebGL profile Emscripten only; sibling ../easy-gl (+ ../meta-gl) | One implementation with a runtime GlProfile per identity, context, state, shader and FBO paths, context loss, tested limits. |
SDL_gpu (sdl-gpu/) | SDL_GPU (Supported) | Plain window; adapter MSAA clamp to 8, 4 or 2 samples | SDL3 platform required; SDL_shadercross + SPIRV-Cross (CNA_SDL_GPU_SHADERCROSS, default ON on Windows and Apple, where SDL picks its Direct3D 12 or Metal GPU driver) | SDL device and shader routes, queued draw and pass replay, acquire, submit and present, lifetime. |
Software (software/) | SOFTWARE (Experimental) | None; surface presenter when the platform offers only presentation (Terminal on a TTY) | Any host; compiled effects opt-in (CNA_SOFTWARE_COMPILED_EFFECTS) | The CPU framebuffer and rasteriser, texture and target storage, the optional presenter and how a presenting platform shows the frame. |
Headless (headless/) | HEADLESS (Supported) | None | Any host; CNA_HEADLESS_MODE = Fast, Validation (default) or Trace | Non-rasterising validation, resource bookkeeping, trace, truthful readback refusal and the limits of its tests. |
Stub (stub/) | STUB (Supported) | None | Any host; the renderer of the dev, unit and release-modules configure presets | The minimal no-op implementation, count-only resource objects (vertex and index counts) and what smoke tests on it can and cannot show. |
Families without an internals page yet
For these three families the Development area has no source tour. What is known about them from the source at this snapshot is on the user-level pages linked in the last column; start there, then read the family directory directly.
| Family | Identity (declared maturity) | Window | Build gate and dependency | Start at |
|---|---|---|---|---|
webgpu/ | WEBGPU (Supported) | Plain; adapter MSAA clamp | Pinned wgpu-native (Linux, macOS, Windows) or, under Emscripten, the emdawnwebgpu port | Modern GPU APIs |
directx9/ | DIRECTX9 (Production) | Plain; adapter queries for profile, formats and MSAA | Windows target only (native or mingw-w64) | Windows renderers |
fna3d/ | FNA3D (Experimental) | OpenGL; pre-window 24/8/double-buffered | SDL3 platform required; fetched FNA3D and MojoShader | Translation and adapter layers |
The window kinds and services above come from each family's *RendererDescriptor.cpp; platform gates from cmake/RendererSelection.cmake and cmake/RendererCombinations.cmake; the two SDL3-only families (sdl-gpu, fna3d) set the REQUIRES_PLATFORM SDL3 target property in their own CMakeLists.txt. Capability answers per identity are in the capability matrix and are not repeated here.
Contract notes for FNA3D
FNA3D has no source tour yet. The notes below were checked by reading the TARGET source at 3b43bd86 (not executed) and name what a maintainer is most likely to break.
FNA3D: native handles outlive the draw, targets finalise on unbind
FNA3D is one CNA identity even though the library picks its own driver at run time; the driver is an implementation detail, pinned in tests with FNA3D_FORCE_DRIVER (Tutorial 108). Every wrapper in Fna3dResources.cpp (textures, render targets and their renderbuffers, vertex and index buffers) and every effect wrapper releases its native handle through FNA3D's own deferred-disposal queue — FNA3D_AddDisposeTexture, FNA3D_AddDisposeRenderbuffer, FNA3D_AddDisposeVertexBuffer, FNA3D_AddDisposeEffect — and only while the owning device is still live. A handle that a queued batch still references is therefore freed by FNA3D after its command stream is done with it, not at C++ destruction time. Fna3dRenderer::UnbindTargetsEXT first binds no targets, then calls FNA3D_ResolveTarget for every previously bound target that is multisampled or mipped: that call is where FNA3D resolves MSAA and builds the mip chain, so a later draw samples finished contents. A PbrEffect or SkinnedPbrEffect draw selects CNA's own compiled CnaPbrEffect or CnaSkinnedPbrEffect blob, committed beside the XNA stock effects in modules/renderers/fna3d/effects, and the stock-effect selection names the vertex inputs each needs (normal, tangent, texture coordinates, and blend indices and weights for the skinned variant) before anything is bound (Fna3dDraw.cpp). The resolve-on-unbind step lives in Fna3dResources.cpp.
How to determine actual state
- Find the identity in
cmake/RendererIdentities.cmakeand its declared maturity and category inGraphicsRendererMaturity.hppandGraphicsRendererCategory.hpp. - Find its arm in
cna_configure_renderer_identity()(cmake/RendererSelection.cmake): host gates, dependencies, compile options; then any rule incmake/RendererCombinations.cmake. - Find its row in
cmake/RendererRegistry.cmakeand read the family's descriptor: window kind, services, pre-window request, adapter hooks. - Trace the resource methods, present and resize (
OnSurfaceChanged,GetDefaultViewportRect), looking for refusal branches:HandleUnsupported3DCallandEnsure3DSupported,NotYetImplemented()fromNotYetImplemented.hpp(used by the DirectX 9 family), null render-target factories, and capability switches with adefault: return truearm. - Find its focused and conformance tests in
modules/renderers/<family>/examples/CMakeLists.txtandtests/, and the CI workflow that runs them on a host that really has the API (Verification: CI).
Only after all five should documentation call a feature supported. File presence alone is insufficient, and a registered test is not a passing one.
Common implementation obligations
Every family implements the same IGraphicsRenderer contract. The obligations that recur in every source tour, with the hook that carries each:
- Translate public formats, states, primitive topology and shader binding consistently:
ClassifySurfaceFormatEXTandClassifyRenderTargetFormatEXT(no classifier meansColoronly), the normalised state ordinals fromXnaStateConversion.hpp,GpuDrawParams. - Create resources on the required device or context thread and destroy them before that device or context. EasyGL, the only family that returns a lease from
AcquireThreadContextLeaseEXT, serialises cross-thread work through it; a family whose resource objects can outlive it (retainedITextureRenderercopies, compiled effects) must release their native handles and detach them in its own destructor, as SDL_GPU does (SdlGpuRenderer.cpp). - Separate logical window size from drawable pixel size:
RendererSurfaceInfocarriesdrawableSizeanddisplayScale;GetViewportSize()is logical,GetDefaultViewportRect()physical. - Handle zero-sized or minimised surfaces without presenting invalid extents (SDL_GPU, for example, clamps the drawable size to zero in
OnSurfaceChangedand, when a minimised window acquires no swapchain texture, still submits the frame but presents nothing). - Keep render-target and back-buffer transitions and coordinate conventions consistent:
SetRenderTargets(nullptr, 0)returns to the back buffer; EasyGL stores render targets bottom-up and corrects for it when sampling and reading back. - Report unsupported capabilities honestly rather than silently succeeding: answer
SupportsCapabilitywithout a permissive default arm, return false from EXT hooks, and refuse 3D work throughEnsure3DSupportedorHandleUnsupported3DCall(which honoursUnsupported3DGraphicsCallBehavior).
Contract comments that lag the code
The renderer headers carry many design notes, and most of them are accurate. A few, read at 3b43bd86, describe an earlier state. A maintainer should trust the code over these comments:
SetDepthTestEnabled. The@noteinIGraphicsRenderer.hppsays only EasyGL honours the toggle and that other renderers throw on first 3D use. The method is pure virtual, likeSetDepthWriteEnabled, so every family defines it (the blend toggle is not a renderer virtual at this snapshot:GraphicsDevice::SetBlendEnabledinstalls aBlendStateequation itself, the fix for CNA-DEMOS-ISSUE-066, which CNA’s repair matrix records as validated on a complete checkpoint): EasyGL, DirectX 9 and FNA3D apply it to the native depth state, SDL_GPU, WebGPU and Software store it for their draw paths, Headless counts and traces it, and only Stub ignores it.- The DirectX 9 default list.
DirectX9Renderer.hppsaysCreateOcclusionQueryandCreateTexture3Dare "left un-overridden here on purpose", yet the same header overrides both. The principle stated in that comment still holds, and every family follows it: "inheriting 'throws' is fine, only inheriting silence is the trap". - Where FNA3D's stock effects come from. The
CNA_RENDERER_FNA3Dcomment inRendererSelection.cmakesays the stock-effect blobs are fetched from a pinned FNA checkout.ThirdPartyFNA3D.cmakedeclares a single fetch, of FNA3D itself, and states thatmodules/renderers/fna3d/effectsholds the binaries. Those binaries are committed, with their provenance in that directory's README. - The split depth/stencil queries. The documentation of
HasRealBackBufferDepthBuffermotivates the split with a graphics API that CNA does not ship, and the neighbouringSupportsStencilBuffercomment says that no renderer in the tree answersSupportsDepthBufferandSupportsStencilBufferdifferently today. That is out of date: SDL_GPU answers depth from the depth format it created and stencil only when that format has a stencil plane (SdlGpuRenderer.hpp), so a depth-only back buffer reports depth without stencil. The comment's reason for keeping the split still stands: the single-argumentClear(Color)must keep an available depth clear and leave out a stencil plane that does not exist.
Validation by backend
Use the API's own validation where the family wires it, but keep it supplementary to CNA's conformance tests:
| Family | Hook in the source |
|---|---|
| SDL_GPU | Debug (non-NDEBUG) builds create the SDL GPU device with SDL's debug mode on, which asks SDL's Vulkan GPU driver for its validation layer; release builds do not (SdlGpuRenderer.cpp). The family's focused CTest entries fail on Validation Error, Validation Warning or VUID- output (CMakeLists.txt). |
| EasyGL | glGetError checks at specific points in the renderer; no debug-message callback is installed by CNA's EasyGL source. |
| DirectX 9 | No debug-runtime switch in the renderer source; SetStringMarkerEXT emits D3DPERF_SetMarker labels that PIX or RenderDoc show when attached (DirectX9Renderer.cpp). |
| WebGPU | Validation error scopes and uncaptured-error callbacks in WebGPURenderer.cpp and WebGPURendererDevice.cpp. |
Run window and GPU tests through tools/platform/run_gpu_tests_private.sh: it starts a private headless Weston plus a rootful Xwayland with DRI3 on the real GPU, points DISPLAY and WAYLAND_DISPLAY at them, and runs ctest (or one command with --exec), so nothing appears on the desktop; it exits 77 when no compositor is available and refuses a build tree that forces its own test display. Compare a second family to separate a shared-layer bug from one translation layer.
What these pages do not establish
This page routes; it is not a renderer-parity certificate. The Development area has source tours for 5 of the 8 families; the other 3 are listed above with the user-level sections that describe them. There is no feature-by-feature evidence matrix for all 10 identities here: capability answers are code-derived, test registrations are not results, and the Windows paths (DIRECTX9 through DXVK, SDL_GPU on SDL's Direct3D 12 driver through vkd3d-proton) are chiefly exercised under Wine and in CI lanes that were not examined for this page. Before a risky change, inspect the chosen family's build wiring, tests and a host that can actually run it.
Read in this order
RendererIdentities.cmakeandRendererRegistry.cmake: the 10 identities and the 8 families behind them.modules/renderers: one family directory, itsCMakeLists.txt,src/*RendererDescriptor.cppandexamples/CMakeLists.txt.IGraphicsRenderer.hpp: the contract and its defaults, before reading any override.GlStockShaderSources.hppandGlPresentationSurfaceState.hpp: EasyGL's single GLSL ES 3.00 stock-shader corpus (adapted per profile) and its presentation transform, before reading a GL draw or present path.run_gpu_tests_private.sh: how GPU tests are run without touching the desktop.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Capability answers, interface defaults and draw evidence — What a GraphicsCapability answer asks and guarantees in CNA: the 17 contract questions, renderer versus device polarity, known report mismatches, the unsupported-3D policy, four interface-default failure shapes and portable draw claims.
- Compiled XNA effects: admission, reflection, passes and renderer runtimes — What happens to Direct3D 9 Effect Framework bytecode in CNA: admission order and preflight bounds, the reflected object graph, parameter upload, pass-state publication, cloning, the XNB EffectReader and per-renderer translation.
- Custom HLSL ShaderEffect on DIRECTX9 — How DIRECTX9 compiles a custom HLSL ShaderEffect, resolves uniform names through the constant table, feeds the SpriteBatch path, and why a custom effect does not reach its 3D draws.
- Direct3D evidence: MinGW cross-builds, Wine translators and native Windows — How DIRECTX9, and SDL_GPU on SDL's Direct3D 12 driver, are built and tested off Windows: MinGW cross-builds, the DXVK and vkd3d-proton gates and their Wine prefixes, the manual Windows workflow and what each evidence tier proves.
- Direct3D presentation, swap interval, clears, viewport and scissor — How DIRECTX9, CNA's Direct3D 9 renderer, treats presentation modes, PresentInterval, back-buffer and depth formats, full screen, readback, viewport, scissor, clears and depth bias.
- DIRECTX9: stock-effect bytecode, device lifecycle and oracle findings — How the XNA-fidelity renderer compiles Microsoft's stock effects, enforces GraphicsProfile from D3DCAPS9, recovers lost devices, handles targets and why its sprite projection is what it is.
- EasyGL: three GL profiles, one implementation, and how far evidence carries — What OPENGLES3, OPENGL33 and WEBGL2 share in EasyGL, where they differ, how far evidence carries between them, and how to choose among them.
- Evidence tiers of the native modern GPU renderers — What SDL_GPU and WEBGPU, the two renderers that drive a modern explicit GPU API, implement at this snapshot, what evidence backs each, why a capability bit is not evidence, and the defect shapes they exposed.
- Presentation modes, swap interval, native handles and back-buffer readback across renderers — What each renderer family does with the presentation mode, swap interval, formats and full-screen request, how window handles are borrowed, and exactly what GetBackBufferData returns.
- SpriteBatch sorting, flushing and renderer batching — How CNA's SpriteBatch flushes and sorts (XNA's unstable quicksort, reproduced), what each renderer kind does with the sprite stream, the viewport-local projection and the Direct3D 9 half-pixel offset.
- SpriteBatch: state, lifecycle and error semantics — Exact shared SpriteBatch contract in CNA: Begin defaults, when states reach the device and what End leaves behind, Immediate exclusivity, exceptions, Draw overloads, sub-pixel destinations and texture lifetime.
- SpriteFont and DrawString text layout — Exact text layout in CNA: SpriteFont tables and default character, per-glyph advance and the XNA first-glyph rule, MeasureString, UTF-8 decoding, whole-string flips, rotation and sub-pixel glyphs.
- The renderer contract: IGraphicsRenderer defaults, factories and failure shapes — Which IGraphicsRenderer bodies a renderer family must write, what each inherited default does to a public call, how null factories fail, and the evidence ladder behind a feature.
- WebGPU renderer semantics: surfaces, targets, mips and pipeline state — Exact behaviour of CNA's WEBGPU renderer: non-sRGB surface policy, target-relative SpriteBatch coordinates, cube render targets, blit-free mip generation, dynamic and baked state, ordered clears and BC textures.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-254: docs/directx9-renderer.md says the DIRECTX9 custom ShaderEffect phase (D9-11) has not been started; plans/plan_dx9.md records it fully closed on 2026-07-15 — The DIRECTX9 renderer document lists custom ShaderEffect (D9-11) as not started, while the plan it points to records D9-110, D9-111 and D9-112 all closed on 2026-07-15.
- CNA-BUG-261: Comments and docs say GpuDrawParams::specularEnabled is read by no renderer and DIRECTX9's specular variants are blocked, but D3D9EffectDraw and Fna3dDraw read it — IGraphicsRenderer.hpp says no renderer reads GpuDrawParams::specularEnabled, and two DirectX9 headers and docs/directx9-renderer.md call the specular and per-pixel-lighting variants unreachable, although DIRECTX9 and FNA
- CNA-BUG-280: On FNA3D's SDL_GPU driver a TextureCube, Texture3D or mipmapped Texture2D changed between two draws of one frame shows its new contents in the earlier draw too — SDL_GPU and WEBGPU now order a texture upload after the queued draws that sample it, so SpriteSortMode::Immediate and other mid-frame SetData calls keep XNA's per-draw contents there; FNA3D's SDL_GPU driver still submits
- CNA-BUG-281: FNA3D's Direct3D 11 driver cannot build some valid compiled effects: a ps_3_0 shader that reads two vertex outputs packed in one register fails to compile, and the integer input-layout rewrite can retype the wrong semantic — the MojoShader code behind FNA3D's Direct3D 11 driver, in CNA's pinned fork, names a struct member that does not exist when a ps_3_0 shader reads, from one register, semantics the vertex shader wrote to separate register
- CNA-PLAT-017: FNA3D has no occlusion queries on its SDL_GPU driver, the driver FNA3D tries first and the one it runs on Apple — Fna3dRenderer probes queries only on FNA3D's OpenGL and Direct3D 11 drivers; on its SDL_GPU driver (FNA3D's first choice, and its Metal route on Apple) OcclusionQuery is reported unsupported and its constructor refuses,
- CNA-PLAT-019: FNA3D refuses a PbrEffect or SkinnedPbrEffect draw whose vertex declaration has no Tangent0 on its SDL_GPU and Direct3D 11 drivers, where every other renderer draws it with a neutral tangent — FNA3D's SDL_GPU and Direct3D 11 drivers cannot build a pipeline for a shader input the declaration lacks, so the FNA3D renderer throws NotSupportedException (FNA3D-MISSING-STOCK-INPUT) for a PBR draw over tangent-free ve
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- The renderers at a glance · 2D-only renderers · What each renderer needs from the platform
- Maintainer workflow
- Fix a renderer bug · Blast radius and readiness
- Tests and validation
- Test architecture · Verification: renderers
- Reference
- Selection axes: renderer identities · Module index