MGCB Content Pipeline
ShadowDusk plugs into the MonoGame Content Builder as a content-processor plugin. Add the
ShadowDusk.MgcbPlugin package, point one /reference: line at it, and MGCB compiles your .fx
to .xnb through ShadowDusk, inside its own process — no mgfxc, no fxc.exe, no Wine, no
Windows SDK, on Linux, macOS, and Windows alike.
Important
Do not try to route MGCB through a PATH override. This page used to document one, and it
does not work. Measured on 2026-07-28 against dotnet mgcb 3.8.2.1105, 3.8.4.1, and 3.8.5:
with a real mgfxc executable placed first on PATH, a .mgcb content build never invoked it
once and still produced a valid .xnb. MGCB compiles .fx in-process — there is nothing
for a PATH alias to intercept. MonoGame 3.8.5's code-centric Content Builder is a C# project
over MonoGame.Framework.Content.Pipeline and has no external-tool seam either. The plugin below
is the real integration.
Setup
dotnet add package ShadowDusk.MgcbPlugin
It is a build-time (DevelopmentDependency) package: nothing from it reaches your shipped game
assembly.
Then in your .mgcb, add the reference once and select the ShadowDusk importer and processor on
each effect:
/reference:$(NuGetPackageRoot)shadowdusk.mgcbplugin/<version>/tools/net8.0/any/ShadowDusk.MgcbPlugin.dll
#begin MyEffect.fx
/importer:ShadowDuskEffectImporter
/processor:ShadowDuskEffectProcessor
/build:MyEffect.fx
MGCB does not expand MSBuild properties inside a
.mgcb, so spell the path out (or copy the package'stools/net8.0/anydirectory somewhere stable in your repo and point at that). Everything the plugin needs — the ShadowDusk assemblies and the pinned DXC, SPIRV-Cross, and vkd3d-shader natives for every RID — lives in that one directory, because MGCB resolves a referenced plugin's dependencies from the plugin's own folder.
That is the whole setup. The target comes from the content project's own /platform: line, 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 to get correct output.
Platform → target mapping
.mgcb /platform: |
ShadowDusk target |
|---|---|
Windows |
DirectX 11 (DXBC SM5) |
DesktopGL, MacOSX, iOS, Android, RaspberryPi, Web, NativeClient |
OpenGL (GLSL) |
PlayStation4, PlayStation5, XboxOne, Switch, Xbox360, Stadia |
not supported — the build fails loudly with SD0501 rather than emitting an artifact those runtimes cannot load |
MonoGame's WindowsDX12 and DesktopVK runtimes have no member in MGCB's platform list at all, so
they are reached through the ShaderProfile processor parameter below.
Processor parameters
Every one is optional. The defaults are the correct path — a parameter here is an escape hatch, never a step you must take to get working output.
/processorParam: |
Default | What it does |
|---|---|---|
DebugMode |
Auto |
Auto follows the content build configuration, exactly like MonoGame's stock EffectProcessor. Debug / Optimize force it. |
Defines |
(empty) | Preprocessor macros in mgfxc's /Defines: spelling: NAME=VALUE entries separated by ; or ,; a bare NAME defines it as 1. Same property name and format as the stock processor, so an existing /processorParam:Defines=… carries over unchanged. |
IncludeDirs |
(empty) | Extra #include search directories, ;-separated. The including file's own directory is always searched first and needs no entry. Equivalent to the CLI's /I. |
ShaderProfile |
(empty) | Overrides the target derived from /platform:. DirectX_11, DirectX_12, OpenGL, Vulkan — the only way to reach DirectX 12 and Vulkan, which MGCB's platform list cannot name. Build DX12 content on Windows: DXIL signing needs the Windows-only dxil.dll, or the output is unsigned and retail D3D12 rejects it (SD0214). |
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 surface through MGCB in the file(line,col-col): error CODE: message form
fxc/mgfxc use and MSBuild and IDEs parse, with the underlying compiler's own words verbatim
beneath — the same text the ShadowDusk CLI prints, from the same formatter. The
build fails; nothing is silently swallowed.
C:\game\Content\Broken.fx(49,32-32): error X0000: use of undeclared identifier 'notADeclaredThing'
C:\game\Content\Broken.fx:49:32: error: use of undeclared identifier 'notADeclaredThing'
col.rgb = (col.r + col.g + notADeclaredThing) / 3.0f;
^
#included files are registered as build dependencies, so editing an .fxh rebuilds the effects
that use it.
Is it really ShadowDusk?
Yes, and it is checked rather than claimed. The .mgfx bytes inside the .xnb are byte-for-byte
what the ShadowDusk CLI writes for the same source and target — because the plugin is an adapter onto
the same EffectCompiler, adding no compilation logic of its own. Two gates hold that:
MgcbPluginByteIdentityTests drives the processor and compares against the real CLI binary on every
dotnet test, and validation/MgcbPlugin runs an actual dotnet mgcb build and additionally checks
that the .xnb envelope matches MGCB's own stock output while the payload differs from it. Verified
green on dotnet mgcb 3.8.2.1105, 3.8.3, 3.8.4, 3.8.4.1, and 3.8.5.
The other routes (still supported)
The plugin is the native integration for teams who want MGCB in their build, but none of these went away:
0. Skip MGCB entirely and let ShadowDusk write the .xnb itself. If effects are the only reason
MGCB is in your build, you no longer need it there:
ShadowDuskCLI Content/MyEffect.fx Content/MyEffect.xnb /Profile:OpenGL
Drop the file where the pipeline-built one used to go and Content.Load<Effect>("MyEffect") keeps
working unchanged — the container matches what MGCB writes (verified against a real dotnet-mgcb
build, loaded through a real ContentManager, rendered pixel-identically). From the library it is
result.Value.ToXnb(). See Drop-in mgfxc.
1. Compile with the CLI and let MGCB copy the result. Build .fx → .mgfx with the
ShadowDusk CLI as a pre-build step, then /copy: it:
ShadowDuskCLI Content/MyEffect.fx Content/MyEffect.mgfx /Profile:OpenGL
#begin MyEffect.mgfx
/copy:MyEffect.mgfx
2. Skip the content pipeline for shaders entirely — compile at runtime and hand the bytes to
Effect, which is what the library is for:
var result = await new EffectCompiler().CompileAsync(File.ReadAllText("MyEffect.fx"),
new CompilerOptions { Target = PlatformTarget.OpenGL });
var effect = new Effect(GraphicsDevice, result.Value.Data);
The /Profile: names in route 1 are the same names the ShaderProfile processor parameter takes.
A worked sample
The repository's samples/mgcb sample carries two content projects over the
same shader corpus: Content.mgcb (MonoGame's own compiler) and Content.ShadowDusk.mgcb (this
plugin), so you can build both and compare.
Known limits
- Macro-defined techniques on the OpenGL target fail with
SD0010 "Effect source contains no techniques". That is a compiler-library gap, not a plugin one — the same shaders fail identically through the CLI. It affects MonoGame's ownBasicEffect-family stock effects and Penumbra's. - The package is large (~81 MB) because it carries the pinned DXC for every RID. That is what "add the package and point at it" costs; nothing has to be installed separately.