ContentManager

Microsoft::Xna::Framework::Content — typed asset loading from .xnb binaries, CNA’s own .cnb containers, or loose files

ⓘ

Implementation status: In this snapshot, core Load<T>() and Unload() work for all built-in types. ContentManager reads .xnb files through a real reader (61 built-in type readers — see XNB Loading & Interoperability), reads CNA’s own compiled .cnb container (see CNB Format), and loads loose files. CNA also has a build-time Content Pipeline (cna-content) that writes .cnb and .xnb. IServiceProvider wiring is simplified; Game::Services is available but not all XNA services are pre-registered. Two specifics: ResourceContentManager is now implemented (it was a stub at alpha.1), and Unload() clears the manager’s cache of loaded assets — a copy you already hold stays valid.

Overview

ContentManager lives in the Microsoft::Xna::Framework::Content namespace and is the standard mechanism for loading typed game assets from disk. It mirrors the XNA 4.0 API: call Load<T>(assetName) with a path relative to RootDirectory, and the manager returns the loaded asset by value (a failed load throws ContentLoadException; there is no null to check). Assets are cached internally by type and name; repeated calls with the same name return the cached copy without re-reading the file.

CNA supports three kinds of asset on the same call. It reads the XNA Content Pipeline binary format (.xnb) through a real reader; it reads its own compiled container (.cnb), which the cna-content tool produces; and it also loads loose files directly: complex asset types such as fonts, models, and shaders can be described by small JSON descriptor files, while simple image and audio formats are read straight from their native container (PNG, WAV, OGG, etc.) with no intermediate build step.

A ContentManager is constructed with an IServiceProvider* and, optionally, a root directory string. In a typical Game subclass the instance is reached through getContentProperty() (there is no Content member or pointer), which is already wired to the game's graphics device and has a default root of "Content". A relative root is looked up under the game’s title folder (TitleLocation.Path, normally the executable’s directory) first, as XNA does, and falls back to the working directory when that folder has no such directory; on Android it names packaged assets.

// Inside a Game subclass the manager already exists (root "Content"):
Texture2D player = getContentProperty().Load<Texture2D>("textures/player");

// Or construct your own, e.g. one manager per level so it can be Unload()ed on its own:
ContentManager levelContent(&getServicesProperty(), "Content/Levels/Level1");
Texture2D tiles = levelContent.Load<Texture2D>("tiles");

Default roots differ by constructor: ContentManager(IServiceProvider*) leaves RootDirectory empty (as XNA does), while ContentManager() and the manager inside a Game use "Content". Names may use / or \, and an existing file is matched case-insensitively.

Asset paths: .xnb first, .cnb second, loose files last

For any given asset name, ContentManager prefers a .xnb when one exists, then a .cnb, and only then falls back to the loose-file path. Call sites do not change: Load<Texture2D>("textures/player") resolves to textures/player.xnb if that file is present, to textures/player.cnb if it is not, and to textures/player.png if neither is.

This means content built by the original Microsoft tooling (or by MonoGame's mgcb) can be dropped into a project as-is, content built by CNA's own cna-content tool loads the same way, and both can be mixed freely with loose assets in the same content tree. Migration can go either way and one asset at a time.

What Load<T>("name") tries, in order:

StepWhat is looked forNotes
1The cacheKeyed by the C++ type and the lower-cased, normalised name, so Load<Texture2D>("a") and Load<Model>("a") are separate entries. Load<SoundEffect> is never cached (the type is move-only).
2<root>/name.xnbAlways wins over everything else, even a literal path or a registered loose reader — a compiled .xnb is treated as authentic external content. Needs no per-T reader registered on the manager: the file’s own type-reader table drives dispatch.
3<root>/name.cnbSelf-describing: the asset-type id in the file selects the loader, so no reader needs registering on the manager. See CNB Format.
4A literal name.cnbOnly when the name you passed itself ends in .cnb.
5The loose-file reader registered for TThe literal path if that file exists, then name.cnj (a .cnj sidecar always has the final say over a native file of the same name), then each extension the reader declares (for example .png, .jpg, … for Texture2D). A loose reader that throws any std::exception is wrapped in ContentLoadException. With no reader registered for T the call throws ContentLoadException (“No reader registered for type”).
ⓘ

One required startup call. Outside a Game, the XNB type-reader registry is empty by default: a ContentManager used in a tool, a test or a C API host must call CNA::Internal::Xnb::RegisterAllBuiltInXnbReaders() once before loading XNB content. Inside a Game subclass you need not (new since alpha.1): Game’s constructor makes the call, so the 61 built-in XNB type readers (60 on toolchains without a native 128-bit integer, which drops DecimalReader) are ready before Initialize() and LoadContent() run. Custom XNB readers, including reflection-style ones declared with ReflectiveTypeReaderBuilder<T>, are registered explicitly. See XNB Loading & Interoperability and Tutorial 148.

Producing compiled content

At alpha.1 CNA could only read compiled content. In this snapshot it can also write it. cna-content (CMake target cna_content_tool, also reachable from CMake through cna_add_content()) is a build-time tool that turns source files — PNG textures, WAV sounds, glTF models, .cnj descriptors, .spritefont fonts, and more — into .cnb files (the default) or XNA-compatible .xnb files. The runtime ContentManager on this page is the consumer of that output; nothing in a shipped game needs the tool.

Content Pipeline

The build-time tool: importers, processors and writers, the source-to-output route table, .cna-content.json, incremental builds, cna_add_content(), and the XNA Content.Pipeline facade.

CNB Format

The binary layout of .cnb: 64-byte header, 48-byte table-of-contents entries, CRC-32C, optional Zstandard chunks (library and C ABI only), and how ContentManager loads it.

Tutorial 145: Build Content with cna-content

An end-to-end walk from a ContentSource/ folder to Load<Texture2D>(), including incremental rebuilds and the CMake helper.

Tutorial 147: XNB Interoperability

Loading .xnb files from XNA and MonoGame pipelines, writing .xnb from CNA, and diagnosing reader problems.

For the other command-line tools (cna_tool_cnb_info, cna_tool_gltf_to_cnb, cna_tool_gltf_to_cnj and friends) see Command-Line Tools.

Core API

Member Signature Description
Load<T>() T Load<T>(const std::string& assetName) Loads and caches an asset of type T and returns it by value. Subsequent calls with the same type and name return the cached copy without re-reading the file. Throws ContentLoadException when the asset cannot be found or read.
Unload() void Unload() Clears the manager’s cache of loaded assets. A copy you already hold stays valid (GPU state is shared-owned), so reset your own holders when you want the memory back. The manager can be reused after calling Unload(); Dispose() calls it too.
RootDirectory getRootDirectoryProperty() / setRootDirectoryProperty(const std::string&) Read/write property. The base path prepended to every assetName passed to Load<T>(). See the constructor defaults above.
ServiceProvider System::IServiceProvider* getServiceProviderProperty() const Read-only. The service provider supplied at construction time (null for a manager built with the default constructor). Built-in readers get the GraphicsDevice from setGraphicsDevice() (which Game calls for its own manager) or, failing that, from the IGraphicsDeviceService in the service provider; with neither, a graphics-dependent load throws ContentLoadException.
RegisterTypeReader<T>() void RegisterTypeReader<T>(std::unique_ptr<LooseFileContentTypeReader<T>>) CNAEXT. Registers a loose-file reader for T on this manager instance, replacing any reader already there. See Custom content readers.
RegisterCnjLoader<T>() void RegisterCnjLoader<T>(const std::string& typeName, CnjLoaderFn<T>) CNAEXT. Registers a named .cnj loader so several "type" values can produce the same T. Throws if T already has a reader or the (T, typeName) pair is repeated.
RegisterCnbLoaderEXT<T>() static void RegisterCnbLoaderEXT<T>(std::uint32_t id, const std::string& name, CnbLoaderFn<T>) CNAEXT. Registers a loader for a game-defined .cnb asset type (custom id range only). Process-wide. See CNB Format.
GetContentManifest() / GetXnbReaderUsageSummary() const std::vector<ContentManifestEntry>& / std::vector<ContentManifestReaderUsage> CNAEXT diagnostics. A point-in-time scan of the content root (rebuilt with RefreshContentManifest()) and a per-reader-name count of the .xnb files that reference it, with whether a reader is registered for it. No file watching or hot reload.

Load<T> resolution order

When you call content.Load<T>("path/to/asset"), the manager:

  1. Checks the internal cache; returns the cached copy if found.
  2. Constructs the full file path as RootDirectory + "/" + assetName (backslashes normalised, an existing file matched case-insensitively).
  3. Tries <path>.xnb, then <path>.cnb, then a literal .cnb name — these need no reader registered for T (see the table above).
  4. Otherwise looks up the LooseFileContentTypeReader<T> registered for T on this manager and asks it to resolve the path: the literal path, then .cnj, then each extension the reader declares with GetExtensions().
  5. Calls the reader’s Read(path, manager), stores the result in the cache and returns it.

For types that map directly to a file container (e.g. Texture2D from a PNG), no extension is required in the asset name — the reader appends the appropriate extension automatically. For descriptor-described types the asset name resolves to the .cnj file, with or without the extension.

Caching and lifetime

Loaded assets are held by the manager until Unload(), keyed by the requested C++ type and the lower-cased, normalised name. Requesting a name under a different type is a separate load, not a cast. CNA no longer keeps a separate weak texture cache: a texture stays cached by the manager until you unload it, and the value you received is your own copy of a shared-owned GPU resource. Because Load<T> returns by value, a member declared as Texture2D or Model is your reference; types without a default constructor (SpriteFont, Song, SoundEffect) need std::optional<T> as a holder — Tutorial 45 shows the pattern. ContentManager is not thread-safe (a plain map, no lock); load on one thread or serialise access, as Tutorial 77 explains.

One protected seam matters if you write your own manager: ResourceContentManager (implemented in this snapshot; constructed with a System::Resources::ResourceManager*) overrides OpenStream() to read the named binary resource. The base Load<T>() ladder does not route through OpenStream(); a derived manager reads through the protected ReadAsset<T>().

Built-in content type readers

Type T File format(s) Notes Status
Texture2D .png .jpg .jpeg .bmp .gif .tga .tif .tiff .qoi Decoded by the vendored stb_image decoder into straight (non-premultiplied) RGBA; the loose path does not premultiply alpha. A .cnj descriptor naming a sourceFile (with an optional colorKey) is also accepted. The loaded texture is cached by the manager until Unload(). A single mip level is created; a mip chain comes from .cnb/.xnb content built with generateMipmaps. Implemented
TextureCube .dds Real DDS parser for cube maps. DDS is the only accepted loose container (a .cnj wrapper naming a sourceFile is also accepted, and .xnb/.cnb cubes load through their own tiers). Implemented
SpriteFont .cnj A .cnj descriptor naming a glyph-atlas texture, which is itself resolved through ContentManager. See format below. Implemented
Texture3D .cnj (+ binary pixel sidecar) Loaded as std::shared_ptr<Texture3D> because Texture3D is move-only. Single mip level; self-contained JSON plus a raw binary sidecar. .xnb and .cnb volumes load through their own tiers. Implemented
Model .xnb, .cnb, .cnj, .gltf, .glb Any of these resolves for Load<Model>("name"), in that order. glTF 2.0 loads directly at run time; the gltf_to_cnj tool writes .cnj; cna-content and cna_tool_gltf_to_cnb write .cnb; .x and .fbx sources are build-time only. The XNB model reader family is registered. See Model Loading. Implemented
std::shared_ptr<SkinnedModelEXT> .skinnedmodel.json CNA extension (not an XNA type) for skinned meshes, carrying a skeleton and AnimationClipEXT data. Loaded through a shared pointer. Also registered: AnimationClipEXT and Curve (both from .cnj; .cnb carries both as well). Implemented
std::shared_ptr<Effect> / ShaderEffect .cnj, .xnb A .cnj descriptor naming renderer-native vertex and fragment sources, loaded as std::shared_ptr<Effect> and downcast to ShaderEffect. This is separate from XNA/FNA D3D9 Effect Framework binaries (.fxb or XNB Effect payloads), which require a renderer build advertising CompiledEffects. The run time does not compile HLSL .fx source; at build time cna-content can compile .fx to an .xnb Effect only by running an external fxc-compatible compiler, and CNB has no Effect schema. See Effects System. Implemented
SoundEffect .wav only as a loose file (plus .xnb/.cnb) Short clips loaded fully into memory. Note the asymmetry: unlike Song, the loose SoundEffect loader accepts WAV and nothing else. Compressed formats must be routed through Song/MediaPlayer, decoded yourself, or decoded at build time by cna-content (.mp3/.wma need its optional media pipeline). Load<SoundEffect> is never cached, so each call decodes a fresh instance. Implemented
Song .mp3 .ogg .wav .flac .opus .aac .wma Streamed music through the selected audio platform (see Audio System). Only one Song plays at a time through MediaPlayer. A .cnb song carries metadata plus a streaming reference, never the audio itself; cna-content deploys the media file beside the .cnb. Implemented
Video MP4, OGV, WEBM, MKV, AVI, MOV Played through VideoPlayer. Video, VideoPlayer and the XNB VideoReader exist in every build; decoding needs the optional FFmpeg backend (CNA_ENABLE_VIDEO=AUTO|ON|OFF), which is never built on Windows, Emscripten, Android or iOS. Without a backend the types load their metadata and playback throws NotSupportedException instead of failing to link. See Video Playback. Needs FFmpeg

JSON descriptor formats

Alongside the compiled .xnb and .cnb readers, CNA supports a descriptor for the loose-file path. The types that use one all share CNA's single .cnj document format — a plain JSON file that lives alongside your assets, carries a cnjVersion and a type that must match the C++ type you ask for, and references the actual data files by name. Loading a .cnj needs no build step; when you do want a compiled asset, cna_tool_cnj_to_cnb and cna-content take a .cnj as input and write a .cnb.

Only some content types have a descriptor. Texture2D and SoundEffect do not: the image or WAV file is read directly, and everything you might expect a descriptor to configure is set on the object in C++ instead. There is no .texture.json and no .sound.json. Sampling and wrapping are device state you set at draw time, and volume, pitch and pan are arguments to Play.

.cnj — SpriteFont

Describes a pre-rendered glyph atlas. texture names the atlas and is required — a descriptor without it raises a ContentLoadException. That name goes back through ContentManager, so the atlas is resolved and cached like any other texture. Each entry in the glyphs array maps a code point to its position and metrics within the atlas.

{
  "cnjVersion": 1,
  "type": "SpriteFont",
  "texture": "fonts/arial32_atlas",
  "lineSpacing": 36,
  "spacing": 0.0,
  "defaultCharacter": "?",
  "glyphs": [
    { "char": 32, "source": [0, 0, 6, 30],   "crop": [0, 0, 6, 30],  "kerning": [0.0, 6.0, 0.0] },
    { "char": 65, "source": [10, 0, 22, 30], "crop": [0, 0, 22, 30], "kerning": [0.0, 23.0, 0.0] }
  ]
}

Field reference for each glyph entry:

FieldTypeDescription
charintThe numeric code point this glyph represents.
source[x, y, w, h]The glyph's rectangle inside the atlas.
crop[x, y, w, h]The offset and size used when drawing.
kerning[float, float, float]XNA's three floats: left bearing, advance width, right bearing.

See Tutorial 09: SpriteFont for a worked example. To produce a font without hand-writing glyph rectangles, cna-content builds a .spritefont description into a SpriteFont .cnb (or .xnb) when CNA was configured with FreeType (CNA_ENABLE_FONT_PIPELINE); see Content Pipeline.

.cnj — Model

A model reaches Load<Model> by one of several routes. The offline gltf_to_cnj tool (tools/gltf_to_cnj/, CMake target cna_tool_gltf_to_cnj) converts glTF 2.0 into a .cnj descriptor plus binary vertex/index sidecars, carrying the Model and its AnimationClip data. .gltf and .glb files can also be loaded directly at run time; cna-content and cna_tool_gltf_to_cnb compile glTF (and XNA .x/.fbx sources) into a .cnb or .xnb; and models built by an XNA-compatible pipeline read straight from .xnb. See Model Loading and Tutorial 35.

.cnj — Effect / ShaderEffect

The Effect descriptor has exactly two shader fields, vertex and fragment, each naming a GLSL source file relative to the content root (not to the descriptor's own folder). Missing either raises a ContentLoadException. There is no uniform declaration list — ShaderEffect has no Parameters collection; uniforms are set by name with SetUniformMat4/Vec4/Vec3/Vec2/Float/Int/FloatArray. Load it as std::shared_ptr<Effect> — the type the reader is registered for — then downcast to ShaderEffect.

{
  "cnjVersion": 1,
  "type": "Effect",
  "vertex": "my_effect.vert",
  "fragment": "my_effect.frag"
}

See Tutorial 52: Writing Custom Shaders.

Custom content readers

You can extend ContentManager to load your own types, and CNA offers three mechanisms depending on what the data on disk is. The loose-file mechanism below is the simplest and the one most games start with; the XNB and CNB mechanisms let a custom type travel inside a compiled container.

Data on diskMechanismRegisteredWhere to learn it
Your own file format (JSON, CSV, binary…)LooseFileContentTypeReader<T>: GetExtensions() and T Read(path, ContentManager&)Per ContentManager instance, with RegisterTypeReader<T>(std::make_unique<...>())This page; Tutorial 46
Several .cnj "type" values producing one C++ typeRegisterCnjLoader<T>(typeName, fn)Per instanceTutorial 46 tips
A custom type inside an .xnbContentTypeReader<T> registered with ContentTypeReaderManager::AddTypeCreator(canonicalName, factory), or a declared-field reader from ReflectiveTypeReaderBuilder<T>; EnumTypeReader<TEnum> for enumsProcess-wideTutorial 148; XNB Loading & Interoperability
A custom type inside a .cnbContentManager::RegisterCnbLoaderEXT<T>(id, canonicalName, factory) with an id from CnbAssetTypeIdFromName()Process-wideTutorial 148; CNB Format

1 — Implement the reader

The base class for loose files is LooseFileContentTypeReader<T> (it was renamed in July 2026 to free the name ContentTypeReader<T> for the binary XNB reader, which has a different shape). It is handed a full path and opens the file itself.

#include "Microsoft/Xna/Framework/Content/ContentManager.hpp"
#include "Microsoft/Xna/Framework/Content/LooseFileContentTypeReader.hpp"

struct LevelData {
    std::string name;
    int width = 0, height = 0;
    std::vector<int> tiles;
};

class LevelDataReader
    : public Microsoft::Xna::Framework::Content::LooseFileContentTypeReader<LevelData> {
public:
    // Lets Load<LevelData>("levels/level01") find levels/level01.level.json
    [[nodiscard]] std::vector<std::string> GetExtensions() const override {
        return {".level.json"};
    }

    LevelData Read(const std::string& path,
                   Microsoft::Xna::Framework::Content::ContentManager& /*cm*/) override {
        // Parse your custom format however you like
        LevelData level;
        // ... open `path`, fill level fields ...
        return level;
    }
};

2 — Register the reader

// Register on the manager that will load it, before the first Load<LevelData>() call
getContentProperty().RegisterTypeReader<LevelData>(std::make_unique<LevelDataReader>());

3 — Load as usual

LevelData level = getContentProperty().Load<LevelData>("levels/level01");

Loose readers are registered per ContentManager instance: if you keep one manager per level or screen, register your readers on each. Registering a reader for a type that already has one — including a built-in type such as Texture2D — silently replaces it on that instance, so use that deliberately. Because Load<T> returns by value, T must be copyable (the cache stores it), and a reader that throws is reported to the caller as ContentLoadException.

ⓘ

Custom types are no longer loose-file only. At alpha.1 this page said custom types could not be read from .xnb at all. In this snapshot a game can register an XNB reader for its own type — either a hand-written ContentTypeReader<T> added with ContentTypeReaderManager::AddTypeCreator(), or one declared field by field with ReflectiveTypeReaderBuilder<T> (there is still no run-time reflection: you list the fields once). EnumTypeReader<TEnum> covers enum-typed fields. A loose reader is not consulted for an .xnb or .cnb: those tiers run first and dispatch on the file’s own type information. See Known gaps for what remains unsupported, and Tutorial 148 for a worked example.

Code examples

Example 1 — Loading Texture2D and SoundEffect in LoadContent()

void MyGame::LoadContent() {
    auto& content = getContentProperty();

    // Texture: loads Content/textures/player.xnb, .cnb, or .png, in that order of preference
    Texture2D playerTex = content.Load<Texture2D>("textures/player");

    // Sound effect: loads Content/audio/jump.wav (or a built .cnb/.xnb)
    SoundEffect jumpSound = content.Load<SoundEffect>("audio/jump");

    // Song for background music: loads Content/music/theme.ogg
    Song bgMusic = content.Load<Song>("music/theme");
    MediaPlayer::Play(&bgMusic);
    MediaPlayer::setIsRepeatingProperty(true);
}

Example 2 — Loading a Model

// Resolves Content/models/house.xnb, then house.cnb, then a literal name,
// then house.cnj (gltf_to_cnj), house.gltf or house.glb
Model houseModel = getContentProperty().Load<Model>("models/house");

// Draw all meshes with their associated BasicEffect
houseModel.Draw(worldMatrix, camera.View(), camera.Projection());

Example 3 — Custom loose-file reader (full round-trip)

#include "Microsoft/Xna/Framework/Content/ContentManager.hpp"
#include "Microsoft/Xna/Framework/Content/LooseFileContentTypeReader.hpp"
#include <fstream>
#include <nlohmann/json.hpp>

struct TileMap { int width = 0, height = 0; std::vector<int> tiles; };

class TileMapReader
    : public Microsoft::Xna::Framework::Content::LooseFileContentTypeReader<TileMap> {
public:
    [[nodiscard]] std::vector<std::string> GetExtensions() const override {
        return {".tilemap.json"};
    }
    TileMap Read(const std::string& path,
                 Microsoft::Xna::Framework::Content::ContentManager&) override {
        std::ifstream f(path);
        auto j = nlohmann::json::parse(f);
        TileMap m;
        m.width  = j["width"];
        m.height = j["height"];
        m.tiles  = j["tiles"].get<std::vector<int>>();
        return m;
    }
};

// Registration: call once per ContentManager, e.g. at the top of LoadContent()
getContentProperty().RegisterTypeReader<TileMap>(std::make_unique<TileMapReader>());

// Usage in LoadContent(); the extension is optional because GetExtensions() names it
TileMap map = getContentProperty().Load<TileMap>("maps/world1");

Example 4 — Example SpriteFont .cnj (full file)

{
  "cnjVersion": 1,
  "type": "SpriteFont",
  "texture": "fonts/ui_16_atlas",
  "lineSpacing": 20,
  "spacing": 0.0,
  "defaultCharacter": "?",
  "glyphs": [
    { "char": 32, "source": [0, 0, 0, 0],   "crop": [0, 0, 0, 0],   "kerning": [0.0, 5.0, 0.0]  },
    { "char": 33, "source": [0, 0, 4, 14],  "crop": [0, 3, 4, 14],  "kerning": [0.0, 5.0, 0.0]  },
    { "char": 65, "source": [4, 0, 12, 14], "crop": [0, 3, 12, 14], "kerning": [0.0, 13.0, 0.0] },
    { "char": 66, "source": [16, 0, 11, 14],"crop": [1, 3, 11, 14], "kerning": [1.0, 12.0, 0.0] },
    { "char": 97, "source": [27, 0, 10, 11],"crop": [0, 6, 10, 11], "kerning": [0.0, 11.0, 0.0] },
    { "char": 98, "source": [37, 0, 10, 14],"crop": [1, 3, 10, 14], "kerning": [1.0, 11.0, 0.0] },
    { "char": 48, "source": [47, 0, 11, 14],"crop": [0, 3, 11, 14], "kerning": [0.0, 12.0, 0.0] }
  ]
}

ServiceProvider note

In XNA 4.0, ContentManager accepted an IServiceProvider so that content readers could resolve services (graphics device, audio engine, etc.) at load time. CNA retains this constructor signature for source compatibility, but the service locator implementation is simplified. Built-in readers take the GraphicsDevice from setGraphicsDevice() (which Game calls for its own manager) and fall back to an IGraphicsDeviceService lookup in the service provider only when none was set. Game::Services is available and accepts user-registered services, but not all XNA services (IGraphicsDeviceService, IGraphicsDeviceManager) are automatically pre-populated.