ContentManager

Microsoft::Xna::Framework::Content — typed asset loading with JSON descriptors

Implementation status: Partial — core Load<T>() and Unload() are implemented for all built-in types. XNB binary pipeline is not supported. IServiceProvider wiring is simplified; Game::Services is available but not all XNA services are pre-registered.

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 a reference to the loaded asset. Assets are cached internally; repeated calls with the same name return the same object without re-reading the file.

CNA does not use the XNA Content Pipeline binary format (.xnb). Instead, complex asset types such as fonts, models, and shaders are described by small JSON descriptor files. Simple image and audio formats are loaded directly from their native container (PNG, WAV, OGG, etc.) without any intermediate build step.

A ContentManager is constructed with an IServiceProvider& and a rootDirectory string. In a typical Game subclass the instance is accessed through this->Content, which is pre-configured with the game's service provider and a default root of "Content".

// Typical construction (done for you inside Game)
ContentManager content(services, "Content");

// Or construct manually
ContentManager myContent(game.Services, "Assets/Levels/Level1");

Core API

Member Signature Description
Load<T>() T& Load<T>(const std::string& assetName) Loads and caches an asset of type T. Returns a reference to the cached object. Subsequent calls with the same name return the same instance without re-reading the file.
Unload() void Unload() Disposes all cached assets and frees their GPU/audio resources. The manager can be reused after calling Unload().
RootDirectory std::string RootDirectory Read/write property. The base path prepended to every assetName passed to Load<T>().
ServiceProvider IServiceProvider& ServiceProvider Read-only. The service provider supplied at construction time. Used by built-in readers to obtain the GraphicsDevice and audio services.

Load<T> resolution order

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

  1. Checks the internal cache; returns the existing instance if found.
  2. Constructs the full file path as RootDirectory + "/" + assetName.
  3. Looks up the registered ContentTypeReader<T> for the type T.
  4. Delegates to ReadAsset(fullPath) on the reader.
  5. Stores the result in the cache and returns a reference to 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 JSON-described types the asset name must resolve to the .font.json, .model.json, or .shader.json file, with or without the extension.

Built-in content type readers

Type T File format(s) Notes Status
Texture2D .png .jpg .jpeg .bmp .gif .tga .tif .tiff .qoi Decoded via SDL3_image. Textures are cached by weak reference, so GPU memory is released once your last copy goes out of scope. Implemented
TextureCube .dds Real DDS parser for cube maps. DDS is the only accepted container. Implemented
SpriteFont .font.json JSON glyph-atlas descriptor referencing an image atlas file. See format below. Implemented
Model .model.json JSON descriptor plus binary vertex/index sidecars. Can carry an optional skeleton and animation clips, which arrive as SkinningData on the model's Tag. Implemented
SkinnedModelEXT .skinnedmodel.json CNA extension (not an XNA type) for skinned meshes with their own skeleton/clip sidecars. Implemented
Effect / ShaderEffect .shader.json JSON descriptor pointing to GLSL source (EasyGL) or SPIR-V bytecode (Vulkan). See Effects System. Implemented
SoundEffect .wav only Short clips loaded fully into memory via SDL3_mixer. Note the asymmetry: unlike Song, the SoundEffect loader accepts WAV and nothing else. Compressed formats must be routed through Song/MediaPlayer or decoded yourself. Implemented
Song .mp3 .ogg .wav .flac .opus .aac .wma Streamed music via SDL3_mixer. Only one Song plays at a time through MediaPlayer. Implemented
Video MP4, OGV, WEBM, MKV, AVI, MOV Decoded via FFmpeg and played through VideoPlayer. Not available on Emscripten or Android. Desktop only

JSON descriptor formats

CNA replaces the XNA Content Pipeline's compiled .xnb files with lightweight JSON descriptors. These are plain text files that live alongside your assets and reference the actual binary data files (images, meshes, shaders) by path. No offline build step is required.

.font.json — SpriteFont

Describes a pre-rendered glyph atlas. The image field points to the atlas texture (any format supported by Texture2D). Each entry in the glyphs array maps a Unicode character to its position and metrics within the atlas.

{
  "image": "fonts/arial_32.png",
  "lineHeight": 36,
  "glyphs": [
    { "char": "A", "x": 0,   "y": 0,  "width": 22, "height": 30, "offsetX": 0, "offsetY": 2, "advance": 23 },
    { "char": "B", "x": 22,  "y": 0,  "width": 20, "height": 30, "offsetX": 1, "offsetY": 2, "advance": 21 },
    { "char": " ", "x": 0,   "y": 0,  "width": 0,  "height": 0,  "offsetX": 0, "offsetY": 0, "advance": 10 }
  ]
}

Field reference for each glyph entry:

FieldTypeDescription
charstring (1 char)The Unicode character this glyph represents.
x, yintTop-left pixel coordinate of the glyph in the atlas.
width, heightintPixel dimensions of the glyph rectangle.
offsetX, offsetYintDraw offset from the pen position to the top-left of the glyph.
advanceintHorizontal advance after drawing this glyph (pen movement).

.model.json — Model

Describes a 3D model composed of one or more meshes and their materials. The mesh field references an OBJ or other supported mesh file; materials lists texture assignments for each named material group.

{
  "mesh": "models/house.obj",
  "materials": [
    { "name": "wall",  "diffuse": "textures/wall.png"  },
    { "name": "roof",  "diffuse": "textures/roof.png"  },
    { "name": "glass", "diffuse": "textures/glass.png", "transparent": true }
  ]
}

.shader.json — Effect / ShaderEffect

References the vertex and fragment shader source files (GLSL for EasyGL, SPIR-V for Vulkan) and declares the uniform names the effect exposes as EffectParameter objects.

{
  "vertex":   "shaders/my_effect.vert.glsl",
  "fragment": "shaders/my_effect.frag.glsl",
  "uniforms": ["WorldViewProj", "Tint", "DiffuseTexture"]
}

Custom ContentTypeReader<T>

You can extend ContentManager to load any custom type by implementing a ContentTypeReader<T> and registering it. This matches the extensibility model in XNA 4.0, though the registration mechanism is simpler in CNA (no reflection or attribute discovery).

1 — Implement the reader

#include <CNA/Content/ContentTypeReader.hpp>

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

class LevelDataReader : public ContentTypeReader<LevelData> {
public:
    std::unique_ptr<LevelData> ReadAsset(const std::string& assetPath) override {
        // Parse your custom format however you like
        auto level = std::make_unique<LevelData>();
        // ... read assetPath, fill level fields ...
        return level;
    }
};

2 — Register the reader

// Register before the first Load<LevelData>() call
ContentManager::RegisterReader<LevelData>(std::make_shared<LevelDataReader>());

3 — Load as usual

LevelData& level = content.Load<LevelData>("levels/level01.lvl");

Registered readers are global per type; you only need to register once per application lifetime. Built-in readers for the types listed in the table above are pre-registered by CNA and cannot be overridden (attempting to register a reader for a built-in type throws std::runtime_error).

No XNB pipeline: CNA does not parse .xnb binary files produced by the Microsoft XNA Content Pipeline tool. There is no plan to support XNB. Port your assets by replacing .xnb references with the equivalent raw files (images, audio) or JSON descriptors (.font.json, .model.json, .shader.json) as described above.

Code examples

Example 1 — Loading Texture2D and SoundEffect in LoadContent()

void MyGame::LoadContent() {
    // Texture: loads Content/textures/player.png
    Texture2D& playerTex = Content.Load<Texture2D>("textures/player");

    // Sound effect: loads Content/audio/jump.wav
    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::IsRepeating = true;
}

Example 2 — Loading a Model via .model.json

// Content/models/house.model.json must exist on disk
Model& houseModel = Content.Load<Model>("models/house.model");

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

Example 3 — Custom ContentTypeReader<T> (full round-trip)

#include <CNA/Content/ContentTypeReader.hpp>
#include <fstream>
#include <nlohmann/json.hpp>

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

class TileMapReader : public ContentTypeReader<TileMap> {
public:
    std::unique_ptr<TileMap> ReadAsset(const std::string& path) override {
        std::ifstream f(path);
        auto j = nlohmann::json::parse(f);
        auto m = std::make_unique<TileMap>();
        m->width  = j["width"];
        m->height = j["height"];
        m->tiles  = j["tiles"].get<std::vector<int>>();
        return m;
    }
};

// Registration — call once, e.g. in Game constructor or Initialize()
ContentManager::RegisterReader<TileMap>(std::make_shared<TileMapReader>());

// Usage in LoadContent()
TileMap& map = Content.Load<TileMap>("maps/world1.tilemap.json");

Example 4 — Example .font.json (full file)

{
  "image": "fonts/ui_16.png",
  "lineHeight": 20,
  "glyphs": [
    { "char": " ",  "x": 0,   "y": 0,  "width": 0,  "height": 0,  "offsetX": 0, "offsetY": 0,  "advance": 5  },
    { "char": "!",  "x": 0,   "y": 0,  "width": 4,  "height": 14, "offsetX": 0, "offsetY": 3,  "advance": 5  },
    { "char": "A",  "x": 4,   "y": 0,  "width": 12, "height": 14, "offsetX": 0, "offsetY": 3,  "advance": 13 },
    { "char": "B",  "x": 16,  "y": 0,  "width": 11, "height": 14, "offsetX": 1, "offsetY": 3,  "advance": 12 },
    { "char": "a",  "x": 27,  "y": 0,  "width": 10, "height": 11, "offsetX": 0, "offsetY": 6,  "advance": 11 },
    { "char": "b",  "x": 37,  "y": 0,  "width": 10, "height": 14, "offsetX": 1, "offsetY": 3,  "advance": 11 },
    { "char": "0",  "x": 47,  "y": 0,  "width": 11, "height": 14, "offsetX": 0, "offsetY": 3,  "advance": 12 }
  ]
}

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 obtain the GraphicsDevice through a direct reference rather than a service lookup. Game::Services is available and accepts user-registered services, but not all XNA services (IGraphicsDeviceService, IGraphicsDeviceManager) are automatically pre-populated.