MonoGame 3.8.5 Content Builder
MonoGame 3.8.5 made the code-centric Content Builder project the template default: instead of a
.mgcb file, your content is described by ordinary C# you own — a console project referencing
MonoGame.Framework.Content.Pipeline, whose ContentBuilder subclass lists what to build. ShadowDusk
plugs into it as a library: the ShadowDusk.ContentPipeline package gives you
ShadowDuskEffectImporter and ShadowDuskEffectProcessor as types you new up, and your .fx
shaders compile to .xnb through ShadowDusk, inside your Builder's own process — no mgfxc, no
fxc.exe, no Wine, no Windows SDK, on Linux, macOS, and Windows alike.
It is the same importer and processor the MGCB plugin ships, compiled from
the same source files; only the packaging differs. The plugin is a tools-only package (MGCB needs
everything in one directory it can /reference:), which cannot be referenced from C# — so the
Content Builder gets its own, library-shaped package.
MonoGame 3.8.6: use ShadowDusk 0.24.0 or later. MonoGame 3.8.6 removed a constructor (
ContentIdentity(string, string)) that the 0.23.0 importer calls, so with 0.23.0 every.fxfails withMissingMethodException. Until you can update, MonoGame's ownEffectImporterwith the ShadowDusk processor works on 3.8.6. Newer releases are checked against every MonoGame content pipeline from 3.8.2.1105 to 3.8.6.
Setup
In your Content Builder project (the MonoGame.ContentBuilder.CSharp template, short name mgcb):
dotnet add package ShadowDusk.ContentPipeline
Keep the template's own package list as it is. ShadowDusk's assemblies and its pinned DXC,
SPIRV-Cross, and vkd3d-shader natives flow into the Builder's bin/ through normal NuGet asset flow;
nothing has to be installed separately.
Then pass the two instances where you include your effects:
using Microsoft.Xna.Framework.Content.Pipeline;
using MonoGame.Framework.Content.Pipeline.Builder;
using ShadowDusk.ContentPipeline;
var builder = new Builder();
builder.Run(args);
return builder.FailedToBuild > 0 ? -1 : 0;
public class Builder : ContentBuilder
{
public override IContentCollection GetContentCollection()
{
var content = new ContentCollection();
content.Include<WildcardRule>("Effects/*.fx",
new ShadowDuskEffectImporter(), new ShadowDuskEffectProcessor());
content.Include<WildcardRule>("Font/*.spritefont");
content.Include("splash-screen.png");
return content;
}
}
That is the whole setup. The target follows the Builder's platform (-p / $(MonoGamePlatform)
from the game's BuildContent.targets), and the output is the backwards-compatible MGFX v10 container
every MonoGame 3.8.1.263+ and KNI runtime loads. You never pick a version, a format, or a flag.
Important
Pass the instances. content.Include<WildcardRule>("Effects/*.fx") with no importer/processor
lets the Builder auto-discover one by extension, and with both ShadowDusk's and MonoGame's pairs
loaded it picks MonoGame's stock EffectImporter/EffectProcessor — silently, with no way for a
second .fx importer to win (measured against the real 3.8.5 Builder). The explicit instances are the
route.
Platform → target mapping
Builder platform (-p) |
ShadowDusk target |
|---|---|
Windows |
DirectX 11 (DXBC SM5) |
DesktopGL, MacOSX, iOS, Android, RaspberryPi, Web, NativeClient |
OpenGL (GLSL) |
DesktopVK |
Vulkan (SPIR-V) |
WindowsDX12 |
DirectX 12 (DXIL SM6), signed on every host |
PlayStation4, PlayStation5, XboxOne, XboxSeries, Switch, Xbox360, Stadia |
not supported — the asset fails loudly with SD0501 |
The map keys on the platform's name (what -p and $(MonoGamePlatform) carry), so it is correct
under MonoGame 3.8.5's renumbered TargetPlatform even though the package is compiled against the
3.8.2.1105 contract (the floor, so the same assembly also loads into older MGCBs).
Processor properties
Processor parameters are C# properties on the instance, not a dictionary, and they take part in the Builder's per-asset cache key, so changing one rebuilds the asset:
new ShadowDuskEffectProcessor { Defines = "FOO=1;BAR", DebugMode = EffectProcessorDebugMode.Debug }
| Property | Default | What it does |
|---|---|---|
DebugMode |
Auto |
Auto optimizes, as MonoGame's stock EffectProcessor does (on MGCB too, whatever the build configuration); set Debug explicitly for debug info. |
Defines |
(empty) | Preprocessor macros in mgfxc's /Defines: spelling: NAME=VALUE entries separated by ; or ,; a bare NAME defines it as 1. |
IncludeDirs |
(empty) | Extra #include search directories, ;-separated. The including file's own directory is always searched first. #included files are registered as dependencies, so editing an .fxh rebuilds. |
ShaderProfile |
(empty) | Escape hatch: DirectX_11, DirectX_12, OpenGL, Vulkan overrides the platform-derived target. Not needed on 3.8.5, whose DesktopVK / WindowsDX12 platforms already name those targets. |
MgfxVersion |
10 |
11 opts into the newer MGFX container (MonoGame 3.8.5+). |
DxbcBackend |
vkd3d |
d3dcompiler opts into the Windows-only correctness oracle for the DirectX target. |
Diagnostics
Shader errors reach the Builder's logger in the file(line,col-col): error CODE: message form
fxc/mgfxc use and MSBuild's [E]-prefixed error regex picks up, with the underlying compiler's own
words verbatim beneath — the same text the ShadowDusk CLI prints, from the same
formatter. The asset fails; nothing is silently swallowed.
Is it really ShadowDusk, and does it really load?
Checked, not claimed. validation/ContentBuilder runs a real MonoGame 3.8.5 ContentBuilder (and, with
-p:MonoGameVersion=3.8.6, a real 3.8.6 one)
subclass over the fixture set with two content roots — MonoGame's stock pair and ShadowDusk's — and
asserts that the ShadowDusk .xnb payload is byte-for-byte the ShadowDusk CLI's, that the .xnb
envelope is byte-for-byte the stock build's, that the payloads differ, and then loads both through a
real ContentManager.Load<Effect> on MonoGame 3.8.5 and requires pixel-identical renders
(11/11 assets across Windows, DesktopGL, DesktopVK and WindowsDX12, 4/4 renders at
1,230,720 px identical).
On DesktopVK and WindowsDX12 there is one deliberate difference from the CLI's bytes. Their MGFX
v11 container stores a source-file string per shader, and the Builder hands the importer your
effect's absolute path. The CLI's .mgfx output records the path it is given, as mgfxc
does (its .xnb output writes <unknown>, like a content build); the processor
writes <unknown>, exactly what MonoGame's stock EffectProcessor writes, so the .xnb never
carries your build machine's directory and is the same bytes wherever the project is checked out.
Build errors still name the real file, line and column. The gate compares that string with the stock
build's, compares the rest of the payload with the CLI's, and runs the Builder a second time from a
different source directory to require a byte-identical .xnb. pack-consume.yml
additionally consumes the packed package cold from a local feed in a scratch Builder on Linux, macOS,
and Windows. Details in Validation.
The same run is also the proof that ShadowDusk's dependency graph survives the Builder's unguarded
Assembly.GetTypes() scan of every assembly a Builder project references — a scan that used to crash on
one type in Vortice.Direct3D12, which is why that package left ShadowDusk's graph.
Known limits
- Macro-defined techniques on the OpenGL target fail with
SD0010, identically through every route (a compiler-library gap, not a Builder one). It affects MonoGame's ownBasicEffect-family stock effects. - Extension auto-discovery cannot prefer ShadowDusk (see the note above): pass the instances.
- The Builder's
servermode uses the same importer and processor and needs nothing extra, but it is untested here and not claimed.