Getting Started with CNA

CNA - C++ XNA 4.0 reimplementation  ·  near-complete XNA 4.0 type coverage  ·  snapshot c1c316b9

⚠

CNA 0.1.0-alpha.1 is still the version string CNA reports, but this guide documents a newer commit than the v0.1.0-alpha.1 tag: snapshot c1c316b9 (9 October 2026) on the apple/m4-stabilization branch. That matters for the very first command: git clone https://github.com/libcna/cna.git gives you the default branch (develop, the alpha.1 commit), so clone with -b apple/m4-stabilization, and use the apple/m4-stabilization branch of sharp-runtime too (below). Start with a single renderer and the default SDL3 platform/audio implementations; multi-renderer selection, the headless and terminal platforms, SDL-free (windowless) builds, non-default audio-device selection and compiled Effect Framework bytecode are opt-in topics covered by their dedicated guides. Only CNA_AUDIO_PLATFORM=SDL3 and ALSA enable the real XNA mixer/playback path. The experimental C API source layer is not a getting-started path. APIs may change before 1.0, so pin a commit when following these commands.

➤

Writing in C#? This guide builds CNA’s C++ framework. For C#, pick the route that matches where you start: a new C# game starts from the installable dotnet new cna-game template (Tutorial 171); an existing XNA, FNA or MonoGame game keeps its source and gets a thin SDK project over CNA.XnaCompat (Migrating C# games to CNA.NET); and cna-dotnet-samples holds the original Microsoft samples as compatibility references. All three go through CNA.NET.

What you are getting

CNA is a C++ framework that mirrors the XNA 4.0 programming model. You write game code against Microsoft::Xna::Framework. The default build uses SDL3 for host and audio services, but CNA_PLATFORM, CNA_AUDIO_PLATFORM and graphics-renderer selection are independent, and a windowless build can contain no SDL at all (Building without SDL).

If you have XNA or MonoGame experience, the patterns will feel familiar. The main difference is C++ instead of C#.

Prerequisites

Linux

  • CMake 3.20 or newer
  • A C++23-capable compiler. CMake does not check the version, but the code uses <format>, and CI builds with GCC 14 (g++-14 on Ubuntu 24.04); do not assume GCC 12
  • ../sharp-runtime directory (sibling to the CNA repo), on its apple/m4-stabilization branch - no external dependencies
  • ../easy-gl and ../meta-gl directories - needed for all three GL-profile renderers (OPENGLES3, OPENGL33, WEBGL2), including the Linux default OPENGLES3; easy-gl (default branch develop) needs meta-gl (apple/m4-stabilization branch) next to it
  • SDL3, SDL3_image, and SDL3_mixer are built from vendored submodules - no system SDL packages required. The X11/GL/Vulkan/ALSA/D-Bus development packages should be installed before the first configure so the vendored SDL gets real window and audio drivers (see Building for the list)
  • FFmpeg development packages are optional: without them Video/VideoPlayer exist but throw NotSupportedException

Windows

  • CMake 3.20 or newer
  • MSVC (the manual CI workflows use it on windows-latest) or MinGW-w64 (cross-compiling from Linux); clang-cl is not exercised by any workflow
  • ..\sharp-runtime directory (sibling to the CNA repo, apple/m4-stabilization branch)
  • SDL3 built from vendored submodules - no pre-built binaries or CMAKE_PREFIX_PATH needed
  • Windows-only renderers (2): DIRECTX9, DIRECTX11. FFmpeg video is never built for Windows

macOS

  • CMake 3.20 or newer and a current Xcode / AppleClang (CI uses macos-26); deployment floor macOS 13.3
  • ../sharp-runtime sibling (apple/m4-stabilization branch); SDL3 from the vendored submodules; SDL_RENDERER is the default renderer (METAL is the native Apple renderer, on macOS and iOS)
  • FFmpeg (for example from Homebrew) is optional

System requirements

ComponentRequirement
CompilerA C++23 compiler. CI's workflows use GCC 14, AppleClang (macos-26), MSVC (windows-latest), mingw-w64 GCC and Emscripten 6.0.3; the CMake files enforce no compiler version
Build systemCMake 3.20+ (3.27+ for the default libcna.so layout on native Linux)
GitAny recent version (for the five submodules)
OpenGL (OPENGLES3 renderer)OpenGL ES 3.0 or OpenGL 3.0+ desktop; GPU driver installed (or Mesa's software driver)
Vulkan (VULKAN renderer)Vulkan-capable GPU or driver; vulkan-headers / libvulkan-dev
Disk spaceSeveral GB for a focused build (cna_demo_2d or one focused test target) including the configure-time SDL build. A default build of everything (tests and examples) produces hundreds of executables; CNA's own note measured about 104 MB per statically linked Debug executable before the shared-library layout became the Linux default
RAMNo requirement is published or measured; if a compile is killed for lack of memory, rebuild with fewer parallel jobs (--parallel 2)
Sibling repossharp-runtime (apple/m4-stabilization branch) and (for the GL renderers) easy-gl (develop) plus meta-gl (apple/m4-stabilization) cloned alongside cna/

Clone & initialise

mkdir cna-workspace && cd cna-workspace

git clone -b apple/m4-stabilization https://github.com/libcna/cna.git
git clone -b apple/m4-stabilization https://github.com/libcna/sharp-runtime.git
git clone https://github.com/libcna/easy-gl.git
git clone -b apple/m4-stabilization https://github.com/libcna/meta-gl.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. After this step no system SDL packages are required. Leave off --recursive: CNA's CMake says the non-recursive form is the right one (recursion only pulls unused codec submodules). To pin the exact snapshot this guide documents: git checkout c1c316b9c7a846ce8002809c151fcd1af14942c9.

Quick start build (Linux - EasyGL renderer)

cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
cmake --build build --target cna_demo_2d --parallel
(cd build && ./cna_demo_2d)

The first configure builds SDL3, SDL3_image and SDL3_mixer from source into a persistent .sdl-prebuilt-* directory, so it takes a while (no timing was measured); later configures reuse it. Run the demo from the build directory, where the post-build step copied its Content/ folder. cmake --build build with no target builds every test and example and is much larger.

Quick start build (Linux - SDL_Renderer renderer)

cmake -S . -B build-sdlrenderer -DCNA_GRAPHICS_RENDERER=SDL_RENDERER
cmake --build build-sdlrenderer --target cna_demo_2d --parallel

This needs only sharp-runtime and the vendored SDL3 - no easy-gl/meta-gl checkouts. SDL_RENDERER is 2D only.

Verify the build

# Pure-CPU unit tests of one module - no display needed
cmake --build build --target CnaMathTests --parallel
./build/CnaMathTests

# Run the demo for six frames and exit (needs a display; on a headless box install
# xvfb and let xvfb-run provide one)
(cd build && xvfb-run -a ./cna_demo_2d --smoke 6)

CNA defines no hello-triangle-sdl target of its own (older text mentions one; the name is easy-gl's example, see Building); the demos are cna_demo_2d and, when the default renderer is one of the three EasyGL profiles, VULKAN, WEBGPU or FNA3D, cna_house3d_demo.

Minimal game skeleton

Here is the smallest possible CNA game - a window that clears to cornflower blue, the traditional XNA default clear colour, and draws one texture. (Replace assets/logo.png with a file that exists relative to where you run the program.)

#include <memory>

#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
#include "Microsoft/Xna/Framework/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"
#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"

using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;

class MyGame final : public Game {
public:
    MyGame() : graphics_(this) {}

protected:
    void LoadContent() override {
        spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
        logo_ = std::make_unique<Texture2D>("assets/logo.png", getGraphicsDeviceProperty());
    }

    void Update(GameTime& gameTime) override {
        // Update game state here.
        Game::Update(gameTime); // updates registered game components
    }

    void Draw(const GameTime& gameTime) override {
        auto& device = getGraphicsDeviceProperty();
        device.Clear(Color::CornflowerBlue);
        spriteBatch_->Begin();
        spriteBatch_->Draw(*logo_, 100.0f, 80.0f);
        spriteBatch_->End();
        Game::Draw(gameTime); // draws components; Game presents the frame after Draw returns
    }

private:
    GraphicsDeviceManager graphics_;
    std::unique_ptr<SpriteBatch> spriteBatch_;
    std::unique_ptr<Texture2D> logo_;
};

int main() {
    MyGame game;
    game.Run();
    return 0;
}

Testing your build

CNA's snapshot contains 813 C++ test source files and 11,380 static GoogleTest-family definitions covering the framework and configuration-specific modules (alpha.1: 568 and 8,263). There are also standalone pixel programs under examples/ that are not in those figures.

# Build and run one module's tests directly
cmake --build build --target CnaCoreTests --parallel
./build/CnaCoreTests

# Or the aggregate binary (all modules; a large build)
cmake --build build --target CnaTests --parallel
./build/CnaTests

# CTest: build everything first (a full `cmake --build build`), then filter
xvfb-run -a ctest --test-dir build -L input --output-on-failure

This runs what the configured build registered. Parameterized tests, platform/renderer selection, optional layers and dependencies change the executable and CTest inventory; run ctest --test-dir build -N to inspect it. Many CTest entries are separate executables that --target CnaTests does not build, and window-creating tests need a DISPLAY, so an unfiltered ctest after building only CnaTests is misleading. There are 22 focused test targets (CnaCoreTests, CnaMathTests, CnaAudioTests, ...), listed on Building. See Verification & Known Issues.

3D rendering example

The following example draws a coloured triangle using the EasyGL or Vulkan renderer. It demonstrates VertexBuffer, BasicEffect, and the DrawPrimitives call.

#include <memory>

#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/BasicEffect.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexBuffer.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexPositionColor.hpp"

using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;

class TriangleGame final : public Game {
public:
    TriangleGame() : graphics_(this) {
        graphics_.setPreferredBackBufferWidthProperty(800);
        graphics_.setPreferredBackBufferHeightProperty(600);
    }

protected:
    void LoadContent() override {
        effect_ = std::make_unique<BasicEffect>(getGraphicsDeviceProperty());
        effect_->VertexColorEnabled = true;

        VertexPositionColor vertices[] = {
            { Vector3( 0.0f,  0.5f, 0.0f), Color::Red   },
            { Vector3( 0.5f, -0.5f, 0.0f), Color::Green },
            { Vector3(-0.5f, -0.5f, 0.0f), Color::Blue  },
        };
        vb_ = std::make_unique<VertexBuffer>(
            getGraphicsDeviceProperty(),
            VertexPositionColor::getVertexDeclarationStatic(),
            3, BufferUsage::None);
        vb_->SetData(vertices, 3);
    }

    void Update(GameTime& gameTime) override { Game::Update(gameTime); }

    void Draw(const GameTime& gameTime) override {
        auto& gd = getGraphicsDeviceProperty();
        gd.Clear(Color::CornflowerBlue);

        effect_->World = Matrix::getIdentityProperty();
        effect_->View = Matrix::CreateLookAt(
            Vector3(0, 0, 2), Vector3::Zero, Vector3::Up);
        effect_->Projection = Matrix::CreatePerspectiveFieldOfView(
            MathHelper::PiOver4, 800.0f / 600.0f, 0.1f, 100.0f);

        gd.SetVertexBuffer(vb_.get());
        for (auto& pass : effect_->getCurrentTechniqueProperty()->getPassesProperty()) {
            pass.Apply();
            gd.DrawPrimitives(PrimitiveType::TriangleList, 0, 1);
        }
        Game::Draw(gameTime); // Game presents the frame after Draw returns
    }

private:
    GraphicsDeviceManager graphics_;
    std::unique_ptr<BasicEffect> effect_;
    std::unique_ptr<VertexBuffer> vb_;
};

int main() { TriangleGame game; game.Run(); }

Build with the OPENGLES3, OPENGL33 or VULKAN renderer — 3D rendering is not available on SDL_RENDERER. See the 3D Rendering guide for the full API.

Beyond the default: other platforms at a glance

The commands above use the default SDL3 platform. A few other one-line starting points (each is explained on the linked page):

  • X11 or Wayland (Linux): the default SDL3 platform picks the session’s video driver; force one with SDL_VIDEODRIVER=x11 or SDL_VIDEODRIVER=wayland — Tutorial 135 and Tutorial 136.
  • No SDL at all (windowless): cmake -S . -B build -DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=HEADLESS -DCNA_AUDIO_PLATFORM=NULL -DCNA_GRAPHICS_RENDERER=HEADLESS — Tutorial 138.
  • Web: emcmake cmake --preset web — Building and Platforms.
  • Windows from Linux: the cmake/toolchains/mingw-w64.cmake toolchain file — Building.

Next steps

External references

CNA targets the XNA 4.0 API surface. These references document the target API: