Rendering Backends
How backends work
CNA separates the game-facing rendering API from the underlying rendering implementation through a set of pure virtual interfaces (IGraphicsBackend, ISpriteBatchBackend, ITextureBackend). Game code never calls backend APIs directly.
A backend is selected at build time via the CMake variable CNA_GRAPHICS_BACKEND. Only one backend is compiled per build - there is no runtime dispatch overhead. Swapping backends requires only a CMake reconfiguration.
# SDL_Renderer (2D, all platforms, simplest)
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=SDL_RENDERER
# EasyGL (2D + full 3D; Linux, Emscripten/WebGL 2)
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=EASYGL
# bgfx (2D + full 3D, multi-API)
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=BGFX
# Vulkan (2D + full 3D, all stock effects)
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=VULKAN
How well each backend is verified
Each backend gets its own full test run, because backend selection happens at compile time. These are GPU pixel-readback tests: they render a scene on a real graphics context, read the framebuffer back, and assert on actual pixel values.
| Backend | GPU pixel tests | Backend source | Open failures |
|---|---|---|---|
EASYGL |
188 / 190 | ~4,100 lines, 9 GLSL ES 3.0 programs | Two-attachment MRT; ReferenceStencil override |
VULKAN |
125 / 126 | ~9,300 lines, 23 GLSL→SPIR-V shaders | RasterizerState.DepthBias |
BGFX |
102 / 104 | ~3,900 lines, 21 .sc shaders |
RenderTargetCube depth attachment; MSAA resolve (environmental) |
SDL_RENDERER |
67 / 67 | ~1,000 lines, no shaders | None — 2D-only by design |
Every failure above is named and tracked; see Verification & Known Issues for what each one actually breaks.
Backend comparison
| Backend | Technology | Status | Best for |
|---|---|---|---|
SDL_RENDERER |
SDL3 hardware-accelerated renderer | Implemented | Broad compatibility, simple 2D workflows |
EASYGL |
OpenGL via easy-gl | Implemented | Custom shaders, rendering control |
BGFX |
bgfx cross-platform graphics library | Implemented | Multi-API (Vulkan, Metal, DX, GL) from one codebase |
VULKAN |
Direct Vulkan | Implemented | Full 2D and 3D, all stock effects, instancing, MSAA |
SDL_Renderer backend
CMake flag: -DCNA_GRAPHICS_BACKEND=SDL_RENDERER
The SDL_Renderer backend uses SDL3's built-in hardware-accelerated 2D renderer. SDL_Renderer targets OpenGL, OpenGL ES, Direct3D, Direct3D 11, Direct3D 12, Metal, or software rendering - depending on the platform and driver availability. SDL chooses automatically at runtime.
Advantages:
- Simplest integration - SDL handles all driver selection
- Widest platform portability, including mobile and web (via Emscripten)
- No additional dependencies beyond SDL3
- Good for straightforward 2D workflows
Limitations:
- Less control over rendering pipeline and shaders compared to raw OpenGL or bgfx
- Fixed-function pipeline - custom shaders require SDL3's shader support (still maturing)
Recommended for: Getting started, Windows development, broad platform portability, and 2D-focused applications.
EasyGL (OpenGL ES 3.0) backend
CMake flag: -DCNA_GRAPHICS_BACKEND=EASYGL
The EasyGL backend is a custom shader-driven rendering path using the easy-gl helper library over OpenGL ES 3.0. It supports full 2D and 3D rendering and compiles to WebGL 2 via Emscripten. The House 3D Demo runs live in the browser using this backend.
Advantages:
- Full 2D and 3D rendering pipeline —
BasicEffect,SkinnedEffect, depth test, blend, cull mode - Four shader variants by vertex stride — handles all
VertexPosition*types - Compiles to WebGL 2 via Emscripten — enables browser-based demos today
- Explicit OpenGL ES 3.0 usage — good for learning and debugging
Limitations:
- Requires the
easy-glsibling repository - OpenGL ES 3.0 only — not Metal, DirectX, or Vulkan natively
FillMode::WireFramesilently ignored (OpenGL ES 3.0 has noglPolygonMode)
Recommended for: Linux development, Emscripten/web targets, 3D rendering, and custom shader work.
bgfx backend
CMake flag: -DCNA_GRAPHICS_BACKEND=BGFX
The bgfx backend integrates bgfx - a cross-platform rendering library that supports Vulkan, Metal, DirectX 9/11/12, OpenGL, OpenGL ES, and WebGL from a single API. bgfx is fetched via CMake FetchContent at build time - no manual library installation is required.
Current state: Reached full 2D+3D pixel-verified parity with EasyGL and Vulkan as of this project's Phase 72. Working: all five stock effects (BasicEffect, AlphaTestEffect, DualTextureEffect, EnvironmentMapEffect, SkinnedEffect), RenderTarget2D/Cube (MSAA, mip, depth), Texture2D/3D/Cube SetData/GetData (including blit-based GetBackBufferData readback), all four state classes. Not implemented: custom ShaderEffect source compilation (CreateEffectBackend returns nullptr — bgfx wants precompiled binary shaders). Unverified in this project's sandbox: OcclusionQuery Begin/End wiring is real, but visible-vs-occluded pixel-count correctness can't be pixel-verified under this sandbox's software GL 2.1 driver.
Advantages:
- Same backend code targets Vulkan, Metal, DirectX, and OpenGL
- High-performance, production-proven rendering library
- Good mobile and console potential
- Future benefit: bgfx abstracts Vulkan, Metal, DX11/12, and OpenGL — enabling platforms CNA does not otherwise reach (macOS Metal, DirectX 12)
Limitations:
- Custom
ShaderEffectsource compilation is not supported — bgfx requires precompiled binary shaders, andCreateEffectBackendreturnsnullptr OcclusionQuerycorrectness can't be pixel-verified in this project's CI sandbox (software GL 2.1 driver, no dedicated-view architecture yet)- More complex integration than SDL_RENDERER
- Requires internet access during CMake configure for
FetchContent FillMode::WireFrameis not available on this backend
Recommended for: Multi-API portability goals; full 2D and 3D rendering at parity with EasyGL/Vulkan today, aside from custom shader source compilation.
Vulkan backend
CMake flag: -DCNA_GRAPHICS_BACKEND=VULKAN
The Vulkan backend is a fully featured direct-Vulkan implementation and CNA's second-most mature backend after EasyGL. It uses a dedicated 2D pipeline with push constants for SpriteBatch, and a complete 3D pipeline supporting all five stock effects, all pixel-tested. Instanced rendering (VK_VERTEX_INPUT_RATE_INSTANCE), render targets (RenderTarget2D, RenderTargetCube, MRT), MSAA 4x with subpass auto-resolve, per-slot sampler state (16 slots), GetBackBufferData pixel readback, Texture3D/TextureCube upload, custom ShaderEffect (SPIR-V, 128-byte push constants, std140), DepthStencilState compare-op and stencil ops, debug labels, and FillMode::WireFrame (VK_POLYGON_MODE_LINE) are all implemented.
Two gaps that older documentation still lists are now closed. BlendState has a real per-Blend/BlendFunction mapping covering all 13 XNA blend factors, including BlendFactor and InverseBlendFactor. OcclusionQuery now performs real per-draw-call correlation. Both landed in code after the write-ups that describe them as blocked; if you read otherwise elsewhere, that source is out of date.
Current state: Full 2D and 3D pipeline, all five stock effects pixel-tested, custom SPIR-V shaders supported. 125 of 126 GPU pixel tests pass; the one failure is RasterizerState.DepthBias.
Recommended for: Production 3D applications on Vulkan-capable desktop and mobile hardware, and anywhere you want low-level GPU control with a near-complete XNA effects pipeline.
Stock Effect backend parity
The table below shows the per-backend status for each stock XNA effect. Legend: pixel-tested = rendered output verified; compiles/links = code present but no pixel-level regression test yet; not implemented = missing; N/A = effect is 3D-only, not applicable to the 2D-only backend.
| Effect | EasyGL | Vulkan | bgfx | SDL_Renderer |
|---|---|---|---|---|
BasicEffect |
pixel-tested | pixel-tested | pixel-tested | N/A |
AlphaTestEffect |
pixel-tested | pixel-tested | pixel-tested | N/A |
DualTextureEffect |
pixel-tested | pixel-tested | pixel-tested | N/A |
EnvironmentMapEffect |
pixel-tested | pixel-tested | pixel-tested | N/A |
SkinnedEffect |
pixel-tested | pixel-tested | pixel-tested | N/A |
ShaderEffect (custom GLSL/SPIR-V) |
pixel-tested | pixel-tested | not implemented | N/A |
FillMode::WireFrame availability
FillMode::WireFrame (wireframe rendering via RasterizerState) is available on the Vulkan backend only. It is not available on EasyGL (OpenGL ES 3.0 does not support glPolygonMode), SDL_RENDERER, or BGFX.
| Backend | FillMode::WireFrame | Notes |
|---|---|---|
VULKAN |
Available | Implemented via VK_POLYGON_MODE_LINE |
EASYGL |
Not available | OpenGL ES 3.0 has no glPolygonMode; silently ignored |
SDL_RENDERER |
Not available | SDL_Renderer has no polygon mode control |
BGFX |
Not available | Not implemented in the bgfx backend |
Which backend should I choose?
For getting started or Windows: Use SDL_RENDERER. It is the simplest, most portable 2D option.
For 3D rendering or Emscripten/web: Use EASYGL. It provides full 2D and 3D, custom shaders, and compiles to WebGL 2 in the browser.
For multi-API portability: Use BGFX. It reached full 2D+3D pixel-verified parity with EasyGL/Vulkan as of Phase 72, and enables Vulkan, Metal, and DirectX from one CNA build (only custom ShaderEffect source compilation is still unsupported).
For Vulkan production use: The VULKAN backend has all five stock effects (pixel-tested), instancing, MSAA 4x, MRT, custom SPIR-V shaders, real per-factor BlendState and working OcclusionQuery. Choose it when you need full low-level GPU control on Vulkan-capable hardware.