Getting started

ⓘ

Contribute against the development branches, not the default branch. The GitHub default branch of libcna/cna is develop, which is the v0.1.0-alpha.1 tag commit. Active development happens on next and topic branches cut from it; this page describes snapshot c1c316b9 (9 October 2026) of that branch, 3,687 commits after alpha.1. CNA's product version string is still 0.1.0-alpha.1.

Step 1 — Clone CNA and initialise submodules

git clone -b apple/m4-stabilization https://github.com/libcna/cna.git
cd cna
git submodule update --init

This populates third_party/SDL, third_party/SDL_image, third_party/SDL_mixer, third_party/draco and vendor/googletest (needed while tests are on). SDL3 is built from source by CMake — no system SDL packages are required. The non-recursive form is correct: recursion only pulls codec submodules CNA does not use.

Step 2 — Clone sibling repositories

CNA always requires sharp-runtime as a sibling directory next to cna/, and it must be sharp-runtime's apple/m4-stabilization branch — its main and develop lack components CNA now requests. Two more siblings are needed only by the GL renderers: easy-gl (with meta-gl) for the three GL-profile identities, which includes the Linux default OPENGLES3.

parent/
├── cna/
├── sharp-runtime/   # always required, branch apple/m4-stabilization
├── easy-gl/         # OPENGLES3, OPENGL33, WEBGL2
└── meta-gl/         # needed by easy-gl

Clone them at the same level:

git clone -b apple/m4-stabilization https://github.com/libcna/sharp-runtime.git
git clone https://github.com/libcna/easy-gl.git
git clone https://github.com/libcna/meta-gl.git

Step 3 — Build with EasyGL

cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
cmake --build build --target CnaTests

CNA is an interface umbrella, not a standalone build target; build CnaTests, a focused module target such as CnaMathTests, an example, or your consumer target. CNA accepts 14 renderer identities across 12 implementation families. A normal configure selects one through CNA_GRAPHICS_RENDERER; an opt-in CNA_GRAPHICS_RENDERERS list can link several compatible families for pre-device runtime selection. A name outside the 14 is a configure-time error. See the Building documentation for the full inventory and platform gates.

Step 4 — Run the tests

./build/CnaTests                        # from the repository root: the tests preset's authoritative way to run the aggregate suite
ctest --test-dir build -N               # list what this configuration registered (an unfiltered ctest needs a full build first)

The snapshot's source tree has 813 C++ test files and 11,380 statically discoverable GoogleTest-family definitions (781 files contain at least one; alpha.1 had 568 and 8,263). A further 781 standalone examples/**/*_test.cpp pixel programs are not in those figures. What reaches CnaTests and CTest depends on renderer set, platform, audio implementation, host and options. Run the configuration you changed and include its configure command, ctest -N inventory and failing output in a report. On a headless box, launch CTest under a virtual display (xvfb-run -a ctest ...).

Step 5 — Run one module, or run tests safely

Each module has a focused test executable built from the same objects as CnaTests: CnaMathTests, CnaCoreTests, CnaAudioTests, CnaContentTests, CnaContentPipelineTests, CnaGraphicsTests, CnaGraphicsExtTests, CnaStorageTests and 14 more (22 in all). They are iteration targets, not extra CTest entries:

cmake --build build --target CnaMathTests
./build/CnaMathTests --gtest_filter='Vector2Test.*'      # run from the repository root

Window-creating GPU tests no longer default to your live desktop. CNA_TEST_DISPLAY is empty by default, so those tests inherit the DISPLAY of whoever launches them; naming :0 needs -DCNA_TEST_ALLOW_LIVE_DISPLAY=ON. To run them on a private headless compositor with the real GPU, use tools/platform/run_gpu_tests_private.sh build -L Parity. A large binary can exhaust memory in one process (the whole CnaTests could not run on WEBGPU that way), so tools/tests/run_gtest_bounded.sh shards a GoogleTest binary, reports a signal-killed shard as KILLED and fails instead of reporting a partial pass. Tutorial 160 walks through all of it, and Tutorial 161 shows how to diff a renderer against the XNA oracle corpus.

ⓘ

Single-renderer build directories remain the easiest way to isolate a matrix. Multi-renderer builds are valuable for registry, selection and fallback work, but they do not remove renderer-specific dependencies or platform gates. The repository has 18 workflow files (24 jobs; 16 run automatically on pushes and pull requests, the Windows lanes are manual); local coverage of the exact family and driver you changed remains valuable, because no workflow names OPENGL33, SDL_GPU, FNA3D or DIRECTX9, and WEBGPU is only built inside the browser bundle.

Repository structure

ⓘ

Working on CNA itself? The Development area goes further than this overview: a source-ownership map (which part of CNA owns a behaviour), the Maintainer Handbook with task recipes, the Human Takeover path, and internals pages for the runtime, renderers, platforms, content and the C API.

PathContents
modules/*/include/ Module-owned public C++ headers, including the XNA 4.0-shaped API and CNA namespaces. Non-XNA declarations in the XNA surface require CNAEXT.
modules/*/src/ Module-owned implementations; renderer families live under modules/renderers/ (12 families implementing 14 public identities).
modules/*/tests/ Module-owned GoogleTest sources. This snapshot has 813 C++ test files and 11,380 statically discoverable definitions; a build compiles only its applicable subset, and each module also has a focused executable.
modules/*/examples/ Example programs and the renderer pixel-readback tests registered with CTest (781 standalone *_test.cpp programs), plus the shared cross-renderer parity fixtures under modules/graphics/examples/parity/.
third_party/, vendor/ Vendored submodules and libraries: SDL3, SDL3_image, SDL3_mixer, Draco and GoogleTest as submodules, plus in-tree ENet, cgltf, stb and dr_libs. Do not modify these directly.
tools/, scripts/, spikes/ Verification tooling: the XNA oracle corpus (tools/xna-oracle/), the FNA reference harness, the Content Pipeline oracle, coverage generators, the bounded test runner (tools/tests/), the private-compositor runner (tools/platform/), the corpus and parity scripts, and one-question probes run against real XNA.
docs/ CNA-internal documentation: per-renderer pages, renderer-registry.md, removed-renderers.md, and the generated coverage reports (xna-4-runtime-member-coverage.md, xna-content-pipeline-parity-report.md, c-api/COVERAGE.md). Older status documents such as coverage.md and xna-4-api-coverage.md are historical snapshots.
CHECKLIST.md Per-file porting checklist tracking which XNA types have been ported. Also explains the CNAEXT marker convention.
plans/, known_bugs.md, TODO.md Per-area implementation plans with task ids (the graphics task plan that used to be GRAPHICS_TASKS.md is now plans/plan_graphics.md, with per-renderer plans beside it), CNA's own list of known bugs, and open to-dos.

The CNAEXT marker

📝

Rule: When porting XNA APIs, stay faithful to the public XNA 4.0 surface. Use the CNAEXT marker only for C++ glue code that has no counterpart in the managed XNA/FNA API.

Methods and types annotated with the CNAEXT macro (from CNA/CNAHelper.hpp) are not part of the XNA 4.0 public API. They exist purely to support C++ idioms and integration patterns. In a normal build the macro expands to nothing; under the CNA_STRICT_XNA_API purity mode it expands to [[deprecated]], so a strict compile with -Werror=deprecated-declarations fails if code calls an extension. That strict check is run for the Microsoft::Devices and Sensors surface today. Examples:

  • Iterator support — begin() / end() on collections to enable range-for loops
  • GetTypeName() — runtime type name helper for debugging
  • RAII helpers and move constructors that have no equivalent in managed C#
  • Engine-layer additions, conventionally named with an EXT suffix (for example GetGraphicsRendererType() or ShowAchievementsEXT())

The authoritative reference for the XNA 4.0 public API surface is FNA (C#). When in doubt whether a method belongs to the XNA API, check the corresponding FNA class. If it is present there without modification, it belongs in CNA without the CNAEXT marker. If it is a C++-only addition, mark it CNAEXT.

What needs work

Below are the most impactful contribution opportunities, grouped by area. Any of these would be a meaningful addition to the project. Each was checked against the snapshot's source or CNA's own records; items taken from CNA's notes say so.

Open

Broaden compiled-effect portability

This snapshot implements XNA/FNA D3D9 Effect Framework bytecode on nine renderer families: FNA3D always, and eight opt-in build options (EasyGL, Vulkan, WebGPU, Software, DIRECTX9, DIRECTX11, METAL, SDL_GPU), so a default configure reports CompiledEffects as unsupported on 13 of 14 identities (it is true on FNA3D only). The open work is to make the qualified paths default-ready (Metal’s, for example, is opt-in and not yet primary-production), add real sample runtime evidence, or build an explicit conversion strategy for different inputs such as DXBC and MGFX. HLSL .fx source is compiled only at build time, through an external legacy fxc. Do not treat those formats as interchangeable.

Open

Cube faces in multiple-render-target sets

Binding a cube-map face inside a multi-target SetRenderTargets call throws "not implemented by this CNA renderer" on DIRECTX9 and SDL_GPU. EasyGL, DIRECTX11 and METAL attach cube faces to a target set, and Vulkan has a dedicated binding test, so there are working siblings to follow. Well-scoped, with a clear specification and a pixel test to write.

Open

Register more renderers for the parity fixtures

32 renderer-neutral parity fixtures exist, each stating its own expected result. Only EasyGL, WebGPU, SDL_GPU and DIRECTX11 register them (one cna_register_parity_fixtures() call in each renderer's examples CMake). Wiring in another 3D renderer and triaging what its fixtures reveal turns a per-renderer "looks right" claim into a comparable one. The oracle is the fixture's own assertions, not real XNA.

Open

Truthful SupportsCapability()

The base default still returns true for the original capabilities, while multi-stream input, compiled effects and float render targets are false by default and six answers are derived at the device level. DIRECTX9 is the one renderer that never overrides it; the others use switches or partial default: true arms (WebGPU, Headless, Software, EasyGL, FNA3D). Where an entry still fails open, a renderer can advertise a feature whose implementation throws or silently does nothing. Renderers that report honestly — deterministically refusing what they cannot do — are the model to follow.

Open

Stale entries in known_bugs.md

CNA's own known_bugs.md still lists two defects as open that the source at this snapshot has already fixed. FNA3D resource renderers no longer keep a raw device pointer: each holds a shared device state that the renderer clears when it is destroyed, and the Fna3d_Device_Lifetime test destroys the renderer while its resources are alive, the order that used to free through a dangling pointer. A repeated SpriteBatch Begin()/End() cycle within one frame on VULKAN is pinned by the Vulkan_SpriteBatch_MultiBeginEnd test. Both were read at the pinned commit and not executed. Removing the two entries, with the fixing tasks named, is a small first contribution.

Open

Re-measure the XNA oracle

The zero-tolerance DIRECTX9 result was recorded through Wine and DXVK on Linux; running it on native Windows is still an open, human-only task in CNA's own plan. The EasyGL and FNA3D whole-corpus counts (10 of 39) date from 2026-08-11, before later EasyGL fixes, and nothing committed re-measures them; the Software measurement (18 of 39 byte-exact) dates from 2026-09-11. Re-running the corpus scripts on a reference machine and recording dated, per-renderer results is valuable. See Verification & Known Issues and Tutorial 161.

Needs CI

Broader CI coverage

This snapshot has 18 workflow files (24 jobs) covering Linux, Apple, Emscripten, platform abstraction (including SDL3 on X11 and Wayland under a private Xvfb and a private headless Weston), runtime multi-renderer selection and five C API header gates, while the Direct3D Windows lanes remain manual. Gaps a contributor could close: no workflow runs the XNA oracle corpus or the FNA harness, none builds the C API library (whose release gate reports "Not ready" because 631 planned public C++ declarations do not yet have C mappings), none covers Android, and the general Linux job does not install Pillow, which the oracle line test imports. Fixing or extending a reproducible gap is useful work.

Needs a Windows toolchain fix

CnaTests on Windows

Windows-native lanes exist but the Direct3D workflows are manual, and there is no DIRECTX9 Windows lane. A useful contribution is to make a specific renderer lane automatic and reproducible, with its exact MSVC configuration and scoped test inventory documented.

Open

macOS and iOS evidence

CNA’s Apple campaign ran the full test suites of seven renderer trees on one physical Mac mini M4 and settled METAL as supported but not primary-production; iOS has SDL_RENDERER and METAL builds for device and Simulator, with Simulator runs only. The runs had a locked console, so what is still missing is evidence a person or other hardware has to supply: on-screen presentation and PresentInterval pacing on an unlocked Mac, a physical iPhone or iPad, an Intel Mac, a Metal soak run, and a green hosted run of the Apple and Metal workflows after the campaign.

Open

XNB reader gaps

The runtime XNB loader has 61 built-in readers (60 in a build without native 128-bit integers), and VideoReader, typed external references and a real compiled-effect EffectReader are present. Reflection-based discovery is not; custom readers and closed generic types need explicit registration through the available creator/reader APIs, and Intel E8 LZX preprocessing is still unfinished, as in FNA. CNA also has a build-time content pipeline (cna-content) that can write .xnb and .cnb. See XNB Content Pipeline.

Open

ContentManager lifecycle

ContentManager::Unload() clears its asset map without any explicit disposal step, so cached assets are released only by their normal ownership rules. (ResourceContentManager is no longer a stub; its OpenStream is implemented.) The fix is small and self-contained with obvious correctness criteria.

Always welcome

Test coverage

Coverage remains uneven even with 11,380 statically discoverable definitions. Storage has 14 GoogleTest-family definitions (four cover StorageContainer path containment, none a save/load round trip), while Media has 304; none of those source counts proves runtime success on a target. New focused tests and configuration-specific execution evidence are welcome.

Adding a renderer

⚠

Renderer count is not a goal. CNA intentionally maintains a curated renderer set of 14 public identities. A new renderer is added only when it provides meaningful platform coverage, compatibility value, architectural value, or a capability the existing set does not reasonably cover. Breadth for its own sake has a real cost in tests, CI, documentation and maintenance. Please open an issue or a Discord discussion before writing one.

CNA's own candidate list is explicitly research, not a roadmap: nothing in it is planned or authorised, and each candidate would need a fresh explicit owner instruction, its own plan file and its own acceptance criteria. If a proposal is accepted, the mechanical part is that all of these must agree, and a script holds them to each other:

  • the GraphicsRendererType enumerator and its canonical name, the CMake selector and compile definition, the selected target and factory branch, and the platform and dependency gate;
  • the runtime registry and the C ABI value table — C ABI values are stable and sparse, gaps in the numbering stay reserved and are never reused, and the next new identity takes value 52;
  • scripts/check_renderer_identities.py (registered as the CTest RendererIdentityRegistry) fails when the registries disagree, and a name outside the 14 public identities is refused at configure time by name — never aliased, never a silent fallback.

A renderer also needs a declared maturity and category, a capability profile that answers honestly, its tests registered through cna_register_renderer_test, and evidence at its real scope: at minimum the shared parity fixtures (cna_register_parity_fixtures()) and pixel-readback tests, and an honest statement of what the oracle corpus does and does not say about it. See Verification & Known Issues for how evidence is labelled and the Renderers page for the current set.

Code style

  • C++23 throughout. CNA needs CMake 3.20 or newer and a C++23 compiler with <format> (libstdc++ 13, a current libc++, or MSVC 2022). CMake does not check compiler versions; CI exercises GCC 14, AppleClang, MSVC and MinGW-w64, so older compilers are untested. Use modern C++ features freely — structured bindings, ranges, concepts — but keep readability first.
  • API shape is fixed. The Microsoft::Xna::Framework public API mirrors XNA 4.0 exactly. Do not rename, remove, or add overloads to public methods without a compelling reason backed by FNA precedent.
  • Properties become getter/setter methods. C# public int Width { get; set; } becomes intcs getWidthProperty() const and void setWidthProperty(intcs value) in C++.
  • Use sharp-runtime types. Use intcs, bytecs, Single, String, events, and interfaces from sharp-runtime to match C# semantics. Avoid raw int or float at the public API boundary.
  • No extra runtime dependencies. CNA's dependency set is deliberately small and mostly vendored or submoduled — SDL3 (plus SDL_image and SDL_mixer), sharp-runtime, and the GL renderers’ sibling easy-gl (with meta-gl), alongside in-tree enet, cgltf, stb, dr_libs and the fetched wgpu-native and FNA3D. Do not introduce new third-party libraries without prior discussion.
  • Mark non-XNA additions as CNAEXT. Any method or type added purely for C++ convenience must carry the CNAEXT macro so that API coverage tooling and the strict purity check stay accurate.
  • Tests state their oracle. A test that compares with real XNA, FNA, a golden image or only its own assertions says which in its header, uses tolerance 0 unless a written reason says otherwise, and never widens a tolerance to turn a comparison green.

License and attribution

CNA is licensed under the Microsoft Public License (Ms-PL). All contributions must be compatible with the Ms-PL. By submitting a pull request you agree that your contribution will be distributed under the same license.

📜

Deriving from FNA (C#): FNA is the authoritative XNA 4.0 reference and is also licensed under the Ms-PL. If you port logic from FNA's C# source into CNA, you must preserve the Ms-PL attribution. See NOTICE.md and THIRD_PARTY_NOTICES.md for the existing attribution text and the pattern to follow.

View CNA on GitHub