Content architecture
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. The resolution order, cache key and disposal behavior were read from ContentManager.hpp and ContentManager.cpp and the module CMake files; the build-time split is also stated in CNA's own pipeline documentation. The named tests exist but were not executed for this page.
Build-time import, process and write, and run-time load and cache, are separate systems in CNA. A content-pipeline tool must not need a game window, a graphics device or an audio device merely to convert an asset, and a shipped game must not carry the authoring dependencies (FreeType, FFmpeg importers, FBX readers, an effect compiler) that only a build needs. This page states where that boundary sits in the modules, how ContentManager resolves a request, what its cache keys and ownership mean, and how to add or change an asset type without breaking either half.
Build-time pipeline
source asset (.png, .gltf/.glb, .wav, .cnj, .spritefont, .fbx, ...)
└─ importer → canonical content value → processor → writer
├─ CNB (CNA's own format, the default)
└─ XNB (the XNA 4.0 container, selected per build)
▼
runtime file in the Content tree
Importers decode source formats and answer “what is in this source?”; processors produce runtime-oriented data and answer “how should this become runtime content?”; writers serialize. The importer, the processor and the canonical value are shared by both output formats and only the serializer differs, so a route that works for CNB is not automatically covered for XNB: XNB writers are a separate, format-keyed registry. This keeps authoring dependencies out of runtime loading. CNA's own pipeline documentation states that no pipeline component constructs a GraphicsDevice, opens an audio device, creates a window or initializes a renderer.
The conversion code is split across three build targets, and only the first ships in a game:
modules/content(cna_content) holds what a running game needs to load content and also the canonical pipeline engine:src/Pipeline(build configuration, manifest, the per-type pipelines), the CNB codecs and writer (src/Cnb), the XNB readers and writers (src/Xnb) and the glTF importer.modules/content-pipeline(cna_content_pipeline, aliasCNA::ContentPipelineBuild) holds the build-time-only components: the FreeType-backed.spritefontroute, the FFmpeg-based MP3, WMA and WMV importers, the FBX and.xreaders, the external effect-compiler service and the XNAContent.Pipelinefacade. Their probes are three-state switches (CNA_ENABLE_FONT_PIPELINE,CNA_ENABLE_MEDIA_PIPELINE) or optional (zlib for binary FBX arrays).cna_content_compiler(CNA::ContentCompiler, defined inToolContentPipeline.cmake) links both and implements the command-line coordinator: source discovery, cache and configuration, atomic publication. Thecna-contentexecutable is a smallmainover that library, andcna_add_content(...)in the same file wires a content build into a consumer's CMake.
Only cna_content_compiler links cna_content_pipeline, so the boundary is enforced by the dependency graph rather than by convention (see the module graph). The format and workflow references are Content Pipeline, CNB format and XNB and the content pipeline.
Runtime resolution order
ContentManager::Load<T>(name) resolves a request in this order:
- The cache, keyed by the requested C++ type and the normalized name.
- A compiled
.xnb(<root>/<name>.xnb). This tier needs no per-type reader registered on the manager: root-object dispatch is driven by the file's own type-reader table through the process-wideContentTypeReaderManager. A genuine external.xnbis never shadowed by a CNA convenience file. - A compiled
.cnb. It ranks just below.xnband above everything it could have been compiled from. A.cnbis self-describing: its header's asset-type identifier selects the loader through the process-wideCnbLoaderRegistry, again with no per-type reader on the manager. A caller who spells outFoo.cnbis handled too. A malformed.cnbis a hard error and does not fall through. - Loose-file readers, registered per
TasLooseFileContentTypeReader<T>(everyContentManagerconstructor registers the built-ins: 2D, cube and 3D textures,SoundEffect, effects,SpriteFont, models, skinned models, animation clips, curves, songs and videos). For such a reader the literal path wins if it exists, then<name>.cnj, then each extension the reader declares, then the bare path. With no reader forTthe load throwsContentLoadException.
Extension fallback and root-directory normalization are observable behavior. Backslashes are converted to slashes in the root and the asset name, and paths are resolved case-insensitively component by component (on Android, relative paths go to the platform's packaged-asset namespace). The precedence is pinned by CnbContentManagerTest.ResolutionOrderIsXnbThenCnbThenLiteralThenCnjThenNative, ContentManagerXnbTest.XnbWinsOverCnjAndNativeExtensionForTheSameName and CnbContentManagerTest.ACurveCnbOutranksASameNamedCurveCnj. Load<SoundEffect> is an explicit specialization because the type is move-only; it repeats the same tier order by hand, which is why a tier once went missing for that one type.
Built-in XNB readers are registered process-wide by RegisterAllBuiltInXnbReaders(), which Game.cpp calls in the Game constructor: a bare ContentManager stays neutral for isolated use, while a real Game is ready to load built-in types before Initialize runs (GameTest.ConstructionRegistersBuiltInXnbReadersBeforeLoadContent).
Cache and ownership
The cache key combines the requested C++ type with the normalized asset name (lower-cased, slash-separated). The same textual asset requested as two types therefore does not alias by name alone: the second request goes back down the ladder and, if that type's reader rejects the file, throws a ContentLoadException instead of an unrelated bad_any_cast (CnjAssetCacheTypeSafetyTest.DifferentTypeSameNameThrowsContentLoadExceptionNotBadAnyCast; a repeat of the same type and name returns the cached instance). SoundEffect is outside the generic cache: each Load<SoundEffect> returns an independently owned instance.
ContentManager::Unload() clears the owned cache and nothing else; Dispose(true) calls it, and Dispose(false) also drops the cache without going through Unload(). Because a returned value can share the underlying resource, code that retains objects after unload must follow that object type's ownership semantics rather than assume the cache keeps it alive or that unloading disposed it: ContentManagerTexture2DXnbTest.UnloadClearsTheTextureCache asserts that a load after Unload() produces a different renderer texture even while the caller keeps its first copy. Content readers create graphics resources, so review content lifetime together with device teardown (content cache ownership).
Adding or changing an asset type
- Decide which piece owns the change: a loose run-time reader (
RegisterTypeReader<T>, orRegisterCnjLoader<T>for a game-defined.cnjtype), a CNB loader (a game extension registers a custom identifier throughContentManager::RegisterCnbLoaderEXT; the built-in identifiers belong to CNA and are unreachable by a caller), an XNB reader (the type-reader registry), or a pipeline conversion (importer, processor, writer). - Keep the importer free of run-time device creation, and keep a new authoring dependency in
content-pipelinerather than wideningcna_content. - Register the reader or loader at the intended scope (registrations are process-wide for XNB and CNB, per manager for loose readers) and test duplicate and unknown identifiers:
CnbContentManagerTest.RegisteringTwoTypesUnderOneIdentifierIsRefused,CnbContentManagerTest.AGameExtensionCannotClaimABuiltInOrReservedIdentifierandContentTypeReaderManagerTest.RepeatRegistrationOfSameNameIsIgnoredNotReplacedshow the existing shape. - Test cache identity, malformed or truncated input (
CnbContentManagerTest.AMalformedCnbIsAHardErrorAndDoesNotFallThroughToTheLowerTiers) and unload. - If the object creates GPU or audio resources, test device and service destruction order.
- If a writer changed, follow the change into the matching run-time reader: the format contract and cache disposal differ from ordinary graphics allocation.
The step-by-step recipes are modify ContentManager and modify the Content Pipeline; the source tours are content runtime internals and content pipeline internals.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- CNJ documents: envelope, sourceFile and sidecar rules — The rules a .cnj document must satisfy in CNA: envelope validation, which reader accepts which type, how sourceFile and sidecar paths resolve, which parser reads which field, and what CNJ does not bound.
- Content input boundaries and hostile-input behaviour — Where CNA enforces size, count, nesting and path limits across XNB, CNB, CNJ and the build importer, how the loaders are fuzzed, which exceptions hostile files produce, and which gaps remain.
- ContentManager resolution, caching and failure rules — Which file ContentManager::Load<T> actually reads, what RootDirectory does and does not confine, what the cache keeps, and which exception each tier throws at this snapshot.
- Curve evaluation: keys, tangents, loop types and the XNA reference — How CNA's Curve evaluates: sorted keys, the per-segment Hermite basis, Step continuity, the five loop types, smooth tangents and degenerate curves, each compared with the XNA 4.0 algorithm.
- glTF feature matrix: importer, runtime and evidence — Every glTF 2.0 feature area with what CNA's import core does, what the runtime Model represents and which committed tests and layers provide evidence, at this snapshot.
- Model, ModelMesh and ModelMeshPart: the runtime graph and its draw contract — What Model::Draw and ModelMesh::Draw do, the invariants a loaded graph supplies, what copies share, the five model content routes and the collection rules, at this snapshot.
- Skinning, animation and morph targets on a Model — SkinningData and AnimationPlayer semantics, the three glTF skin index spaces, the D8 root prefix, clip resampling, rigid scene-node clips and CPU morph blending, at this snapshot.
- The CNJ model toolchain: gltf_to_cnj, the Model envelope and sidecars — What cna_tool_gltf_to_cnj writes, the per-type version-2 Model envelope, which descriptor and sidecar rules the .cnj reader enforces or trusts, route parity and the dual-texture occlusion remap.
- The glTF import core: parser boundary, scene graph and extraction — How CNA's shared glTF import core parses, validates, flattens the scene, groups meshes, extracts materials and reports losses, and what its runtime and offline front ends share.
- The XNB container and the ContentReader object graph — Byte-level and session-level rules for reading .xnb files in CNA: header, compression, the type-reader table, one-based object indices, shared resources, limits and custom-reader rules.
- XNB type readers: wire contracts and validation — What each built-in XNB type reader reads, validates and refuses at this snapshot, from primitives and textures to SpriteFont, Model, effects and audio, with the evidence behind each.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-021: ContentManager::Unload() only clears the cache; it disposes none of the assets it loaded — Unload() is loadedAssets_.clear(): no IDisposable is tracked or disposed, so assets a game still references stay alive and undisposed, where XNA's Unload disposes everything the manager loaded.
- CNA-BUG-119: The XNB type-reader registry is process-wide and unsynchronised, and every reader construction writes to it — Every ContentTypeReader constructor writes the unlocked process-wide TargetTypeNames map during each .xnb load, so two XNB loads on different threads race unless a GL renderer's context lease happens to serialise them; X
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- ContentManager: the two asset paths · ContentManager: caching and lifetime · Content Pipeline · CNB format · XNB: how ContentManager resolves an asset
- Architecture
- Architecture overview · Runtime lifecycle · Physical module dependency map
- Maintainer workflow
- I need to modify ContentManager · I need to modify the Content Pipeline · Ownership: content cache
- Tests and validation
- Test architecture
- Reference
- Module index