Use build-time compilation by default; runtime loading only for post-release content
For static game assets, the MonoGame Content Pipeline is the correct choice. Bypass it with Texture2D.FromStream only when assets must be added, replaced, or shipped after the game is built—mods, downloadable content, or user-generated files.
Confirmed behavior
- The pipeline converts source files into platform-specific
.xnb binaries. DesktopGL typically receives DXT-compressed textures, Android receives ETC2, and iOS receives PVRTC. ContentManager.Load<T>() caches these automatically, so repeated requests return the same object without re-decoding.
- Runtime loading requires the original source format (PNG, JPG, etc.) to be present on disk. The texture is decompressed and uploaded to the GPU on the fly, which adds CPU overhead and does not apply platform-specific compression.
Likely explanation and trade-offs
Because .xnb output is platform-specific, the pipeline does not yield a single binary bundle that is optimal on every target; instead it produces a per-platform bundle already compressed for the GPU. Loading raw images at runtime can give you one cross-platform asset folder, but on iOS and Android you sacrifice the memory and bandwidth savings of compressed textures. Large uncompressed textures can increase package size and runtime memory pressure, especially on older mobile devices.
Mixing approaches does not interfere with the ContentManager cache. Pipeline assets behave exactly as they would in a pipeline-only project. Runtime-loaded textures, however, are invisible to ContentManager; if you load the same file twice you will receive two distinct Texture2D instances unless you maintain your own lookup table.
Steps for this case
- Keep all shipped art, audio, and fonts in the Content Pipeline and load them through
ContentManager.
- Restrict runtime loading to external directories (e.g., a
Mods/ folder) using TitleContainer.OpenStream.
- Wrap runtime assets in a
Dictionary<string, Texture2D> so repeated access does not trigger redundant decodes.
- If a specific asset feels slow, measure it with
Stopwatch and compare GPU memory in the platform profiler.
// ContentManager caches; FromStream does not
var a = Content.Load<Texture2D>("hero");
var b = Content.Load<Texture2D>("hero");
Debug.Assert(ReferenceEquals(a, b));
using var s1 = TitleContainer.OpenStream("Mods/hero.png");
var c = Texture2D.FromStream(GraphicsDevice, s1);
using var s2 = TitleContainer.OpenStream("Mods/hero.png");
var d = Texture2D.FromStream(GraphicsDevice, s2);
Debug.Assert(!ReferenceEquals(c, d)); // cache manually if needed
Missing diagnostic detail
Do you need to support user-installed mods or post-release DLC that must load without rebuilding the client? If not, remain exclusively with the Content Pipeline. If yes, isolate runtime loading to those external paths only.
Assumptions: MonoGame 3.8+; Content Pipeline behavior is stable across recent releases. Exact performance deltas depend on asset size and device, so verify on your target hardware.