Getting Started with CNA

CNA - C++ XNA 4.0 reimplementation  ยท  227 of 245 XNA 4.0 types implemented

CNA implements 227 of the 245 public types in the XNA 4.0 API, measured against FNA. Every type in Graphics, Audio, Input, Media, Storage and the math namespace is present. Two things are deliberately absent and will shape any port: the .xnb content pipeline (raw assets plus JSON descriptors are used instead) and compiled .fx shader bytecode (custom shaders must be hand-written GLSL/SPIR-V). Verification is real: 4,373 unit tests and 490 GPU pixel-readback tests across four backends, plus differential testing against a running FNA build. 63 of the 86 official XNA samples build on it. APIs are still evolving — good for research, demos and porting experiments, not yet for shipping a commercial game.

What you are getting

CNA is a C++ framework that mirrors the XNA 4.0 programming model. You write game code against the Microsoft::Xna::Framework C++ API. CNA handles windowing, input, audio, and rendering through SDL3 and a pluggable backend system.

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
  • C++23-capable compiler: GCC 12+ or Clang 15+
  • ../sharp-runtime directory (sibling to the CNA repo) - no external dependencies
  • ../easy-gl directory - only needed for the EASYGL backend
  • SDL3, SDL3_image, and SDL3_mixer are built from vendored submodules - no system packages required

Windows

  • CMake 3.20 or newer
  • One of: MSVC 2022 (v17.8+), clang-cl, or MinGW-w64
  • ..\sharp-runtime directory (sibling to the CNA repo)
  • SDL3 built from vendored submodules - no pre-built binaries or CMAKE_PREFIX_PATH needed

System requirements

ComponentRequirement
CompilerGCC 12+, Clang 15+, or MSVC 2022 v17.8+ (C++23 required)
Build systemCMake 3.20+
GitAny recent version (for submodules)
OpenGL (EASYGL backend)OpenGL ES 3.0 or OpenGL 3.0+ desktop; GPU driver installed
Vulkan (VULKAN backend)Vulkan-capable GPU; vulkan-headers / libvulkan-dev
Disk space~500 MB (source + build artifacts)
RAM4 GB minimum for compilation
Sibling repossharp-runtime and (for EASYGL) easy-gl cloned alongside cna/

Clone & initialise

git clone https://github.com/openeggbert/cna.git
cd cna
git submodule update --init --recursive

This populates third_party/SDL, third_party/SDL_image, and third_party/SDL_mixer. After this step no system SDL packages are required.

Quick start build (Linux - EasyGL backend)

git submodule update --init --recursive
cmake -S . -B build -DCNA_GRAPHICS_BACKEND=EASYGL
cmake --build build --target CNA CnaTests
ctest --test-dir build --output-on-failure

Quick start build (Linux - SDL_Renderer backend)

cmake -S . -B build-sdlrenderer -DCNA_GRAPHICS_BACKEND=SDL_RENDERER
cmake --build build-sdlrenderer --target CNA CnaTests

Verify the build

# Run the test suite
ctest --test-dir build --output-on-failure

# Run the hello-triangle verification demo
cmake --build build --target hello-triangle-sdl

Minimal game skeleton

Here is the smallest possible CNA game - a window that clears to cornflower blue, the traditional XNA default clear colour.

#include <memory>

#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
#include "Microsoft/Xna/Framework/Graphics/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 {
        (void)gameTime;
        // Update game state here.
    }

    void Draw(const GameTime& gameTime) override {
        (void)gameTime;
        auto& device = getGraphicsDeviceProperty();
        device.Clear(CornflowerBlue);
        spriteBatch_->Begin();
        spriteBatch_->Draw(*logo_, 100.0f, 80.0f);
        spriteBatch_->End();
        device.Present();
    }

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 includes a GoogleTest-based test suite with 4,373 tests covering math types, geometry, curves, game loop semantics, and PackedVector precision.

# Run all tests
ctest --test-dir build --output-on-failure

# Or build and run directly
cmake --build build --target CnaTests
./build/CnaTests

All 4,373 unit tests should run, with 4,371 passing and 2 skipped (those two need a real accelerometer/gyroscope). The unit suite has no known failures. The separate GPU pixel-test suite (ctest) does have 5 known, tracked failures โ€” see Verification & Known Issues.

3D rendering example

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

#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Graphics/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_.setPreferredBackBufferWidth(800);
        graphics_.setPreferredBackBufferHeight(600);
    }

protected:
    void LoadContent() override {
        effect_ = std::make_unique<BasicEffect>(getGraphicsDeviceProperty());
        effect_->setVertexColorEnabled(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::VertexDeclaration,
            3, BufferUsage::None);
        vb_->SetData(vertices, 3);
    }

    void Update(const GameTime&) override {}

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

        effect_->setWorld(Matrix::Identity);
        effect_->setView(Matrix::CreateLookAt(
            Vector3(0, 0, 2), Vector3::Zero, Vector3::Up));
        effect_->setProjection(Matrix::CreatePerspectiveFieldOfView(
            MathHelper::PiOver4, 800.0f / 600.0f, 0.1f, 100.0f));

        gd.setVertexBuffer(*vb_);
        for (auto& pass : effect_->getCurrentTechnique().Passes) {
            pass.Apply();
            gd.DrawPrimitives(PrimitiveType::TriangleList, 0, 1);
        }
        gd.Present();
    }

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

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

Build with the EASYGL or VULKAN backend โ€” 3D rendering is not available on SDL_RENDERER. See the 3D Rendering guide for the full API.

Next steps

External references

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