Migrating from MonoGame / XNA
Snapshot status: the XNA-shaped namespaces and core types make structural translation largely mechanical, and every documented type and member of the XNA 4.0 runtime is represented (331/331 types, 3,627/3,627 members — representation, not behaviour; see XNA Compatibility). CNA reads built-in XNB types, lets games register explicit C++ type readers, and now has a build-time content pipeline (cna-content) that can write .xnb and .cnb. Already-compiled XNA/FNA Effect Framework bytecode loads on FNA3D and on builds that opt in to a compiled-effect option (11 of the 14 renderer identities); HLSL source and MonoGame MGFX still need a separate porting strategy.
Keeping your game in C#? This guide is for rewriting a game in C++. If you would rather keep the C# source, CNA.NET compiles an unchanged XNA 4.0 C# game against CNA through its C ABI; see Migrating C# games to CNA.NET.
Overview
CNA is a C++23 reimplementation of Microsoft XNA 4.0, with SDL3 as its windowing platform layer (it reaches Windows, X11, Wayland and macOS through SDL’s own video drivers). Its API is intentionally faithful to XNA: the same namespace hierarchy, the same class names, the same method names. For a developer coming from MonoGame or XNA the conceptual model is identical — Game, SpriteBatch, ContentManager, GraphicsDevice, BasicEffect — all exist and behave as expected.
The differences are almost entirely syntactic: C++ does not have garbage collection, properties, delegates, or using-disposal blocks. Each of these maps cleanly to a C++ equivalent. Once you have internalised the mechanical substitutions in this guide, most XNA code ports line-by-line.
1. Namespace mapping
XNA uses dot-separated C# namespaces; CNA uses the identical names with :: separators. The using directive works the same way.
| C# (XNA / MonoGame) | C++ (CNA) |
|---|---|
Microsoft.Xna.Framework |
Microsoft::Xna::Framework |
Microsoft.Xna.Framework.Graphics |
Microsoft::Xna::Framework::Graphics |
Microsoft.Xna.Framework.Input |
Microsoft::Xna::Framework::Input |
Microsoft.Xna.Framework.Audio |
Microsoft::Xna::Framework::Audio |
Microsoft.Xna.Framework.Content |
Microsoft::Xna::Framework::Content |
using Microsoft.Xna.Framework; |
using namespace Microsoft::Xna::Framework; |
2. C++ vs C# quick reference
The table below covers the most common syntactic substitutions you will encounter during a port.
| Concept | C# (XNA) | C++ (CNA) |
|---|---|---|
| Property read | game.IsActive |
game.getIsActiveProperty() |
| Property write | graphics.PreferredBackBufferWidth = 800 |
graphics.setPreferredBackBufferWidthProperty(800) |
| Struct fields (vectors, matrices, rectangles) | v.X, m.M11, rect.Width |
Plain public fields, the same spelling (v.X); only C# properties become get/setXProperty() accessors (for example color.getRProperty()) |
| Content access | Content.Load<Texture2D>("a") |
getContentProperty().Load<Texture2D>("a") — returns Texture2D by value; there is no Content member or pointer |
| Graphics device | GraphicsDevice.Clear(...) |
getGraphicsDeviceProperty().Clear(...) (a reference) |
| Async operation results | IAsyncResult returned by Begin* |
std::unique_ptr<IAsyncResult>; pass result.get() to End* |
| Inheritance | class MyGame : Game |
class MyGame final : public Game |
| Object creation | new SpriteBatch(gd) |
std::make_unique<SpriteBatch>(gd) |
| Null reference | null |
nullptr |
| String type | string |
std::string or String (sharp-runtime alias) |
| Boolean | bool (true/false) |
bool (true/false) — same |
| Delegates / events | EventHandler handler = ... |
Virtual method override or EventHandler<T> |
using resource disposal |
using (var x = ...) {} |
RAII / std::unique_ptr<> |
| Abstract override | override void Draw(...) |
void Draw(...) override |
| Integer types | int, byte, short |
intcs, bytecs, shortcs (sharp-runtime aliases) |
| Float type | float |
Single or float |
| Array | T[] |
std::vector<T> or std::array<T, N> |
| Optional / nullable | T? |
std::optional<T> |
foreach |
foreach (var x in col) |
Range-for: for (auto& x : col) |
3. Game class migration
The Game subclass is the entry point for any XNA game. The lifecycle methods — Initialize, LoadContent, Update, and Draw — exist identically in CNA (they are protected virtual; Update takes GameTime& and Draw takes const GameTime&). The differences are C++ syntax: override comes after the signature, member variables are declared with their types (no implicit nullability), and resources are owned by std::unique_ptr or held by value rather than being garbage-collected. Two behavioural points matter when you port timing-sensitive code: with the default fixed time step the first Update sees an elapsed time of zero (XNA's clock; a variable step gets the time since the loop started), and a Game registers the built-in XNB readers for you. See Game Loop.
| C# (MonoGame) | C++ (CNA) |
|---|---|
|
|
4. Memory management
XNA relies on the .NET garbage collector: objects are allocated with new and reclaimed automatically. C++ has no garbage collector; you manage object lifetimes explicitly. CNA follows standard modern C++ ownership conventions:
std::unique_ptr<T>— single-owner resource. Use this for game components, renderers, and anything yourGameclass owns exclusively. The resource is released automatically when the owning pointer goes out of scope or is reset.std::shared_ptr<T>— shared ownership, for objects that several owners really do share. Content assets are not the usual case:ContentManager::Load<T>returns each asset (Texture2D,SoundEffect,SpriteFont,Model) by value, and the GPU state behind a texture is shared-owned, so a copy you hold stays valid even after the manager unloads. Keep assets as value orstd::optionalmembers; types without a default constructor (SpriteFont,Video) needstd::optionalor aunique_ptr.- Stack allocation — use for temporaries such as
Vector2,Color,Rectangle, andGameTime. These are small value types and incur no heap allocation. ContentManager::Unload()— releases all assets currently held by the content manager's cache. Call this when transitioning between scenes to free GPU and CPU memory. Copies you already hold remain valid.- Game components — add drawable/updatable components to the
getComponentsProperty()collection (identical to XNA; astd::shared_ptroverload keeps the component alive for you), or hold them asstd::unique_ptrmembers on yourGamesubclass.
The key rule: if you would write new Foo(...) in C#, write std::make_unique<Foo>(...) in C++ and store the result in a std::unique_ptr<Foo> member. The destructor will run automatically when your game object is destroyed.
5. Content pipeline
XNA and MonoGame pre-compile source assets into .xnb binary files. CNA reads XNB directly. Its 61 built-in readers (60 in a build without native 128-bit integer support) cover primitives, math, textures, fonts, audio, video, stock and compiled effects, and models, with LZX and shared-resource resolution, and ContentManager can migrate asset by asset between XNB, CNA's own .cnb container and supported loose formats. New in this snapshot: CNA can also write content at build time with the cna-content tool (.xnb or .cnb output, including .fbx, .x, .spritefont and XNA .contentproj inputs), so a project no longer has to keep MGCB or the XNA pipeline around just to produce assets. It is not an MGCB replacement: there is no .mgcb route.
A Game registers the built-in XNB readers in its constructor, so a game subclass needs no setup. A stand-alone ContentManager used without a Game (a tool, a test) still starts with an empty registry and must call CNA::Internal::Xnb::RegisterAllBuiltInXnbReaders() itself. See XNB Loading for the full reader list and the known gaps.
| XNA / MonoGame | CNA |
|---|---|
| Pre-compile assets with MGCB / XNA Content Pipeline Tool | Existing .xnb output can be copied across as-is; loose source files also work with no build step; or build with cna-content (XNB or CNB output) |
.xnb binary files at runtime |
.xnb binary files at runtime, or raw asset files (PNG, WAV, OGG) / JSON descriptors |
Content.Load<Texture2D>("player") loads player.xnb |
getContentProperty().Load<Texture2D>("player") prefers player.xnb, then player.cnb, else falls back to a loose player.png (or another supported image extension) |
Fonts: .spritefont + MGCB |
Fonts: a compiled SpriteFont .xnb, a .cnj descriptor naming a pre-rendered glyph-atlas texture, or a .spritefont built with cna-content (FreeType, when CNA_ENABLE_FONT_PIPELINE is available) |
Models: .fbx / .obj compiled to .xnb |
Models: the XNB model readers, a .gltf/.glb file read directly, a .cnj from the gltf_to_cnj converter, or .fbx/.x built to .xnb/.cnb with cna-content. There is no .obj route |
Custom shaders: .fx HLSL compiled by pipeline |
XNA/FNA D3D9 Effect Framework binaries load on FNA3D or on builds that opt in to a compiled-effect option (11 of the 14 renderer identities; see Effects). Otherwise migrate to renderer-native ShaderEffect. HLSL .fx source and MGFX are not accepted at run time; cna-content can compile .fx at build time through an external legacy fxc (unverified against a genuine Microsoft fxc). |
| Custom / user-defined content types | No reflection-based discovery. Implement ContentTypeReader<T> and register its creator explicitly (ContentTypeReaderManager::AddTypeCreator), declare the field list once with ReflectiveTypeReaderBuilder<T>, or move the data to a loose format. |
For a typical 2D game the migration is straightforward: copy your existing content directory across, and the Load<T> calls are identical apart from the accessor spelling and the by-value result. Where you would rather drop the pre-compilation step entirely, place PNG and WAV files in the content directory instead and point getContentProperty().setRootDirectoryProperty() at it (a Game's content root already defaults to "Content").
6. Audio
CNA implements the main XNA audio API in the Microsoft::Xna::Framework::Audio namespace. The common classes work as expected:
SoundEffect— loads a WAV file (or a compiled.xnb) and plays it as a fire-and-forget sound. API identical to XNA;Load<SoundEffect>returns it by value andCreateInstance()returns the instance by value.SoundEffectInstance— a controllable instance of aSoundEffect: pause, resume, loop, and volume control all work throughsetVolumeProperty,setPitchProperty,setPanPropertyandsetIsLoopedProperty. As in XNA,Apply3DafterPlay()on a pan-mode instance throwsInvalidOperationException.MediaPlayer/Song— background music playback. API identical to XNA.
The XACT subsystem (AudioEngine, SoundBank, WaveBank, Cue) is a real parser and player rather than a facade, with FACT-shaped volume/RPC behavior and 3D calculations, playing through the SDL3 or ALSA audio implementation (the Null choice compiles it without audible output). Important caveats: only the first PlayWave per track is honoured; XMA/WMA wave-bank entries log and return a null sound rather than throwing; reverb is a no-op; and the XNB SoundEffectReader has different failure behavior. SoundEffect and MediaPlayer remain simpler alternatives.
7. Known gaps and limitations
Before porting, review these known gaps. The API surface is largely there, but the items below are absent, partial, or need hardware validation. See Verification & Known Issues for the complete current list.
- Compiled effects have a renderer boundary. XNA/FNA D3D9 Effect Framework binaries and XNB Effect payloads load on FNA3D always and on 10 more renderer identities when their default-off
CNA_*_COMPILED_EFFECTSbuild option is enabled (a default configure reports the capability on FNA3D only). HLSL.fxsource and MonoGame MGFX are not accepted at run time. QueryGraphicsCapability::CompiledEffectsbefore depending on the path. - Surface formats are profile- and renderer-dependent — the default
Reachprofile now refuses the wider formats (and multiple render targets, occlusion queries, 32-bit indices, float targets and large cubes) on every renderer unlessHiDefis requested withgraphics.setGraphicsProfileProperty(GraphicsProfile::HiDef); on top of that, eight renderer families (ten of the 14 identities) classify the broader public texture formats themselves, each accepting its own subset, while the other four identities (DIRECTX9,SDL_RENDERER,HEADLESSandSTUB) have no classifier and accept onlySurfaceFormat::Colorfor a publicTexture2D. Verify the active renderer. - XNB is supported, with explicit registration for custom types — built-in video, typed external-reference and renderer-qualified effect readers exist, and a
Gameregisters the built-ins. Custom types can register a reader/creator, but there is no reflection-based catch-all. See XNB Loading. - GamerServices is offline by default, with an optional CNA server — all 52 types are present with complete signatures, and unlike FNA's no-op shim CNA backs them with a real local implementation. Achievements and leaderboard entries are genuinely written as JSON under
<StorageDevice root>/GamerServices/(the storage root, notSDL_GetPrefPath()) and reload in a later process, andLeaderboardWriter/LeaderboardReaderdo real sorting, ranking and paging.Guide::BeginShowMessageBoxandBeginShowKeyboardInputare complete implementations with overlay rendering. The Guide is a CNA-drawn system UI whose standard pages open, andAvatarRenderer::Draw()draws CNA’s own avatars. Accounts, social features andPlayerMatch/Ranked/JoinInvitedsessions need a self-hosted CNA Gamer Services server, configured outside game code (not Xbox LIVE; no Steam integration); without one those session types throwGamerServicesNotAvailableException. - Media is build-dependent — the catalogue and playback layers are real, while FFmpeg-backed video decodes only where the optional backend was built (
CNA_ENABLE_VIDEO; Linux and macOS with FFmpeg found). On Windows, Emscripten, Android and iOS theVideotypes link but file-backed playback throwsNotSupportedException. See Video Playback. - Touch input — the
TouchPanelAPI and gesture pipeline are fully wired, with all 10 XNA gesture types genuinely detected. One bug:TouchCollectionreportsIsReadOnly == truebut its mutators mutate rather than throwing. - Apple targets are scoped — macOS/Metal has automatic CI. iOS is experimental and limited to SDL_RENDERER final-link plus a one-frame simulator smoke path; tvOS is unsupported.
- Renderer-dependent state —
FillMode::WireFrameand some other state are not honoured on every renderer, and state objects throwInvalidOperationExceptionif you mutate them after binding (as in XNA). QuerySupportsCapability()and theRendererCapabilityProfile, and verify visually. See Graphics State. - Web builds — no save persistence (the storage root lands in Emscripten's in-memory file system), no LAN discovery, and video throws. A stack-allocated
Gameis fine.
8. Code examples
Full Game class
The following pair shows a minimal but complete game loop: window setup, content loading, update, and draw.
| C# (MonoGame) | C++ (CNA) |
|---|---|
|
|
SpriteBatch draw
| C# (MonoGame) | C++ (CNA) |
|---|---|
|
|
ContentManager load
| C# (MonoGame) | C++ (CNA) |
|---|---|
|
|
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Migrating an XNA game to CNA: a process that keeps every mismatch attributable — A porting process for XNA and FNA games at this snapshot: freeze a baseline, translate mechanically, map assets and shaders to real routes, pick renderers by evidence and verify in rungs.
- Porting case studies: the Blupi games, the official samples and the example catalogue — What real ports around CNA teach: a native-CNA port plan for a reconstructed Blupi game, the Speedy Blupi 2013 port, cna-samples and cna-examples, each pinned.