How CNA is structured
CNA is organised into four clear, strictly separated layers - from high-level game code down to hardware-specific renderers.
Layer overview
┌─────────────────────────────────────────────────────────────────┐
│ Game / Application Code │
│ Uses: Microsoft::Xna::Framework API │
│ Example: MyGame extends Game, calls SpriteBatch::Draw() │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ XNA-Compatible Public API Layer │
│ include/Microsoft/Xna/Framework/... │
│ Game · GraphicsDevice · SpriteBatch · Texture2D · Color │
│ Vector2/3/4 · Matrix · Rectangle · GameTime · Input │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ CNA Internal Abstraction Layer (sharp-runtime support) │
│ include/CNA/Internal/Renderers/... │
│ IGraphicsRenderer · ISpriteBatchRenderer · ITextureRenderer │
│ Registry · descriptors · GraphicsRendererSelection │
└──────────┬───────────────┬──────────────┬───────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌────────────────────────────┐
│ OPENGLES3 │ │ VULKAN │ │ DIRECTX11 · METAL · BGFX │
│ WEBGL2 · … │ │ SDL_GPU │ │ CANVAS · SOFTWARE · … │
│ │ │ │ │ (50 identities total) │
└──────┬───────┘ └──────┬───────┘ └──────────────┬─────────────┘
│ │ │
└─────────────────┴──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Independent host and audio services │
│ CNA_PLATFORM: SDL3 · SDL2 · HEADLESS · TERMINAL │
│ CNA_AUDIO_PLATFORM: SDL3 · SDL2 · NULL │
└─────────────────────────────────────────────────────────────────┘
Every renderer family publishes a descriptor and namespaced factory into a generated runtime registry. A normal build emits one entry; CNA_GRAPHICS_RENDERERS can emit a compatible set. The 50 public identities include five EasyGL profiles and the newer TINYGL, IGL, PIXIJS and NANOVG identities. See the renderer reference.
Detailed architecture diagram
Layer descriptions
Game / Application Code
Your game logic. Subclasses Game, overrides LoadContent(), Update(), Draw(). Calls XNA-style APIs exclusively. Has zero knowledge of which renderer is in use - this is the contract CNA enforces. Games written against this layer are portable across renderers.
XNA-Compatible API Layer
Lives under include/Microsoft/Xna/Framework/. This is the surface that game code touches. Class names, namespaces, and method signatures mirror the original XNA 4.0 API as faithfully as practical in C++. No renderer-specific code leaks into this layer. Includes: Game, GraphicsDevice, GraphicsDeviceManager, SpriteBatch, Texture2D, Color, all math types, input surfaces, GameTime, GameComponent, etc.
CNA Internal Layer & sharp-runtime Support
Lives under modules/graphics/include/CNA/Internal/Renderers/, in the CNA::Internal::Renderers namespace. It defines renderer interfaces plus the descriptor/registry contract used before a device exists. GraphicsRendererSelection resolves the compiled default or an explicit runtime choice and then dispatches through the selected family's namespaced factory.
Renderer, platform and audio implementations
Concrete renderers live under modules/renderers/<family>/: 46 implementation families carrying 50 identities. Platform implementations separately implement CNA::Platform::IPlatform; audio implementations separately implement the audio device surface. The target OS is a fourth concern, reported by CNA::TargetPlatform.
SDL3 and SDL2
SDL3 remains the default platform and audio selection, but it is no longer inseparable from CNA. CNA_PLATFORM=SDL2 uses the real SDL 2.30 API, while Headless and POSIX Terminal avoid a graphical host. Audio-device code may independently be SDL3, SDL2 or Null; only SDL3 defines SOUND_ENABLED and supplies alpha.1's high-level SDL3_mixer playback/decoding engine.
- SDL3_mixer uses the
MIX_Mixermodel with track-based, per-mixer channels — unlike SDL2_mixer's global audio system. - SDL3_image is used for texture/image loading.
- All three libraries (
SDL,SDL_image,SDL_mixer) are vendored as Git submodules underthird_party/. No system SDL install is required by default; pass-DCNA_USE_SYSTEM_SDL=ONto use system packages instead.
The platform service contract covers:
- Windowing —
SDL_Windowcreation and management - Event loop — cross-platform event pumping
- Input —
SDL_Keyboard,SDL_Mouse,SDL_Gamepad,SDL_Touch - Audio initialisation — SDL3_mixer setup
- OpenGL context creation — used by the EasyGL renderer
- Vulkan surface creation — used by the VULKAN renderer
- Native surface creation — used by the WEBGPU renderer (Win32/Metal/X11/Wayland surfaces built directly from SDL3 window properties, no separate compatibility library needed)
- Android JNI Activity layer — Android platform integration
- Emscripten main loop — WebAssembly/browser event scheduling
A renderer asks for narrow services such as a native surface, GL context or Vulkan surface. Headless and Terminal applications can still choose the full SDL3 audio engine; graphical applications can select Null to remove it, with the resulting no-playback boundary documented explicitly. See Platform Support for valid combinations.
Required sibling repositories
CNA depends on two sibling C++ libraries that must be cloned adjacent to the CNA repository. They are not vendored inside CNA — they live at predictable relative paths and are discovered by CMake automatically.
../sharp-runtime/sharp-runtime
sharp-runtime is a C++ library that provides .NET-style type primitives for C++23. It allows CNA code to closely mirror XNA C# code structure without a managed runtime. Clone it at ../sharp-runtime/ relative to the CNA repo root.
Integer aliases:
bytecs— alias foruint8_tshortcs— alias forint16_tintcs— alias forint32_tlongcs— alias forint64_tushortcs,uintcs,ulongcs— unsigned variants
Other primitives:
Single— alias forfloatString—std::stringwrapperIDisposable— virtualDispose()interfaceIEquatable<T>—Equalsmethod interfaceIComparable<T>— comparison interfaceEventHandler<TArgs>— multicast delegate-style callbacksList<T>,Dictionary<K,V>— collection wrappers- I/O primitives
../easy-gl/ (OPENGLES3 renderer only)easy-gl
easy-gl wraps OpenGL ES 3.0/3.2 for CNA's OPENGLES3 renderer. Clone it at ../easy-gl/ relative to the CNA repo root. It is required only for the OPENGLES3 renderer — not needed for SDL_RENDERER, VULKAN, BGFX, WEBGPU, HEADLESS, SOFTWARE, DIRECTX11, or DIRECTX12.
- Abstracts GLSL shader compilation, linking, and uniform binding
- Manages VAOs, VBOs, FBOs, textures, and renderbuffers
- Provides the OpenGL context for both Linux desktop (OpenGL 3.0+ with ES emulation) and WebGL 2 (via Emscripten)
Which renderer should I choose?
Select your renderer at configure time with -DCNA_GRAPHICS_RENDERER=<NAME>. Use the table below to pick the best fit for your target platform and requirements.
| Renderer | Best for | Scope | Platform |
|---|---|---|---|
OPENGLES3 | Most Linux and Android games | 2D + 3D | Desktop/mobile GL (default on Linux) |
WEBGL2 | Browser games with 3D | 2D + 3D | Emscripten only (the web default) |
SDL_RENDERER | Pure 2D, maximum portability | 2D only | Anywhere SDL3 runs (default elsewhere) |
VULKAN | Low-level GPU control | 2D + 3D | Any Vulkan-capable target |
DIRECTX11 | Native Windows 3D | 2D + 3D | Windows only |
DIRECTX9 | XNA pixel authenticity | 2D + 3D | Windows only |
FNA3D | Running XNA's real compiled stock effects | 2D + 3D | Any; cannot compile custom shaders |
METAL | Native macOS | 2D + 3D | macOS only |
CANVAS | Browser 2D with no GPU context | 2D only | Emscripten only |
SKIA | 2D vector rasterisation | 2D only | Any; needs a pre-built Skia |
SOFTWARE | Deterministic pixels with no GPU | 2D + 3D | Any; never presents to a window |
HEADLESS | Fast CI of game logic | no output | Any; renders nothing by design |
This is a starting point, not the full list: the tag exposes 50 identities across 46 families. Judge a renderer by the scope, dependencies and platform it actually claims; see the renderer reference.
Design PrinciplesKey architectural decisions
Interface/Implementation separation
Public API headers (Microsoft/Xna/Framework/...) never include renderer headers. Renderer code never bleeds into game-facing APIs. This boundary is enforced by directory and include structure.
Compact default, opt-in runtime selection
CNA_GRAPHICS_RENDERER keeps single-renderer builds small. CNA_GRAPHICS_RENDERERS opts into a compatible registry and selects before the first device, with hard failure by default and explicit fallback when requested.
Vendored dependencies
SDL3, SDL3_image, and SDL3_mixer are vendored as Git submodules. No system SDL packages needed. bgfx is fetched via CMake FetchContent. This ensures reproducible builds across environments.
XNA namespace mirroring
C++ namespaces mirror XNA's hierarchy: Microsoft::Xna::Framework, Microsoft::Xna::Framework::Graphics, Microsoft::Xna::Framework::Input, etc. Reduces conceptual migration cost from XNA/MonoGame experience.
sharp-runtime support layer
The sharp-runtime library provides utility and runtime support primitives that CNA's internals depend on. It builds cleanly on all supported platforms with no external dependencies of its own.
Key C++23 features used in CNA
CNA targets C++23 and uses several modern standard library features to mirror XNA's C# semantics cleanly without a managed runtime.
std::optional<T>— replaces XNA nullable types (Nullable<T>) for missing or unset values.std::span<T>— provides buffer views inGetData()/SetData()without copying data.std::string_view— zero-copy string parameters wherever a string is read but not owned.std::unique_ptr<T>/std::shared_ptr<T>— RAII resource management, replacing C#usingblocks andIDisposablepatterns.- Ranges and concepts (where available) — type-safe template constraints replacing unconstrained templates.
[[nodiscard]]attributes — applied toLoad<T>(),Begin(), and similar factory/guard calls to catch accidentally discarded return values.- Structured bindings — used for returning multiple values from internal helpers without heavyweight output-parameter patterns.
if constexpr— compile-time template branching inPackedVectorimplementations and format-dispatch helpers.- Inline variables — for static constants defined directly in header-only types without requiring a separate
.cppdefinition.
Directory structure
cna/
├── modules/ ← every source file lives in a module
│ ├── core/
│ ├── math/
│ ├── graphics/ ← the XNA graphics API + renderer interfaces
│ │ ├── include/
│ │ │ ├── Microsoft/Xna/Framework/Graphics/ ← public XNA API
│ │ │ │ ├── GraphicsDevice.hpp
│ │ │ │ ├── SpriteBatch.hpp
│ │ │ │ └── Texture2D.hpp
│ │ │ └── CNA/Internal/Renderers/ ← renderer interfaces
│ │ │ ├── Common/IGraphicsRenderer.hpp
│ │ │ ├── ISpriteBatchRenderer.hpp
│ │ │ └── ITextureRenderer.hpp
│ │ ├── src/
│ │ ├── tests/
│ │ └── examples/
│ ├── graphics-ext/ ← CNAEXT engine layer (-DCNA_CNAEXT=ON)
│ ├── content/ audio/ input/ media/ net/ storage/
│ ├── devices/ devices-ext/ gamer-services/ runtime/
│ └── renderers/ ← 46 families, 50 public identities
│ ├── easygl/ ← OPENGLES2/3, OPENGL33, WEBGL1/2
│ ├── vulkan/ sdl-gpu/ bgfx/ webgpu/ metal/
│ ├── directx1/ … directx12/ ← the Windows Direct3D ladder
│ ├── canvas/ html-dom/ svg-dom/ ← Emscripten-only
│ ├── skia/ blend2d/ openvg/ ← 2D vector rasterizers
│ └── software/ portablegl/ headless/ stub/ ← no GPU required
│
├── third_party/ ← SDL, SDL_image, SDL_mixer, cgltf, enet, stb
├── tools/ ← gltf_to_cnj, xna-oracle, reference dumpers
├── cmake/ ← CMake helpers and renderer selection
├── examples/ tests/ docs/ scripts/
└── CMakeLists.txt