ContentManager
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:
- Checks the internal cache; returns the existing instance if found.
- Constructs the full file path as
RootDirectory + "/" + assetName. - Looks up the registered
ContentTypeReader<T>for the typeT. - Delegates to
ReadAsset(fullPath)on the reader. - 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:
| Field | Type | Description |
|---|---|---|
char | string (1 char) | The Unicode character this glyph represents. |
x, y | int | Top-left pixel coordinate of the glyph in the atlas. |
width, height | int | Pixel dimensions of the glyph rectangle. |
offsetX, offsetY | int | Draw offset from the pen position to the top-left of the glyph. |
advance | int | Horizontal 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.