Rendering Backends

CNA backend system - selection, tradeoffs, and implementation status

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.

BackendGPU pixel testsBackend sourceOpen 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

BackendTechnologyStatusBest 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-gl sibling repository
  • OpenGL ES 3.0 only — not Metal, DirectX, or Vulkan natively
  • FillMode::WireFrame silently ignored (OpenGL ES 3.0 has no glPolygonMode)

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 ShaderEffect source compilation is not supported — bgfx requires precompiled binary shaders, and CreateEffectBackend returns nullptr
  • OcclusionQuery correctness 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::WireFrame is 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.

EffectEasyGLVulkanbgfxSDL_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.

BackendFillMode::WireFrameNotes
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.