Installation
Prerequisites
- .NET 8 SDK (≥ 8.0.100) or the .NET 10 SDK — the libraries ship both a
net8.0and anet10.0assembly, so a project on either framework can reference them.
That's it. Everything else, including the native compiler pieces, comes from NuGet automatically when you dotnet restore. There is no separate native install.
The library (the product)
Add the ShadowDusk.Compiler package to your project:
dotnet add package ShadowDusk.Compiler
Then call it from code — see the In-Memory Quickstart:
var compiler = new ShadowDusk.Compiler.EffectCompiler();
var result = await compiler.CompileAsync(hlslSource,
new ShadowDusk.Core.CompilerOptions { Target = ShadowDusk.Core.PlatformTarget.OpenGL });
Note
ShadowDusk runs only with Vortice.Dxc 3.3.4, the release that carries its pinned DXC. If
another package in your application raises Vortice.Dxc (Evergine.DirectX12 pulls 3.8.3, for
example), your build shows warning SD0220 next to NuGet's NU1608, and DirectX 12, OpenGL and
Vulkan compiles fail at runtime with SD0219; DirectX 11 and FNA are unaffected. Pin it with
<PackageReference Include="Vortice.Dxc" Version="3.3.4" />. See Diagnostics.
The CLI tool (ShadowDuskCLI)
Install the drop-in mgfxc replacement as a global tool:
dotnet tool install --global ShadowDusk.Cli
This provides a ShadowDuskCLI command with the same flags and .mgfx output as MonoGame's tool. See the CLI Reference and the Drop-in mgfxc guide.
Default-target caveat: the CLI's default
/ProfileisDirectX_11, while the library's Target default isOpenGL. Always pass the target you want explicitly. This is called out again on the Quickstart and CLI Reference pages.
The content pipeline packages
For a .mgcb file (MGCB), add ShadowDusk.MgcbPlugin and /reference: it — see MGCB Content Pipeline. For MonoGame 3.8.5's code-centric Content Builder project, add ShadowDusk.ContentPipeline to the Builder project and pass new ShadowDuskEffectImporter(), new ShadowDuskEffectProcessor() — see MonoGame 3.8.5 Content Builder. Both compile the same importer/processor; only the packaging differs (the plugin is tools-only and cannot be referenced from C#).
The in-browser (WASM) library
For in-browser runtime compilation (KNI / Blazor WebAssembly), add the ShadowDusk.Wasm package. It self-registers as Blazor static web assets — see In-Browser (KNI/Blazor WASM).
Optional ShaderToy and GLSL front-end
ShadowDusk.ShaderToy is a standalone, pure-managed (zero-native) converter that turns a ShaderToy (mainImage) or plain-GLSL fragment shader into a self-contained HLSL .fx source. It is optional — it is not part of the faithful mgfxc-replacement pipeline, and ShadowDusk.Compiler does not depend on it. Install it when you want to convert ShaderToy/GLSL shaders in-process (a shader fiddle, an in-app importer):
dotnet add package ShadowDusk.ShaderToy
var converted = ShadowDusk.ShaderToy.ShaderToyConverter.Convert(glslSource);
// converted.Fx is .fx source — compile it with EffectCompiler.CompileAsync(...)
Unsupported constructs fail loudly with a located (line/column) diagnostic rather than producing a silently-wrong .fx. The CLI has this front-end built in: ShadowDuskCLI auto-detects .glsl/.frag/.fs input and converts it before compiling — see the CLI Reference.
Slang input — the free subset, and the optional full language
ShadowDusk accepts .slang source in two tiers. The free default, built into ShadowDusk.Compiler (no extra package, no native toolchain, works on every host including the browser), accepts the HLSL-compatible subset of Slang: entry points via [shader("vertex")]/[shader("fragment")], a synthesized technique, and a body compiled by the same faithful pipeline as any .fx. Slang-only features (import, generics, interfaces) are rejected with a named SD0600 rather than approximated — see the CLI Reference for --input-format.
For genuine Slang — import, generics, interface conformances, everything real Slang accepts — install the optional ShadowDusk.Slang package. It bundles the real slangc compiler for win-x64, linux-x64, osx-x64, osx-arm64 (Linux needs a GCC 11+ libstdc++, i.e. Ubuntu 22.04 or later; macOS needs macOS 26 or later, the upstream build's own floor; ARM64 Linux is not bundled, because ShadowDusk's own pipeline has no DXC there; ARM64 Windows is not bundled to keep the package small, although the core pipeline is measured there, so a native Windows on Arm process gets SD0620: open an issue if you need it) and routes its HLSL emission to the same unchanged, faithful DXC pipeline:
dotnet add package ShadowDusk.Slang
var compiler = new ShadowDusk.Slang.SlangCompiler();
var result = compiler.Compile(slangSource, new CompilerOptions { Target = PlatformTarget.OpenGL });
// result.Value.Data is .mgfx bytes, same as EffectCompiler.CompileAsync's output
The same route feeds the SkiaSharp and raylib converters (fragment-only, with their own limits): compiler.ConvertToSksl(slangSource, new SkslConvertOptions { SourceName = "Fx.slang" }) and compiler.ConvertToRaylib(slangSource, new RaylibConvertOptions { SourceName = "Fx.slang" }). For the HLSL-compatible subset without this package, use SkslConverter.ConvertSlang / RaylibConverter.ConvertSlang.
In the browser: a browser cannot start slangc as a process, so the same pinned slangc is compiled to WebAssembly and run inside the page (ShadowDusk.Slang.Wasm's WasmSlangCompiler). It gets the identical command line, its output is measured byte-identical to native slangc's, and its HLSL goes through the same in-browser DXC pipeline. It is not published as a NuGet package yet; the ShaderFiddle sample uses it from source.
Parameter names are the ones you wrote. slangc's -no-mangle keeps cbuffer members and Texture2D/SamplerState globals at their source names, and a combined Sampler2D Comb; reflects its texture as Comb (effect.Parameters["Comb"].SetValue(texture); slangc itself splits it into Comb_texture_0 and Comb_sampler_0, and ShadowDusk gives the texture back the name you wrote, the same table a hand-written Texture2D Comb; SamplerState ... gets through the .fx route). A texture held in a struct global, inside a cbuffer/ParameterBlock, or taken as an entry-point uniform parameter has no name you wrote that could identify it, so it is rejected with SD0640 naming the aggregate; declare such textures as globals of their own.
A register on a combined sampler binds one half. Sampler2D A : register(s2); binds the sampler half to s2 and leaves the texture to be numbered (t0 on DirectX, which is also what mgfxc gives the hand-written sampler2D A : register(s2)). To put both halves on slot 2 on DirectX (for a game that binds GraphicsDevice.Textures[2]), write Sampler2D A : register(t2) : register(s2);. On OpenGL the sampler register is the texture unit, exactly as mgfxc treats the legacy sampler2D A : register(s2): register(s0) is SpriteBatch's unit 0. (A split Texture2D + SamplerState : register(sN) pair keeps mgfxc's split-pair rule, where the sampler register reserves its unit.) On OpenGL an array of combined samplers (Sampler2D A[3]) has no representation in MonoGame's effect format and is refused with SD0217, like an array of textures; declare each element separately.
A consumer who only needs the free subset above pays zero extra size or dependency for this package. Same rejection discipline either way: a construct real slangc accepts but that has nowhere to land in an Effect (a compute entry point, an SM6-only wave/quad intrinsic on a target that can't represent it) is rejected loudly by name, never silently dropped. Neither tier is ever mgfxc-equivalent — mgfxc cannot read Slang at all.
Targeting FNA
FNA uses the same ShadowDusk.Compiler package — there is nothing FNA-specific to install on ShadowDusk's side:
dotnet add package ShadowDusk.Compiler
Two things differ from the MonoGame/KNI path, both on FNA's side, not ShadowDusk's:
- FNA is not a NuGet package. Unlike MonoGame and KNI, FNA is consumed as a project reference — a git clone/submodule of FNA-XNA/FNA plus its native
fnalibs(SDL3, FNA3D, FAudio) — per FNA's own setup docs. (Community/unofficial FNA NuGet builds exist, but the project reference is FNA's documented, supported path.) You addShadowDusk.Compilerto that existing FNA project exactly as above. - Different output container. For
PlatformTarget.Fna, ShadowDusk emits a D3D9 fx_2_0.fxb(Shader Model ≤ 3), not the.mgfxMonoGame/KNI load. FNA reads it through MojoShader at runtime vianew Effect(graphicsDevice, fxbBytes). See Compiling for FNA in the quickstart.
The FNA path is cross-platform and self-contained: it uses the same vkd3d-shader native as the DirectX backend, which ships inside the NuGet package, so there is nothing to restore or install.
DirectX backend & native tools
For DirectX 11, the default backend is the cross-platform vkd3d-shader. Its natives for all four desktop RIDs (win-x64, linux-x64, osx-x64, osx-arm64) ship inside the NuGet package, so DirectX compiles out of the box on Linux, macOS, and Windows, with the same bytes everywhere. On Windows you can opt into Microsoft's d3dcompiler_47 (a system DLL already present, the most fxc-faithful option) via CompilerOptions.DxbcBackend = DxbcBackend.D3DCompiler. See the DirectX DXBC (vkd3d) Path for details. On Windows on Arm (win-arm64), OpenGL, Vulkan and DirectX 12 compile to the same bytes as win-x64; DirectX 11 and FNA return SD0211, because no vkd3d-shader build exists for win-arm64. (The restore script is only for building ShadowDusk itself from source.)
Building from source
git clone https://github.com/kaltinril/ShadowDusk.git
cd ShadowDusk
./tools/restore.sh # Linux / macOS (.\tools\restore.ps1 on Windows)
dotnet build ShadowDusk.slnx
See Restore Native Tools for what the restore script does.