Overview

ShadowDusk is a self-contained, in-memory HLSL shader compiler for MonoGame, KNI, and FNA. It compiles .fx shaders into the format each runtime loads (.mgfx for MonoGame/KNI, the D3D9 .fxb for FNA) on Linux, macOS, or Windows. Nothing extra to install: no Wine, no Windows SDK, no fxc.exe, no separate toolchain. (Classic Microsoft XNA 4.0 is out of scope.)

The problem it solves

MonoGame's stock content pipeline (MGCB) compiles .fx with the same effect compiler mgfxc wraps, which depends on fxc.exe from the DirectX SDK and therefore only runs on Windows. That makes shader compilation a Windows-only build step: it cannot run on Linux or macOS, and it cannot run at runtime or in a browser at all. (FNA's equivalent path leans on fxc and has the same Windows-only constraint.)

ShadowDusk replaces that step with one portable, faithful pipeline whose output a real MonoGame, KNI, or FNA Effect loads and renders like the reference compiler's (mgfxc for MonoGame/KNI, fxc for FNA).

What success means — two axes, both required

  1. Reach mgfxc can't. Compile .fx where MonoGame's own toolchain cannot: on Linux/macOS (no Wine, no Windows SDK) and at runtime / in-browser via WASM. Matching mgfxc only on Windows-at-build-time would be pointless — the reach is the reason to exist.
  2. Output the reference compiler would. The compiled effect, loaded into the real runtime, renders the same image as the reference-compiled version — zero code or content-pipeline changes. For MonoGame/KNI the reference is mgfxc (the .mgfx container); for FNA it is fxc /T fx_2_0 (the D3D9 .fxb).

"Same .mgfx as mgfxc" means behaviorally equivalent and Effect-loadable — the same pixels in the real runtime. Byte-identity is only ShadowDusk's own reproducibility (same compiler version + source + target → same bytes); it is never byte-equality with mgfxc (they are different compilers).

The product and its delivery shapes

Shape Package / Tool Use
Library (the product) ShadowDusk.Compiler — EffectCompiler : IShaderCompiler Add the package, call CompileAsync(fx), get .mgfx bytes in-memory.
CLI tool ShadowDusk.Cli — dotnet tool named ShadowDuskCLI The same library for build-time use from build scripts, CI steps, or a terminal (see Drop-in mgfxc).
MGCB plugin ShadowDusk.MgcbPlugin — ShadowDuskEffectImporter / ShadowDuskEffectProcessor The same library as a MonoGame Content Builder content processor. /reference: it in your .mgcb and MGCB compiles .fx → .xnb through ShadowDusk in its own process — the native MGCB route, since MGCB compiles in-process and cannot be redirected by a PATH override (see MGCB Content Pipeline).
Content Builder library ShadowDusk.ContentPipeline — the same ShadowDuskEffectImporter / ShadowDuskEffectProcessor as a normal library For MonoGame 3.8.5's code-centric Content Builder project (the template default from 3.8.5): add the package to the Builder and pass new ShadowDuskEffectImporter(), new ShadowDuskEffectProcessor() in GetContentCollection. Proven at rung 4 in a real 3.8.5 ContentBuilder (see MonoGame 3.8.5 Content Builder).
Direct .xnb output CompiledShader.ToXnb() on the library, or an .xnb output path on the CLI ShadowDusk writes the content-pipeline .xnb itself, so Content.Load<Effect>("Foo") keeps working with no consumer code change and MGCB out of the picture entirely. The XNB platform byte is derived from the target — nothing to select — and the payload inside is byte-for-byte the .mgfx the same call emits. Proven with a real ContentManager loading and rendering pixel-identically to the reference build on MonoGame (WindowsDX and DesktopGL), KNI (4.2.9001 and 4.3.9001) and FNA. Name the profile (/Profile:OpenGL for a GL game): the CLI default is DirectX_11.
WASM library ShadowDusk.Wasm — WasmShaderCompiler : IShaderCompiler The same pipeline inside .NET WASM for in-browser runtime compilation.
SkiaSharp / SkSL converter ShadowDusk.Compiler.Sksl.SkslConverter Converts an .fx pixel shader to an SkSL runtime effect for SKRuntimeEffect, so the same shader source can also serve a SkiaSharp render path. Fragment-only, coordinate-driven effects with uniform inputs (post-process, tint, gradient, SDF); a shader that reads an interpolated varying is refused by name rather than silently narrowed.

Every shape implements the same IShaderCompiler interface and runs the same faithful pipeline — no substitute compilers. The in-browser ShaderFiddle.Web is a sample of the WASM reach, not a separate product.

Slang as an input. Alongside .fx and .glsl, the .fx pipeline also accepts .slang source (the HLSL-compatible subset of Slang): a pure managed text transform (ShadowDusk.Compiler.Slang.SlangFrontend) converts it to .fx text — entry points from Slang's [shader("vertex")]/[shader("fragment")] attributes, a synthesized technique — before the same faithful pipeline compiles it. No Slang toolchain is shipped or invoked anywhere. There is no mgfxc oracle for Slang input (mgfxc cannot read Slang at all), so no route through it is mgfxc-equivalent.

Supported backends

Backend Output Status
OpenGL / DesktopGL GLSL .mgfx Supported
DirectX 11 DXBC .mgfx Supported
WebGL (KNI browser) GLSL .mgfx Supported
Android (on-device) GLSL .mgfx Supported (byte-identical to the desktop build)
FNA D3D9 .fxb Supported
Metal (macOS / iOS) MSL Not yet
Vulkan SPIR-V .mgfx Supported (MonoGame DesktopVK only — KNI has no Vulkan platform)
DirectX 12 (MonoGame WindowsDX12) DXIL .mgfx Supported (MonoGame WindowsDX12 only — KNI has no DirectX 12 platform)
Slang input, HLSL-compatible subset (.slang, ShadowDusk.Compiler, free) Same .mgfx/.fxb as the target backend Compile-proven (17-shader corpus cross-validated against the real slangc; no mgfxc oracle for Slang input)
Slang input, full language (.slang, ShadowDusk.Slang, opt-in) Same .mgfx/.fxb as the target backend Render-proven on OpenGL + DirectX_11 (21-shader corpus, 4-target compile sweep, procedural pixel-diff vs slangc's own HLSL, real DirectX_11 Effect load; no mgfxc oracle for Slang input)
SkSL output (SkiaSharp) SkSL text (SKRuntimeEffect) Image-fidelity proven vs the original HLSL's math (no mgfxc-equivalence claim; Skia has no reference compiler)

Supported targets are tested end-to-end against the reference compiler and render identically (on-device Android via byte-identity: its output is byte-identical to the desktop build, whose renders are proven — the on-device pixel diff is a tracked follow-up). See Validation for how that's proven, and Choosing a Target to pick one.

Output format. The default is MGFX v10, which loads on MonoGame 3.8.1.263 (the measured floor) and every newer MonoGame, plus KNI — you never set a flag for correct output. Targeting a newer runtime? MgfxVersion = 11 (MonoGame 3.8.5+) and Container = EffectContainer.Knifx (KNI v4.02+) are optional and load and render just like v10. See Parameters & Caveats.

Cross-platform. Every target compiles on Windows, macOS, and Linux, and OpenGL, DirectX 11, and FNA produce the same bytes on every OS — ShadowDusk bundles its own native pieces, and the full test suite runs green on all three in CI.

Two carve-outs. Build DirectX 12 content on Windows: DXIL validation and signing run through the Windows-only dxil.dll, so a non-Windows DX12 compile still succeeds but emits unsigned DXIL (warned as SD0214) that retail D3D12 rejects at pipeline-state creation — see DirectX 12. And a small family of advanced-texture intrinsics (tex3D, tex2Dlod, tex2Dgrad) currently fails to compile on the Linux/macOS DXC builds — see Parameters & Caveats.

Next steps