Installation

Prerequisites

  • .NET 8 SDK (≥ 8.0.100) or the .NET 10 SDK — the libraries ship both a net8.0 and a net10.0 assembly, 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 /Profile is DirectX_11, while the library's Target default is OpenGL. 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 add ShadowDusk.Compiler to 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 .mgfx MonoGame/KNI load. FNA reads it through MojoShader at runtime via new 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.