Building CNA

CNA 0.1.0-alpha.1  ·  CMake ≥ 3.20  ·  C++23  ·  C17 for the optional C API

Step 1 - Install the toolchain and system packages

CNA needs a C++23 compiler, CMake 3.20 or newer, and — on Linux and macOS — the FFmpeg development packages.

# Debian / Ubuntu
sudo apt install build-essential cmake git pkg-config \
     libavcodec-dev libavformat-dev libavutil-dev libswresample-dev

FFmpeg is a hard requirement on Linux and macOS, not an option. No CMake switch disables it: the configure step asks pkg-config for libavcodec, libavformat, libavutil and libswresample as REQUIRED, and fails outright if any is missing. Install the four -dev packages before you configure.

💡

On Windows, Emscripten and Android, FFmpeg is not used at all — and there the three video translation units are excluded from the build entirely. The headers still exist, so code that calls Video or VideoPlayer compiles and then fails to link. See Video Playback.

Step 2 - Clone CNA and its sibling repositories

CNA does not vendor everything. Some dependencies must exist as sibling directories next to the CNA checkout, because CMake pulls them in with add_subdirectory(../<repo>). They are separate git checkouts, not submodules, and CMake will not fetch them for you.

mkdir cna-workspace && cd cna-workspace

git clone https://github.com/openeggbert/cna.git
git clone https://github.com/openeggbert/sharp-runtime.git

# Needed by the default Linux renderer (OPENGLES3) and every other GL profile
git clone https://github.com/openeggbert/easy-gl.git
git clone https://github.com/openeggbert/meta-gl.git
Sibling repoRequired whenWhat it is
../sharp-runtime Every build A C++23 reimplementation of a .NET BCL subset. CNA is built on it: System::* collections, IO, text, threading and numerics all come from here. It evolves independently of the CNA tag, so its own README is the authority for mutable size and test counts. Without it, CNA does not configure at all.
../easy-gl (needs ../meta-gl) The five GL-profile renderers: OPENGLES2, OPENGLES3, OPENGL33, WEBGL1, WEBGL2 A toolkit-independent C++20 wrapper over OpenGL and OpenGL ES. It expects its own sibling checkout, meta-gl, next to it.
../free-direct (uses ../free-api) Only -DCNA_GRAPHICS_RENDERER=FREEDIRECT A reimplementation of a narrow, game-driven DirectX 3 (2D) subset on SDL3 — the renderer that used to be called DX3.

A default Linux build needs three sibling checkouts, not one. The Linux default renderer is OPENGLES3, which is an EasyGL profile — so a plain cmake -S . -B build on Linux requires sharp-runtime, easy-gl and meta-gl. If you would rather keep only sharp-runtime on disk, pick a renderer that does not use EasyGL, for example -DCNA_GRAPHICS_RENDERER=SDL_RENDERER.

Step 3 - Initialise the submodules

cd cna
git submodule update --init

This populates third_party/SDL, third_party/SDL_image, third_party/SDL_mixer and vendor/googletest. SDL is then built from source by the CMake build, so no system SDL packages are required.

💡

Leave off --recursive. The non-recursive form is the correct one here, and it is much faster: it fetches exactly the four submodules CNA builds.

Step 4 - Configure and build

cmake -S . -B build
cmake --build build

That is the whole default build. Which renderer it selects depends on the target: OPENGLES3 on Linux, WEBGL2 under Emscripten, and SDL_RENDERER everywhere else.

cmake --build build --target CNA no longer works. CNA is now an add_library(CNA INTERFACE) umbrella with no sources of its own, so it is not a buildable target. Build everything with cmake --build build, or name a real target such as CnaTests, cna_demo_2d or cna_house3d_demo. CNA's own README still repeats the old command in several places — it does not work there either.

Step 5 - Choose a renderer

A compact build compiles one of the 50 public renderer identities. The long and option forms below are equivalent and must not be mixed:

# Long form: name the renderer
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=VULKAN

# Option form: exactly one CNA_RENDERER_<NAME> may be ON
cmake -S . -B build -DCNA_RENDERER_VULKAN=ON

A few representative choices:

# Linux default - the OpenGL ES 3.0 profile of EasyGL (needs ../easy-gl and ../meta-gl)
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3

# 2D only, nothing beyond the vendored SDL3 required
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=SDL_RENDERER

# Vulkan, driven directly
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=VULKAN

# SDL3's own GPU API
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=SDL_GPU

# CPU rasterizer - no GPU, no display server; read pixels back with GetBackBufferData()
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=SOFTWARE

# No pixel output at all - game logic only
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=HEADLESS

# The free-direct sibling (formerly DX3); 2D only, and not Windows-gated
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=FREEDIRECT

These values are dead and stop the configure step with a FATAL_ERROR: EASYGL, D3D9, D3D11, D3D12, DX3 and ASCII. Use one of the five GL profiles instead of EASYGL, the DIRECTX9 / DIRECTX11 / DIRECTX12 spellings instead of D3D*, and FREEDIRECT instead of DX3. ASCII is not a renderer any more — it became the CNAEXT AsciiPostProcessEffect. And note that a renderer called DIRECTX3 does exist today, but it is real DirectX 3 and Windows-only, not the old DX3.

Opt into several renderers

CNA_GRAPHICS_RENDERERS links a compatible semicolon-separated set. The singular option remains the default and must occur in the set:

cmake -S . -B build-multi -G Ninja \
  -DCNA_GRAPHICS_RENDERER=HEADLESS \
  -DCNA_GRAPHICS_RENDERERS="HEADLESS;SOFTWARE;STUB"

An empty plural option is ordinary single-renderer mode. Multi-renderer builds increase dependency and binary size. PortableGL + real OpenGL, GDI + Software, Glide + any other renderer, and cross-platform partitions are rejected at configure time. See runtime renderer selection.

Choose platform and audio independently

# Defaults shown explicitly
cmake -S . -B build \
  -DCNA_PLATFORM=SDL3 \
  -DCNA_AUDIO_PLATFORM=SDL3 \
  -DCNA_GRAPHICS_RENDERER=OPENGLES3

# Deterministic display-free build
cmake -S . -B build-headless \
  -DCNA_PLATFORM=HEADLESS \
  -DCNA_AUDIO_PLATFORM=NULL \
  -DCNA_GRAPHICS_RENDERER=HEADLESS

CNA_PLATFORM accepts SDL3, SDL2, HEADLESS and POSIX-only TERMINAL. CNA_AUDIO_PLATFORM accepts SDL3, SDL2 and NULL. Reserved values fail rather than falling back. Only the SDL3 audio value defines SOUND_ENABLED and links the tag's SDL3_mixer-based XNA playback/decoding engine; SDL2 and Null select low-level device code but are not feature-equivalent game-audio backends. If both axes select SDL2, choose a renderer that does not directly link SDL3; SDL_RENDERER, SDL_GPU, FNA3D and FREEDIRECT are refused in that combination.

Several renderers are hard-gated and refuse to configure off their platform: 14 are Windows-only (the DIRECTX1DIRECTX12 ladder plus DIRECT2D, GLIDE and GDI, with GLIDE additionally requiring a 32-bit i686 toolchain), METAL is macOS-only, WEBGL1 / WEBGL2 / CANVAS / HTML_DOM / SVG_DOM / PIXIJS are Emscripten-only, and OPENGLES2 / OPENGLES3 / OPENGL33 / MAGNUM / WICKED cannot be selected under Emscripten. Graphics Renderers lists all 50 identities with their scope, platform gate and dependency.

CMake presets

Five presets ship with the repository:

PresetWhat it configures
webAn Emscripten release build on the WEBGL2 renderer, examples on, tests off.
testsA Ninja debug build on OPENGLES3, tests and examples on.
devices-asanOPENGLES3 debug build with CNA_DEVICES=ON under AddressSanitizer.
devices-tsanThe same, under ThreadSanitizer.
devices-ubsanThe same, under UndefinedBehaviorSanitizer.
cmake --preset tests
cmake --build --preset tests

Each preset chooses its own build directory, so drive the build with cmake --build --preset <name> rather than guessing the path.

Cross-compiling

Windows, from Linux

sudo apt install mingw-w64

cmake -S . -B build-win \
      -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
      -DCNA_GRAPHICS_RENDERER=DIRECTX9
cmake --build build-win

The 14 Windows-gated renderers require CMAKE_SYSTEM_NAME=Windows, which the MinGW-w64 toolchain file supplies. The repository includes manual Wine + DXVK and Wine + vkd3d-proton execution paths, but alpha.1 has no automatic MinGW/Wine workflow. GLIDE is the exception that needs the 32-bit toolchain file, cmake/toolchains/mingw-w64-i686.cmake, because Glide's application ABI is 32-bit.

Web (Emscripten)

# 1. Install and activate the Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
cd ../cna

# 2. Configure and build through the bundled preset (WEBGL2)
cmake --preset web
cmake --build --preset web

# Or select one of the DOM renderers explicitly
emcmake cmake -S . -B build-htmldom -DCNA_GRAPHICS_RENDERER=HTML_DOM
cmake --build build-htmldom

Output is .html, .js and .wasm; serve it from a local web server, because browsers block direct file:// access. Three web caveats matter before you ship anything: there is no save persistence, no video, and your Game object must be heap-allocated. Platforms explains each.

Android

cmake -S . -B build-android \
      -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
      -DANDROID_ABI=arm64-v8a \
      -DANDROID_PLATFORM=android-24 \
      -DCNA_BUILD_TESTS=OFF \
      -DCNA_BUILD_EXAMPLES=OFF \
      -DCNA_GRAPHICS_RENDERER=SDL_RENDERER
cmake --build build-android

The Android code paths and NDK sensor implementations are real, but there is no CMake preset and no CI job for Android. The tag also contains only a packaged Devices demo, not a reusable general-game Android template. Treat this command as the framework cross-build shape and validate final APK packaging separately.

CMake options reference

OptionValuesDefaultDescription
CNA_GRAPHICS_RENDERERAny one of the 50 renderer identitiesOPENGLES3 on Linux, WEBGL2 under Emscripten, SDL_RENDERER elsewhereSelects the only renderer in a single-renderer build, or the default in a multi-renderer build.
CNA_GRAPHICS_RENDERERSSemicolon-separated compatible identities(empty)Opt-in multi-renderer build. The singular default must be a member.
CNA_RENDERER_<NAME>ON, OFFOFFThe equivalent per-renderer form. Exactly one may be ON; two or more is a configure error.
CNA_PLATFORMSDL3, SDL2, HEADLESS, TERMINALSDL3Selects window, event, input, timing and host services. Terminal is POSIX-only.
CNA_AUDIO_PLATFORMSDL3, SDL2, NULLSDL3Selects low-level device integration independently. Only SDL3 enables SOUND_ENABLED and the high-level XNA mixer/playback engine in alpha.1.
CNA_BUILD_C_APION, OFFOFFRequests the experimental native C17 API. In alpha.1 the final target is compile-blocked by a 49-entry C renderer map against 50 canonical identities; disabling CNA_ENABLE_NET instead fails earlier on an unconditional GamerServices include.
CNA_BUILD_TESTSON, OFFONBuild the CnaTests binary and register the CTest entries.
CNA_BUILD_EXAMPLESON, OFFONBuild the demo programs and the example programs under the modules' examples/ directories.
CNA_CNAEXTON, OFFOFFThe CNAEXT engine layer, beyond XNA 4.0. Gates AsciiPostProcessEffect, CRTEffect, DepthEffect, PbrMaterial and CNA::Graphics. Off by default.
CNA_DEVICESON, OFFOFFThe CNAEXT device layer (battery, camera, clipboard, sensors and so on). Off by default.
CNA_ENABLE_NETON, OFFONBuild the networking layer (GamerServices and Net, over ENet).
CNA_USE_CCACHEON, OFFONRoute compilation through ccache when it is available.
CNA_USE_SYSTEM_SDLON, OFFOFFLink against system SDL3 packages instead of the vendored submodules.
CNA_EASYGL_COMPILED_EFFECTSON, OFFOFFAdd compiled Effect Framework bytecode support to EasyGL identities.
CNA_SDL_GPU_COMPILED_EFFECTSON, OFFOFFAdd compiled Effect Framework bytecode support to SDL_GPU.
CNA_VULKAN_COMPILED_EFFECTSON, OFFOFFAdd compiled Effect Framework bytecode support to Vulkan.
CNA_SANITIZEcomma-separated sanitizer list(empty)Enable compiler sanitizers, for example address,undefined.
CNA_TEST_DISPLAYX display:0The DISPLAY value used by window-creating CTest entries.
CNA_SHARP_RUNTIME_ROOTpath../sharp-runtimeWhere to find the sharp-runtime checkout, when it is not the sibling directory.
CNA_MACOS_DEPLOYMENT_TARGETversion13.3macOS deployment floor. It may be raised but not lowered because CNA and sharp-runtime require Apple's floating-point std::to_chars.
CNA_IOS_DEPLOYMENT_TARGETversion16.3iOS deployment floor, with the same hard lower-bound rule.
CNA_IOS_SIMULATORON, OFFOFFWith cmake/toolchains/ios.cmake, selects the simulator SDK instead of a device SDK.
CNA_APPLE_BUNDLE_IDENTIFIER_PREFIXreverse-DNS stringcom.openeggbert.cnaPrefix for generated Apple bundle identifiers.
CNA_APPLE_BUNDLE_VERSIONnumeric version0.1.0Apple bundle version. The prerelease suffix is intentionally omitted because Apple requires numeric components.
CNA_APPLE_DEVELOPMENT_TEAMteam ID(empty)Codesigning team for iOS products; empty disables signing for simulator/CI use.
CNA_APPLE_BUNDLE_MACOS_EXECUTABLESON, OFFOFFEmit macOS executables as app bundles instead of plain command-line binaries.
CNA_BUILD_APPLE_SMOKE_APPON, OFFON for iOS; OFF otherwiseBuild the minimal Apple final-link/simulator smoke application.
CNA_APPLE_ALLOW_UNVALIDATED_RENDERERON, OFFOFFiOS experiment escape hatch for identities outside the sole validated SDL_RENDERER allow-list; unsupported configurations are expected to fail.
CNA_SOKOL_APIGLCORE, GLES3, D3D11, METAL, WGPUGLCOREWhich native API the SOKOL renderer dispatches onto. Values other than the default are wired but unverified.
CNA_SKIA_ROOT / CNA_SKIA_BUILD_DIRpaths(unset)Required by -DCNA_GRAPHICS_RENDERER=SKIA: Skia must be checked out and built separately, then pointed at.
CNA_WEBGPU_AUTO_DOWNLOADON, OFFONDownload the pinned wgpu-native release for the WEBGPU renderer.
CNA_WEBGPU_ROOTpath(unset)Root of a manually extracted wgpu-native release; set it together with -DCNA_WEBGPU_AUTO_DOWNLOAD=OFF for an offline build.
CNA_STRICT_XNA_APIcompile definition(not set)Purity mode. Compiling with this definition turns any use of a CNA extension into a compile error, so a codebase can prove it stays inside the XNA 4.0 surface.

Running the tests

cmake -S . -B build -DCNA_BUILD_TESTS=ON
cmake --build build --target CnaTests
ctest --test-dir build --output-on-failure

The tag contains 568 C++ test source files and 8,263 statically declared GoogleTest macros. Those are source inventory figures, not a promise that one executable instantiates or runs that many cases. Conditional compilation, parameterized tests, renderer families and build options change both GoogleTest and CTest inventories. Use ctest -N in the configured build to see that build's registrations, then run the labels relevant to its selected renderer/platform.

💡

Test labels follow the current renderer names. ctest -L D3D9 and ctest -R D3D11 match zero tests; the labels are DIRECTX9 and DIRECTX11.

CI runs focused subsets across Linux, macOS and a headless browser, but alpha.1 does not have an effective full-suite gate: general-tests-ci.yml still configures the removed EASYGL identity and fails before building. The GPU pixel/oracle matrix is not continuously gated either. See Platforms for exactly which workflows fire and what they cover.

Running the demos

# 2D demo
cmake --build build --target cna_demo_2d
./build/cna_demo_2d

# House 3D demo
cmake --build build --target cna_house3d_demo
./build/cna_house3d_demo

Using CNA from your own project

The C++ framework has no general install(), export() or CPack rules. C++ consumers bring the source tree in with add_subdirectory(), alongside the same sibling checkouts CNA itself needs. The experimental C layer declares separate CNACApi install/package rules and CNA::CApi/CNA::CApiStatic targets, but alpha.1 cannot compile the final implementation because its C renderer table omits NanoVG. Those declarations are not a usable release artifact.

Troubleshooting

sharp-runtime not found

CNA requires sharp-runtime as a sibling directory:

cna-workspace/
├── cna/
└── sharp-runtime/

Clone it next to CNA and re-run the configure step, or point -DCNA_SHARP_RUNTIME_ROOT= at your checkout.

easy-gl or meta-gl not found

The five GL-profile renderers — including OPENGLES3, the Linux default — are built on the easy-gl sibling, which itself expects meta-gl next to it. Clone both alongside CNA, or select a renderer that does not use EasyGL, such as -DCNA_GRAPHICS_RENDERER=SDL_RENDERER.

Configure fails on libavcodec / libavformat / libavutil / libswresample

Those four FFmpeg development packages are mandatory on Linux and macOS, and no option turns them off. Install libavcodec-dev, libavformat-dev, libavutil-dev and libswresample-dev (or your distribution's equivalents) and configure again.

"Unknown graphics renderer"

The value you passed is not one of the 50 public identities. The usual cause is an old name: EASYGL, D3D9, D3D11, D3D12, DX3 or ASCII. See the translation callout in Step 5 above, or the full list on Graphics Renderers.

"renderer only builds when targeting Windows"

You selected one of the 14 Windows-gated renderers on a non-Windows target. Either build natively on Windows, or use the repository's manual cross-compile path with -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake and run the result under Wine + DXVK / vkd3d-proton. This is not an automatic alpha.1 CI lane. GLIDE additionally needs the i686 toolchain file.

CANVAS, HTML_DOM, SVG_DOM, PIXIJS or WEBGL1/WEBGL2 refuse to configure

All six need browser APIs and are Emscripten-only. Configure through emcmake cmake or the web preset; they cannot be selected on a native target.

Vulkan: no Vulkan device found

The VULKAN renderer needs Vulkan headers, a loader and a driver ICD. On Debian/Ubuntu install libvulkan-dev and check with vulkaninfo. Virtual machines without GPU passthrough usually have no usable Vulkan device.

OPENGLES3: OpenGL context creation failed

The GL profiles need a working GL/GLES driver and a display server. On a headless machine, force a software GL implementation (for example LIBGL_ALWAYS_SOFTWARE=1 with Mesa's llvmpipe), or switch to SOFTWARE, PORTABLEGL, HEADLESS or STUB — none of which need a GPU at all.

SKIA: missing CNA_SKIA_ROOT

The SKIA renderer deliberately does not fetch Skia: upstream Skia builds with GN and carries a large revision-locked dependency graph, so it must be checked out and built separately, then pointed at with -DCNA_SKIA_ROOT= and -DCNA_SKIA_BUILD_DIR=.

--target CNA fails with "No rule to make target"

Expected. CNA is an INTERFACE library with no sources. Drop the --target argument, or name CnaTests or a demo target instead.