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. .slang input: SkslConverter.ConvertSlang, or SlangCompiler.ConvertToSksl for real slangc.
raylib converter ShadowDusk.Compiler.Raylib.RaylibConverter Converts a single-pass, pixel-only .fx to a glsl330 fragment shader for Raylib-cs (LoadShaderFromMemory(null, fs)), so one post-process source runs on MonoGame and raylib. Maps to raylib's fixed names (fragTexCoord, fragColor, texture0, finalColor); refuses multi-pass, render states, vertex shaders, and other shapes raylib cannot hold (SD0630–SD0636). .slang input: RaylibConverter.ConvertSlang, or SlangCompiler.ConvertToRaylib for real slangc.

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. That free subset route ships and invokes no Slang toolchain. Genuine Slang (import, generics, interfaces) needs the opt-in ShadowDusk.Slang package, which bundles the real slangc and hands its HLSL to the same pipeline. There is no mgfxc oracle for Slang input (mgfxc cannot read Slang at all), so no route through it is mgfxc-equivalent.

Input with no file extension. ShadowDusk.Compiler.ShaderConverter detects the format from the text (ShadowDusk.Core.ShaderFormatDetector) and routes an .fx effect or HLSL-compatible Slang to the MonoGame/KNI/FNA compiler, the SkSL converter, or the raylib converter. Input it does not read fails with the typed ShaderErrorKind.UnsupportedInput (SD0650) without compiling anything, so a caller that chains loaders can try the next one. See the In-Memory Quickstart.

Supported backends

Backend Output Status
OpenGL / DesktopGL (incl. MonoGame 3.8.6 DesktopGL4) 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 against the reference compiler's build of the same assembled .fx on DirectX_11, DirectX_12, Vulkan and FNA (21-shader corpus, real mgfxc 3.8.5 / fxc /T fx_2_0, max delta 0/1/1), and textured shaders on OpenGL (validation/SlangTexturedGl). Runs on win-x64, linux-x64 (Ubuntu 22.04+), osx-x64 and osx-arm64 (macOS 26+). In the browser the same slangc runs in-process as WebAssembly (ShadowDusk.Slang.Wasm, proven in CI, not published as a package yet); not on Android yet.
SkSL output (SkiaSharp) SkSL text (SKRuntimeEffect) Image-fidelity proven vs the original HLSL's math and vs the same .fx in real KNI (no mgfxc-equivalence claim; Skia has no reference compiler)
raylib output (Raylib-cs, fragment-only) glsl330 fragment shader text (LoadShaderFromMemory(null, fs)) Image-fidelity proven in real Raylib-cs vs the same .fx on real MonoGame OpenGL (15/15 maxd 0, plus a 16th .fx case and 12/12 .slang arms; no mgfxc-equivalence claim; raylib 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.

One carve-out. 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