Extending MonoGame's Content Pipeline with a Custom Texture Processor
Learn how to create a MonoGame ContentProcessor that converts raw PNG data into a runtime‑ready texture atlas, register it in an .mgcb file, and load it safely with Content.Load.
01 Feb 2026, 14:37 UTC

Problem: You need asset data that the default MonoGame importers don’t provide
Suppose your game stores sprite sheets as plain PNG files, but at runtime you want each sheet to be automatically split into individual frames and exposed as a Texture2D[] array. The built‑in TextureProcessor only yields a single Texture2D, so you would have to write extra loading code or duplicate assets. A custom ContentProcessor lets you perform that splitting during the build step, producing an XNB that already contains the array you need.
Thesis: By inheriting from ContentProcessor<TInput, TOutput> and registering the processor in the .mgcb file, you can transform any asset format into a strongly‑typed runtime object without extra boilerplate.
How the pipeline works
When MGCB compiles an asset, it looks for a Processor attribute in the .mgcb entry. If found, it instantiates the specified ContentProcessor, calls its Process method with the raw input data, and serializes the returned object into an XNB file. At runtime, Content.Load<T>(assetName) deserializes that XNB and returns an instance of T. No additional code is required beyond defining the output type.
Worked example: Texture atlas processor
- Define the output type – a simple wrapper that holds an array of textures.
- Create the processor class – inherits from
ContentProcessor<Texture2D, TextureAtlas>. The input is the texture produced by the default importer; the processor slices it into frames. - Register the processor in the .mgcb file – add or edit an entry for your sprite sheet.
- Build the pipeline – run MGCB from the command line or via your IDE.
# From the solution root, assuming the .mgcb file is at Content/content.mgcb mgcb /platform:Windows /outputDir:bin\Windows\Content /intermediateDir:obj\Windows\Content Content/content.mgcbRequired permissions: read access to the source asset and write access to the output directories. No elevated privileges are needed.
- Verify the generated XNB – after the build, inspect the
bin\Windows\Contentfolder forspriteSheet.xnb. Then load it in game code.
public class TextureAtlas
{
public Texture2D[] Frames { get; set; }
}
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework;
[ContentProcessor(DisplayName = "Texture Atlas Processor")]
public class TextureAtlasProcessor : ContentProcessor
{
public override TextureAtlas Process(Texture2DContent input, ContentProcessorContext context)
{
// Assume frames are 64x64 and arranged in a grid.
int frameWidth = 64;
int frameHeight = 64;
int cols = input.Width / frameWidth;
int rows = input.Height / frameHeight;
var frames = new List();
var bitmap = input.GetPixelData();
for (int y = 0; y < rows; y++)
{
for (int x = 0; x < cols; x++)
{
var rect = new Rectangle(x * frameWidth, y * frameHeight, frameWidth, frameHeight);
var frameBitmap = bitmap.Crop(rect);
var texture = new Texture2DContent();
texture.SetPixelData(frameBitmap);
frames.Add(context.Convert(texture, "TextureProcessor"));
}
}
return new TextureAtlas { Frames = frames.ToArray() };
}
}
Place this file in a shared library (e.g., MyGame.ContentPipeline) and reference it from the .mgcb project.
# Example .mgcb snippet
/build: spriteSheet.png
/importer: TextureImporter
/processor: TextureAtlasProcessor
/processorParam: ;
The Processor value must match the DisplayName (or the full type name) of your ContentProcessor subclass.
TextureAtlas atlas = Content.Load<TextureAtlas>("spriteSheet");
// atlas.Frames now contains the individual frame textures.
You can confirm the type at runtime with:
System.Diagnostics.Debug.Assert(atlas.GetType() == typeof(TextureAtlas));
Trade‑offs and limitations
- Build‑time only: The processor cannot read runtime state (e.g., player position) or access services like
GraphicsDevice. All transformation logic must be deterministic and side‑effect free. - Version compatibility: The
ContentProcessor<TInput, TOutput>base class changed between MonoGame 3.8 and 4.x. If you upgrade, you may need to re‑target the pipeline project and update any namespace references. - Increased build time: Custom logic adds to the MGCB pass; complex operations (e.g., large image processing) can lengthen iteration cycles.
Practical way to check the result: after building, run the game and place a breakpoint after the Content.Load call. Verify that atlas.Frames.Length matches the expected frame count and that each frame renders correctly.
Actionable closing
If you find yourself writing repetitive loading code to reshape assets, consider moving that logic into a custom ContentProcessor. Start with a simple test asset, register the processor, and verify the XNB loads as your expected type. Keep the processor pure and side‑effect free, and remember to revisit it when you upgrade MonoGame versions. This approach gives you strongly‑typed assets at runtime while keeping your game code clean.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.