Choosing a Target
When you compile a .fx, the choice that changes the output bytes is mostly which graphics backend the result must run on — with one framework-level exception: FNA, which loads a different effect format entirely. GraphicsProfile (Reach / HiDef) feels like it should matter too, but it doesn't. This page untangles the three axes so you can pick the right target with confidence (e.g. when offering shader downloads from a tool like XnaFiddle).
The three axes (which ones change the file)
| Axis | Examples | Changes the compiled output? |
|---|---|---|
| 1. Framework | MonoGame, KNI, FNA | Mostly no — MonoGame and KNI read the same MGFX container. FNA is the exception: it loads the legacy D3D9 fx_2_0 .fxb, so it gets its own output (see below). |
| 2. Graphics backend / API | DirectX 11, OpenGL/DesktopGL, WebGL, Metal, Vulkan | YES — for MonoGame/KNI this is the axis that picks the bytes |
3. GraphicsProfile |
Reach, HiDef | No — a runtime setting, not a compile target |
For MonoGame/KNI the bytes are dictated by axis 2. A DirectX .mgfx contains DXBC GPU bytecode; an OpenGL .mgfx contains GLSL source text. They are not interchangeable — MonoGame's Effect loader reads a profile byte in the header and routes to a completely different code path. So for MonoGame/KNI the one thing you must know to pick a target is which graphics backend the consuming game runs. For FNA, axis 1 decides it: pick PlatformTarget.Fna and you get the .fxb FNA loads — one file serves every backend FNA itself runs on (FNA translates at load time).
Axis 1 — Framework compatibility
| Framework | Status |
|---|---|
| MonoGame | ✅ Supported and validated (DesktopGL + WindowsDX, 10/10 each in the real runtime). |
| KNI | ✅ Supported — KNI reads the same MGFX format as MonoGame (its loader accepts MGFX v10), the default. Render-proven in real headless KNI WebGL and on a current KNI v4.02 desktop runtime. KNI's newer KNIFX v11 container is also available as an opt-in (CompilerOptions.Container = EffectContainer.Knifx, KNI v4.02+); the default v10 needs no KNI-specific output. |
| FNA | ✅ Supported — but a different output format. FNA's effect path differs from MonoGame's: it loads legacy D3D9 fx_2_0 shader bytecode via MojoShader at runtime, not the MGFX container. So ShadowDusk does not hand FNA a .mgfx — it emits the .fxb FNA actually loads (new Effect(gd, bytes)). Select it with PlatformTarget.Fna / CLI /Profile:FNA (D3D9-style .fx, SM ≤ 3). Validated end-to-end — renders pixel-equivalent to fxc /T fx_2_0 in real FNA (PS-only and custom-vertex-shader effects, incl. multi-pass + in-pass render states). Shaders needing SM4+ features fail loudly with a clear diagnostic. |
| Classic Microsoft XNA 4.0 | ❌ Out of scope. XNA's effect .xnb wraps legacy D3D9 bytecode produced by fxc — a different instruction set our pipeline does not emit, on a Windows-only framework abandoned in ~2013. There is no "reach" win (nothing to compile where mgfxc can't) and no fidelity oracle. (Note: "XnaFiddle" is named for the lineage — the runtime it ships is KNI/MonoGame, which is supported.) |
Axis 2 — Graphics backend (the real matrix)
Select the backend with Target (library) or /Profile: (CLI). The defaults differ — see Parameters & Caveats.
| Target | CompilerOptions.Target |
CLI /Profile: |
Output | Status |
|---|---|---|---|---|
| OpenGL / DesktopGL / WebGL | PlatformTarget.OpenGL |
OpenGL |
.mgfx, GLSL text (MojoShader dialect) |
✅ Validated |
| DirectX 11 (WindowsDX) | PlatformTarget.DirectX |
DirectX_11 |
.mgfx, DXBC binary (SM5) |
✅ Validated |
| FNA | PlatformTarget.Fna |
FNA |
.fxb, D3D9 fx_2_0 (SM ≤ 3) |
✅ Validated |
| Metal | PlatformTarget.Metal |
— | MSL | ❌ Not implemented (future) |
| Vulkan | PlatformTarget.Vulkan |
Vulkan |
SPIR-V | 🧪 Experimental: compiles, but unvalidated (parked on no shipping Vulkan runtime) |
For the MonoGame/KNI targets, the on-disk profile byte in the MGFX header encodes the backend choice (OpenGL = 0, DirectX11 = 1, Vulkan = 3). The runtime reads it to pick the shader path, so the target must be chosen at compile time — there is no universal .mgfx that serves both DirectX and OpenGL. A DirectX .mgfx is useless to a DesktopGL game and vice versa. FNA is selected differently (it's a whole separate format, .fxb — see Axis 1) and is not byte-compatible with the .mgfx targets.
Practical rule for shader downloads: first ask which framework — if it's FNA, emit the
.fxb(PlatformTarget.Fna) and you're done (one file, all FNA backends). For MonoGame/KNI, the only remaining question is "DirectX (WindowsDX) or OpenGL/DesktopGL/Web?" — offer one.mgfxper backend.GraphicsProfilenever matters.All three targets can also be compiled in the browser via
ShadowDusk.Wasm, byte-identical to a desktop compile — OpenGL renders live in KNI WebGL, while DirectX and FNA are export targets (a browser cannot render DXBC/D3D9 bytecode). See DirectX & FNA in the Browser.
One-shot selection: capability profiles (Profile / --target-runtime)
Instead of setting the backend (Target), container (Container), and version (MgfxVersion) separately, you can name the whole (runtime, format) contract with a single capability profile. A profile fully specifies the output, including the backend, so picking one is all you need:
Profile (CapabilityProfile.*) |
CLI --target-runtime |
Output |
|---|---|---|
MonoGameGL_3_8_2 |
monogame-gl |
OpenGL, MGFX v10 |
MonoGameDX_SM5 |
monogame-dx |
DirectX, MGFX v10 |
MonoGameGL_3_8_5 |
monogame-gl-v11 |
OpenGL, MGFX v11 (MonoGame 3.8.5+) |
KniGL_4_02 |
kni-knifx |
OpenGL, KNIFX v11 (KNI 4.02+) |
Fna_Fx2 |
fna |
FNA, fx_2_0 |
Set it on the library with CompilerOptions.Profile = CapabilityProfile.KniGL_4_02, or on the CLI with --target-runtime kni-knifx. A set profile overrides Target / Container / MgfxVersion. The set is closed — it holds only render-proven contracts, so you can never request an untested combination.
Auto-detecting the runtime. For an in-app runtime compile, RuntimeProfileDetector.Recommend(typeof(Game).Assembly, target) classifies the loaded framework (MonoGame / KNI / FNA) and returns the proven profile to pass to CompilerOptions.Profile. It is conservative: it returns the universally-loadable MGFX v10 (or fx_2_0 for FNA) and never silently upgrades a consumer to a newer container — the newer formats stay an explicit opt-in.
Axis 3 — Reach vs HiDef (a runtime concept, not a target)
GraphicsProfile.Reach and GraphicsProfile.HiDef are runtime settings, not compile-time targets. mgfxc has no "Reach profile" vs "HiDef profile" — only OpenGL and DirectX_11. So you do not compile twice for Reach vs HiDef.
For the OpenGL/WebGL path specifically:
GraphicsProfile.Reach→ WebGL1 / GLSL ES 1.00GraphicsProfile.HiDef→ WebGL2 / GLSL ES 3.00
ShadowDusk emits one OpenGL .mgfx (legacy GLSL dialect), and KNI's runtime converts it to ES 3.00 at load time for HiDef/WebGL2. One file serves both. See KNI HiDef / WebGL2.
.mgfx vs .xnb
For the MonoGame/KNI targets, ShadowDusk emits a raw .mgfx blob, not an .xnb (FNA's equivalent is the raw .fxb, likewise unwrapped). These are different layers:
.mgfx |
.xnb |
|
|---|---|---|
| What it is | the compiled effect itself | the Content Pipeline container that wraps a .mgfx (and every other content type) |
| Loaded via | new Effect(graphicsDevice, mgfxBytes) |
Content.Load<Effect>("name") |
| Produced by | ShadowDusk (and mgfxc) |
the Content Pipeline / MGCB |
If a consuming project loads effects with new Effect(gd, bytes), hand it the raw .mgfx — done. If it uses Content.Load<Effect>, it needs an .xnb, which means either building the .fx through MGCB (see the MGCB Content Pipeline guide) or wrapping the bytes yourself. ShadowDusk does not produce .xnb directly — that's the content-pipeline layer's job. See also Parameters & Caveats → .mgfx vs .xnb.