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 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Descriptor values, gates and maturity were read from the source; the OpenGL4 measurements quoted are CNA's own records in docs/opengl4-renderer.md and were not re-run.
CNA declares 25 public renderer identities at this snapshot, implemented by 21 families under modules/renderers (plus two shared helper targets, common/d3d and common/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. Five identities share EasyGL, and GDI recompiles the Software family's sources, 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 GraphicsBackendMaturity.hpp, not a measurement.
Families with an internals page
These seven 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/) | OPENGLES2, OPENGLES3, OPENGL33 (Production); WEBGL1, WEBGL2 (Supported) | OpenGL; GL context service; config chosen at context creation | ES/desktop profiles native only, WebGL profiles 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. |
OpenGL4 (opengl4/) | OPENGL4 (Supported) | OpenGL; GL context service; pre-window 24-bit depth, 8-bit stencil, double-buffered | System OpenGL (find_package(OpenGL)); MojoShader only with CNA_OPENGL4_COMPILED_EFFECTS | A separate desktop 4.1+ core family that shares EasyGL's stock shaders and presentation transform (see below). |
Vulkan (vulkan/) | VULKAN (Production) | Vulkan; Vulkan surface service; adapter MSAA clamp | Vulkan SDK/loader (find_package(Vulkan REQUIRED)) | Device, surface and swapchain, deferred draw, frame synchronisation, resource retirement and teardown. |
SDL_gpu (sdl-gpu/) | SDL_GPU (Supported) | Plain window | SDL3 platform required; SDL_shadercross + SPIRV-Cross (default ON on Windows and Apple) | 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 the GDI 2D reuse. |
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 fourteen 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 (Experimental) | Plain | Pinned wgpu-native (Linux, macOS, Windows) or, under Emscripten, the emdawnwebgpu port | Modern GPU APIs |
metal/ | METAL (Supported) | Metal; high-DPI backing | macOS only; Objective-C++ and Apple frameworks; SDL3 supplies the window and CAMetalLayer | Modern GPU APIs |
directx9/ | DIRECTX9 (Production) | Plain; adapter queries for profile, formats and MSAA | Windows target only (native or mingw-w64) | Windows renderers |
directx11/, directx12/ | DIRECTX11, DIRECTX12 (Production) | Plain; adapter queries for formats, depth selection and MSAA | Windows target only; shared common/d3d helpers | Windows renderers |
direct2d/ | DIRECT2D (Supported), 2D-only | Plain | Windows target only | Windows renderers |
gdi/ | GDI (Supported), 2D-only | Plain | Windows target only; recompiles the Software sources with CNA_SOFTWARE_2D_ONLY, so it cannot be co-linked with SOFTWARE | Windows renderers |
sdl-renderer/ | SDL_RENDERER (Production), 2D-only | Plain | SDL3 platform required; default renderer on every host except Linux and Emscripten | 2D-only renderers |
freedirect/ | FREEDIRECT (Experimental), 2D-only | Plain | SDL3 platform required; sibling ../free-direct (+ ../free-api); not Windows-gated | 2D-only renderers |
canvas/, html-dom/, svg-dom/ | CANVAS, HTML_DOM (Supported), SVG_DOM (Experimental), all 2D-only | Plain | Emscripten only | Browser renderers |
fna3d/ | FNA3D (Experimental) | OpenGL; pre-window 24/8/double-buffered | SDL3 platform required; fetched FNA3D and MojoShader | Translation and adapter layers |
portablegl/ | PORTABLEGL (Experimental) | None | Fetched header-only PortableGL; cannot be co-linked with any real-GL identity, OPENGL4 included | CPU renderers |
The window kinds and services above come from each family's *RendererDescriptor.cpp; platform gates from cmake/RendererSelection.cmake and cmake/RendererCombinations.cmake; the four SDL3-only families 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.
OPENGL4 and EasyGL: separate families, shared GL semantics
OPENGL4 is not a sixth EasyGL profile. It has its own directory, target (cna_renderer_opengl4), namespace and descriptor (OpenGL4RendererDescriptor.cpp), and it does not use the easy-gl or meta-gl siblings:
- Its own platforms. CNA's own summary lists native X11 (GLX) and Wayland (EGL), neither using SDL, and SDL3; its only dependency is the platform's GL library, plus MojoShader when the compiled-effects option is on.
- Its own context request.
OpenGL4Renderer.cppasks the platform's GL context service for a 4.1 core context (the highest core version macOS offers), then reads back what it actually got and throws, naming the granted version, if it is below 4.1 or not a core profile. Newer facilities (compute on 4.3+, indirect draw, anisotropy) are discovered at run time behind live checks. - Its own loader.
GL4Loader.hppresolves GL 1.2+ entry points at run time undergl4_-prefixed names; pre-1.2 functions link directly against the system GL library (its CMakeLists.txtprefers the window-system-neutralOpenGL::OpenGLand falls back toOpenGL::GL). Because those globalgl*symbols are real,PORTABLEGL, which defines them itself, is refused besideOPENGL4exactly as beside the EasyGL identities. - Its own window contract. Unlike EasyGL's descriptor, OpenGL4's requests depth, stencil and double buffering before the window exists, because a GLX visual is fixed at window creation; it uses
PlatformGlContextOwnerfrom the shared headerPlatformGlRendererState.hpp, which gainedIsCurrent()andEnsureCurrent()so that each of several devices does its work in its own context.
What decides what XNA means is shared with EasyGL through two headers that moved into the graphics module shortly before this snapshot:
GlStockShaderSources.hpp— the single GLSL ES 3.00 corpus for every stock program (BasicEffect, AlphaTest, DualTexture, EnvironmentMap, Skinned, PbrEffect, SkinnedPbrEffect, SpriteBatch), moved verbatim out ofEasyGLRenderer.cpp. EasyGL adapts it per profile (GLSL 3.30 core, ES 1.00, or ES 3.00 as written); OpenGL4 rewrites#version 300 esto#version 410 corewithAdaptGlslEs300ForDesktopCoreand compiles it inOpenGL4StockDraw.cpp. The uniform and attribute names are a contract both renderers' binders depend on; a second hand-maintained GLSL copy had drifted, which is why there is one.GlPresentationSurfaceState.hpp— the platform-neutral presentation transform (logical versus drawable size, NativeBackBuffer, FixedHeightDynamicWidth, Stretch, Letterbox, Overscan, window-to-logical mapping), formerly EasyGL's own class; EasyGL keepsusing EasyGLSurfaceState = GlPresentationSurfaceState.
The test relationship follows the same idea. EasyGLParityCorpus.cmake, generated by generate_easygl_parity_corpus.py, lists 346 renderer-neutral EasyGL registrations whose sources are rebuilt unchanged against OPENGL4 as OpenGL4_EasyGLParity_<name> (an entry that needs a library the configuration lacks, such as the gamer-services avatar tests, is left out); OpenGL4 also registers the 32 shared parity fixtures through the same list every other parity family uses. docs/opengl4-renderer.md is CNA's own summary: CNA's records there report the OpenGL4_ CTests at 407 of 407 on Wayland (EGL) and 406 of 406 on X11 (GLX), measured on one AMD Radeon 780M under Mesa through the private GPU test wrapper, with the remaining skips classified, and state that Windows and macOS remain unvalidated. Those results were not re-run for this page. RendererCombinations.cmake has no rule against OPENGL4 beside an EasyGL identity, and none of CNA's multi-renderer CI sets contains that pair; the evidence for it is a developer record, not CI: plan_opengl4_modern_graphics.md (GL4-0006) records a build with CNA_GRAPHICS_RENDERERS="OPENGL4;OPENGLES3;OPENGL33" on Wayland whose same test binaries were run against each of the three renderers, selected at run time, on one AMD Radeon 780M under Mesa. That GL4-0006 run is the baseline, measured before any renderer change (OPENGL4 283 of 405 corpus tests passing); the plan's later table “Results after GL4-0009..GL4-0018” records a rerun of the same three-renderer build (405 of 405 on OPENGL4; on the two EasyGL profiles the plan notes that no test which passed before fails). It shows that the pair links and runs in one binary, not that any of this is a CI gate (not re-run for this page). The internals are traced on OpenGL4 renderer internals.
Contract notes for FNA3D and the 2D families
These families have no source tour yet. The notes below were checked by reading the TARGET source at 009d40f5 (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 is refused by RequirePbrShadingSupportEXT before any buffer is bound (Fna3dDraw.cpp), because the stock-effect selection has no PBR case and used to fall through to BasicEffect or SkinnedEffect.
SDL_RENDERER, DIRECT2D, GDI and FREEDIRECT
SDL_RENDERERuploads each texture as anSDL_PIXELFORMAT_RGBA32SDL texture and draws sprites withSDL_RenderTexture,SDL_RenderTextureRotatedandSDL_RenderTextureAffine; readback isSDL_RenderReadPixels(SdlRenderer.cpp).DIRECT2Ddraws throughID2D1DeviceContexton a private Direct3D 11 device that no application code sees (hardware, with a WARP path forced byCNA_DIRECT2D_FORCE_WARP). Public RGBA bytes become tight BGRA at the boundary (CopyRgbaToTightBgra,CopyBgraToTightRgba), bitmaps are bounded byGetMaximumBitmapSize(), and a mipmappedRenderTarget2Dis refused by name (Direct2DRenderer.cpp).GDIpresents its CPU-rasterised RGBA8 frame withSetDIBitsToDevice(StretchDIBitswhen the presentation scales), copying the full frame by default; it copies only the dirty band whenCNA_GDI_DIRTY_PRESENTATION=1is set and the frame is unscaled and needs no repair (GdiPresentation.cpp); no OpenGL or Direct3D device is involved.FREEDIRECTcompositesClearand every sprite into a lockable off-screen shadow backbuffer sized to the logical resolution, because the free-direct primary surface never exposes a writable pointer;Present()is one identityBlt()from it, and readback locks whichever surface is active (FreeDirectRenderer.cpp).
The contract these four share is negative: ThreeD is false, 3D factories and draws fail at a predictable seam (throw by default, warn-and-stub only where honoured) and a draw is never accepted and silently discarded. Test that contract through the public resource and the public draw route, as the shared unsupported_3d_call_behavior_test.cpp fixture does (registered as a test for SDL_RENDERER and FREEDIRECT, built but not registered for CANVAS); a unit test of an internal format or state mapper proves nothing about whether GraphicsDevice refuses the call safely.
How to determine actual state
- Find the identity in
cmake/RendererIdentities.cmakeand its declared maturity and category inGraphicsBackendMaturity.hppandGraphicsBackendCategory.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, HTML DOM and SVG DOM families; DirectX 12 keeps a privateDirectX12Renderer::NotYetImplementedof the same shape), 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. GL families serialise cross-thread work through
AcquireThreadContextLeaseEXT; a family whose resource objects can outlive it (sharedITextureRenderercopies) must release and disconnect them in its own destructor, as Vulkan does. - Separate logical window size from drawable pixel size:
RendererSurfaceInfocarriesdrawableSizeanddisplayScale;GetViewportSize()is logical,GetDefaultViewportRect()physical. - Handle zero-sized or minimised surfaces without presenting invalid extents (Vulkan, for example, recreates its swapchain in
OnSurfaceChangedonly for a positive drawable size). - Keep render-target and back-buffer transitions and coordinate conventions consistent:
SetRenderTargets(nullptr, 0)returns to the back buffer; GL families store render targets bottom-up and correct 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 009d40f5, now 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, likeSetBlendEnabledandSetDepthWriteEnabled, so every family defines it. Vulkan, OpenGL4, DirectX 11 and Software store or apply the value, andDirectX11Renderer.cpprecords the silent cross-renderer divergence that existed before its override.- 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
HasRealBackBufferDepthBufferstill uses a retired renderer family as its example of a back buffer with depth but no stencil. The neighbouringSupportsStencilBuffercomment says that no renderer in the tree answersSupportsDepthBufferandSupportsStencilBufferdifferently today. That is also out of date. GDI answers depthfalseand stenciltruefor its standalone CPU stencil plane. SDL_GPU answers depth from the depth format it created and stencil only when that format has a stencil plane, 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. - A Metal test that nothing builds.
metal_spritebatch_customeffect_test.cppis referenced by no CMake file. It asserts a custom-effect path thatMetalRenderer::CreateEffectRendererrefuses at this snapshot ("Metal custom effects are disabled until the adapted renderer has passing macOS shader and pixel evidence"). It is therefore neither evidence for that path nor a regression guard for it.
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 |
|---|---|
| Vulkan | VK_LAYER_KHRONOS_validation is enabled in non-NDEBUG builds when installed; synchronisation validation is opt-in per test. In any configuration that compiles VULKAN, TestHelpers.cmake fails a renderer test on any [Vulkan Validation] output. |
| OpenGL4 | A synchronous KHR_debug callback in debug builds or with CNA_OPENGL4_DEBUG_OUTPUT=1 (verbose adds informational messages); in any configuration that compiles OPENGL4, renderer tests registered through cna_register_renderer_test in TestHelpers.cmake, the OpenGL4 example directory and the discovered unit tests fail on an [OpenGL4 GL Error] line (the exemption list is empty). |
| EasyGL | glGetError checks at specific points in the renderer; no debug-message callback is installed by CNA's EasyGL source. |
| DirectX 11 | D3D11_CREATE_DEVICE_DEBUG in non-NDEBUG builds, forced on or off with CNA_D3D11_DEBUG_LAYER=1/0; device creation retries without it when the SDK debug component is missing. |
| DirectX 12 | The debug layer is opt-in in every build type (CNA_D3D12_DEBUG_LAYER, with GPU-based validation through CNA_D3D12_GPU_VALIDATION), parsed in D3D12Configuration.cpp. |
| WebGPU | Error scopes and uncaptured-error callbacks in WebGPURenderer.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 7 of the 21 families; the other 14 are listed above with the user-level sections that describe them. There is no feature-by-feature evidence matrix for all 25 identities here: capability answers are code-derived, test registrations are not results, and several families (the Direct3D and GDI families, Metal) are chiefly exercised on hosts or under Wine and 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 25 identities and the 21 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: what the two GL families share.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.
- CANVAS, HTML_DOM and SVG_DOM: browser object models, refusals and evidence — How CNA's three non-WebGL browser renderers represent sprites, targets and blending, where each refuses work, why two cannot read the back buffer, and which browser tests actually run.
- Capability answers, interface defaults and draw evidence — What a GraphicsCapability answer asks and guarantees in CNA: the 19 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 the Direct3D renderers — How DIRECTX11, DIRECTX12 and DIRECTX9 compile custom HLSL, resolve uniform names by reflection, feed SpriteBatch and 3D draws, bind textures and build Direct3D 12 pipeline states.
- DIRECT2D and GDI: two Windows 2D delivery stacks — How CNA's two Windows-only 2D renderers draw, refuse, present and recover: Direct2D over a private Direct3D 11 device versus GDI over a private CPU 2D core, and what their tests prove.
- Direct3D evidence: MinGW cross-builds, Wine translators and native Windows — How DIRECTX9, DIRECTX11 and DIRECTX12 are built and tested: MinGW cross-builds, DXVK and vkd3d-proton gates, the shared parity inventory, forced-headless runs and the manual Windows job.
- Direct3D presentation, swap interval, clears, viewport and scissor — How DIRECTX9, DIRECTX11 and DIRECTX12 treat presentation modes, PresentInterval, back-buffer and depth formats, full screen, flip-model rebinding, viewport, scissor, clears and depth bias.
- DIRECTX11 and DIRECTX12 internals: lifetimes, frames in flight, descriptors and target finalisation — Device creation, lifetime groups and recovery, Direct3D 12 frame slots and growable descriptor heaps, HeadlessEXT, readback, MRT and cube finalisation and render-target usage on the DXGI renderers.
- 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: five GL profiles, one implementation, and the other GL-named renderers — What OPENGLES2, OPENGLES3, OPENGL33, WEBGL1 and WEBGL2 share in EasyGL, where they differ, how far evidence carries between them, and how OPENGL4 and PORTABLEGL differ.
- Evidence tiers of the native modern GPU renderers — What VULKAN, SDL_GPU, WEBGPU and METAL implement at this snapshot, what evidence backs each, why a capability bit is not evidence, and the defect shapes these renderers exposed.
- free-direct and free-api beneath the FREEDIRECT renderer — How CNA's 2D-only FREEDIRECT renderer sits on free-direct and free-api: the dependency chain, what each library implements, the fopen macro hazard and its containment, inherited limits and evidence rules.
- 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.
- SDL_RENDERER: the 2D contract, its refusals and its evidence — Where SDL_RENDERER's 2D boundary sits: execution-time 3D refusal, its single capability, emulated and unhonoured XNA features, address modes, clears, readback coordinates and how its tests run.
- 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-102: On DIRECTX9, SetContextRecoveryEnabled and SetStringMarkerEXT throw 'not yet implemented', and GraphicsDevice has already changed the recovery flag when the first one throws — DirectX9Renderer overrides both hooks with NotYetImplemented (std::runtime_error); GraphicsDevice::SetContextRecoveryEnabled stores the flag before forwarding, so the device's Texture2D shadow policy changes even though
- CNA-BUG-249: misc/CNAEXT.md's 'Renderer shader coverage for PBR' table omits OPENGL4 and METAL and still says WebGPU has no SkinnedPbrEffect path — The table lists six rows covering seven renderers (EasyGL, Vulkan, SdlGpu, WebGPU, D3D11, D3D9/D3D12) plus a fallback row, but OPENGL4 and METAL implement both PBR effects, WebGPU implements skinned PBR, and FNA3D and PO
- 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-257: PORTABLEGL depth clears force glDepthMask(GL_TRUE) and never restore it, although their own comment says both masks are restored as EasyGL does — ClearColorAndDepth, ClearDepth, ClearDepthAndStencil and ClearColorDepthAndStencil call glDepthMask(GL_TRUE) and never put the depth write mask back; only the colour mask is restored.
- 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-271: known_bugs.md still lists the FNA3D dangling-device use-after-free as OPEN and a lost repeated SpriteBatch Begin/End as a live symptom, although the source has fixed both — CNA's own known-bug list keeps two entries whose fixes are in the source and pinned by registered tests: FNA3D resource renderers now hold a shared device state that the renderer clears (Fna3d_Device_Lifetime), and repea
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- The 25 renderers at a glance · Desktop OpenGL 4 · 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