Test Shader Corpus
ShadowDusk is validated against a corpus of canonical .fx test shaders under tests/fixtures/shaders/, with golden references under tests/fixtures/golden/ — mgfxc .mgfx for DirectX_11/, DirectX_12/, OpenGL/, and Vulkan/, fxc fx_2_0 .fxb for FNA/, plus the byte-identity/ manifest. The provenance of each shader and the project-owned examples are documented in the repository and reproduced here as the single source of truth:
Test Shader Corpus — Provenance & Fresh Examples
Last updated: 2026-08-02 — the issue-#189 fix added SamplerRegisterOrder.fx and
SamplerRegisterSparse.fx (the sampler-register set below), both with OpenGL and
DirectX_11 goldens. Previously 2026-08-01: the issue-#187 fix added the four ExPhantom*
fixtures (the phantom-parameter set below). Previously 2026-07-31: Phase 51 A10 added three
DirectX-profile-floor fixtures and reclassified the vendored Nez set, whose DirectX column
collapsed once ShadowDusk started enforcing mgfxc's own floor (see the note above that table).
Corpus on disk: 153 .fx + 7 .fxh — 64 in the fixture root, 50 in examples/, 1 in
shadertoy/, 38 under third-party/.
This document records (1) what is known about where the existing .fx test
fixtures came from, (2) an integrity caveat about those fixtures, and (3) a set
of fresh, project-owned example shaders authored from scratch for ShadowDusk
that we use going forward — with fully known provenance — alongside the original
cross-validated corpus.
1. Why this document exists
ShadowDusk's fidelity claim rests on comparing its output against mgfxc's,
using real third-party shaders as inputs (see CLAUDE.md → What success
actually means). For that to mean anything, the test inputs should have known,
honest provenance.
Two problems surfaced:
- The fixtures were modified before they were ever committed. An earlier
automated pass "fixed" several
.fxfixtures (e.g. to make them compile cleanly) rather than keeping them byte-for-byte as their upstream originals. Because that happened before the initial commit (cfbb039), this repo's git history contains no pre-modification version to diff or revert to. - Per-shader provenance was never recorded.
docs/research.mdandmonogame_runtime_mgfx_compiler_research.mdcontain many project and.fxlinks, but every one is a toolchain or MonoGame-builtin reference (BasicEffect.fx, thehlslparserrepos, DXC/SPIRV-Cross/MojoShader, etc.). None records whereGrayscale/Dissolve/Scanlines/… originally came from.
Consequence: we cannot cleanly "restore the originals" for the modified
fixtures — and mgfxc is not available in this environment to regenerate
goldens anyway (it needs Windows + fxc.exe). So going forward we add a small
set of fresh fixtures we fully own and document, and treat the original 10
cross-validated shaders as legacy-but-grandfathered (they already have mgfxc
goldens and pass the in-engine comparison in validation/).
2. Provenance of the existing fixtures (best effort)
Recovered by inspecting the shader code and confirming upstream repos by their distinctive shader sets / comment style. Treat "Confirmed" as "the upstream project is identified"; it does not guarantee the checked-in file matches upstream verbatim (see the integrity caveat in §1).
| Fixture(s) | Upstream source | Confidence |
|---|---|---|
PenumbraHull.fx, PenumbraLight.fx, PenumbraShadow.fx, PenumbraTexture.fx |
discosultan/penumbra — 2D lighting w/ soft shadows for MonoGame | Confirmed |
BasicShader.fx, TintShader.fx, BlendShader.fx, MultiTexture.fx/MultiTextureOverlay.fx, SimpleLightShader.fx |
manbeardgames/monogame-hlsl-examples — the four worked examples (Apply / PassingValues / MultipleTextures / Simple2DLighting); matches the verbose teaching-comment style | Confirmed (project); per-file naming adapted |
Post-FX pack: Grayscale.fx, Invert.fx, Sepia.fx, Saturate.fx, Pixelated.fx, Scanlines.fx, Fading.fx, Dots.fx |
A common MonoGame post-process tutorial pack; exact upstream not confidently identified | Unknown |
Dissolve.fx, ForwardLighting.fx, PolygonLight.fx |
Nez-style 2D framework (underscore-prefixed sampler convention, discard-based dissolve); exact upstream not confidently identified | Unknown |
Minimal.fx, cbuffer.fx, multipass.fx, multitechnique.fx, render-states.fx, annotations.fx, platform-macros.fx, basiceffect-mini.fx, etc. |
Purpose-built ShadowDusk structural fixtures (SM4/5 feature probes) | Project-owned |
StateBlendAdditive.fx, StateDepthStencil.fx, StateRasterizer.fx, SamplerStatesFull.fx, AnnotatedTechnique.fx |
Writer-fidelity corpus: pass blend/depth-stencil/rasterizer states, baked sampler_state members, parameter/technique/pass annotations. All but AnnotatedTechnique.fx have real mgfxc goldens (mgfxc's grammar cannot parse technique/pass annotations). |
Project-owned |
If you can supply the original source links for the "Unknown" rows, add them here — that lets us diff the checked-in files against upstream and decide, per shader, whether to restore the original.
The 10 cross-validated (image-equivalence) shaders
Grayscale, Invert, TintShader, Sepia, Saturate, Pixelated, Scanlines, Fading, Dots, Dissolve — these have checked-in mgfxc goldens under
tests/fixtures/golden/OpenGL/ and are the corpus the validation/ harness
renders in real MonoGame and compares pixel-for-pixel. They remain in use; this
document does not change them.
3. Fresh, project-owned example shaders
Authored from scratch for ShadowDusk. Provenance is fully known: we wrote them. They are licensed with the repository and derive from no third-party shader. They live in:
tests/fixtures/shaders/examples/
Each targets a distinct part of the legacy→modern rewrite surface so the
FxPreParser rewrites and the monoGameGl GL path have owned, documented
regression coverage. All are SM3 PS-only and follow
MonoGame's conventional SpriteBatch/SpriteEffect shape (the validated path).
| File | What it exercises |
|---|---|
ExBareSamplerTex2D.fx |
Bare sampler s0; + tex2D → synthesized Texture2D + .Sample (gap #2 Form 2 + gap #4). No free uniforms. |
ExSamplerStateUniform.fx |
Texture2D + sampler2D = sampler_state { Texture = <T>; } (gap #2 Form 1) + a free float4 uniform set by name. |
ExDualTexture.fx |
Two textures/samplers, each tex2D-sampled and resolving to its own texture; a float blend uniform (multi-sampler binding). |
ExLegacyTextureDiscard.fx |
Legacy effect-framework texture T; rewritten to Texture2D T; (gap #3) + sampler_state bound to it + clip()/discard + scalar uniform. A clean, owned analogue of Dissolve. |
ExModernSample.fx |
Control / negative case: already-modern Texture2D + SamplerState + .Sample() + SV_TARGET. No rewrite should fire. |
Issue #106 regression set (relationals / ternaries / helpers / loop)
Pins issue #106: a relational operator (<, <=, >, >=), a ternary, an
if/else branch, or a for-loop condition appearing in a shader body was
misparsed by the FxPreParser as the start of an FX annotation. These
fixtures are small, real (full technique + pass, renderable), project-owned
originals in the all-runtime SM3/fx_2_0 subset, so each compiles on OpenGL
(MonoGame-GL / KNI), DirectX_11 (MonoGame-DX), and FNA (D3D9 fx_2_0) — verified
exit 0 with non-empty output on all three.
| File | Bug-class it guards | Runtimes |
|---|---|---|
Issue106Repro.fx |
The verbatim reporter shader from issue #106: a helper (TestEarlyReturn) using an equality (==), a relational (<=), nested if, and an early return in its body, called from the PS entry. Kept exact (only a provenance header added) so the real reported shape is pinned, not just a synthetic stand-in. VS+PS sprite path. |
GL + DX + FNA |
ExTernaryHelper.fx |
The canonical #106 shape: a helper function that returns a ternary over a relational (value <= 0.5f ? 0 : 1), called from the PS entry, plus a ternary in the entry body. VS+PS sprite path. |
GL + DX + FNA |
ExRelationalThreshold.fx |
Relational operators directly in the PS body — <, <=, >, >= as scalar bool expressions (not inside a ternary, not inside clip()), each promoted to a 0/1 float for a banded threshold. |
GL + DX + FNA |
ExRelationalBranch.fx |
A relational-driven if / else if / else branch in the body (not clip()) and a nested / chained ternary (4-band select). |
GL + DX + FNA |
ExLoopRelational.fx |
A relational condition in a for-loop header (for (int i = 0; i < N; i++)) — also closes the corpus's missing all-runtime SM3 loop case. Literal-bounded so fxc unrolls it at ps_3_0/ps_2_0. |
GL + DX + FNA |
These are exercised by Issue106RegressionCorpusTests (compile-asserts each on
all three targets) and the FNA SM3 corpus census. As with the other fresh
fixtures, they prove "ShadowDusk compiles them into a valid effect," not
pixel-equivalence to mgfxc/fxc.
One-shot do-while / ANGLE-derivative set (issues #107 + #136)
Pins the SPIRV-Cross structured-early-return idiom (do { … } while(false)) that
its GLSL backend wraps around an entry point / inlined helper containing an early
return. These fixtures drive the GL-stage MonoGameGlslRewriter Rule-9 handling
(9a unwrap, 9b for-loop fallback) end-to-end through the real pipeline.
| File | Bug-class it guards | Runtimes |
|---|---|---|
Issue107DoWhile.fx |
The verbatim #107 reporter helper (TestEarlyReturn, nested-if early return): the wrapper must not survive as a raw do { … } while(false) (WebGL1 / KNI Reach rejects it at load). Pinned by HidefGeneralityFixtureTests to be do-while-free and, since #136, wrapper-loop-free (unwrapped, not lowered to a for-loop). |
GL (KNI Reach + HiDef) |
Issue136HelperGradient.fx |
An inlined helper that both early-returns and takes a derivative (fwidth) with an entry-point early return around it — the nested-wrapper shape that ANGLE D3D11 (WebGL on Windows) poisons if left as a divergent loop. Pinned by EarlyReturnHelperGradient…Issue136 so no gradient op lands inside a loop with a divergent exit in the emitted GL GLSL. |
GL (KNI Reach + HiDef) |
These are compile + structural pins (emitted-GLSL shape), not pixel-equivalence
claims; the render side is proven separately by validation/AngleDerivativeProbe
and the KNI GL drivers. The vendored apos-shapes-aa.fx (below) is the real-world
derivative-AA shader of the same class.
FX pre-parser robustness set (dropped-operator bug class)
Pins the dropped-operator bug class
(plan/DONE/PHASE-45-fx-preparser-robustness.md). Same shared root
cause as #106: the FxLexer drops several operators (: + [ ] & | ! ? % ^ ~), so
a flat heuristic in FxPreParser pattern-matched the fragmented token stream and
acted wrongly. Each fixture is small, real (full technique + pass, renderable), and
project-owned.
| File | Bug it guards | Runtimes |
|---|---|---|
ExModernSamplerState.fx |
B2 — a sampler S = sampler_state { Texture = <T>; } declaration USED through the modern T.Sample(S, uv) method (not tex2D). Was erased → DXC "undeclared identifier 'S'"; now rewritten to a passthrough SamplerState S;. The MonoGame HiDef SpriteEffect / modern KNI 2D shape. |
GL + DX (.Sample is SM4 method syntax; FNA N/A) |
ExColorWriteMask.fx |
B3 — ColorWriteEnable = Red \| Green \| Blue;. The lexer drops \|, so the value arrived as three adjacent identifiers; the pass parser stopped at the first and demanded ; (FX0008). |
GL + DX + FNA |
ExLegacyTextureAnnotation.fx |
B4 — a legacy texture T < string Name = "x"; >; (FX annotation on a texture object). The annotation has its own inner ;, so ConsumeLegacyTextureDecl stopped early and leaked >; → DXC "expected unqualified-id"; the consume now tracks angle-bracket depth. Ubiquitous FX Composer / RenderMonkey / NVIDIA-sample shape. |
GL + DX + FNA |
ExTextureNamedTexture.fx |
B5 — a modern resource whose VARIABLE NAME is a legacy keyword, Texture2D Texture : register(t0);. The legacy-texture rewrite fired in name position and produced the broken Texture2D Texture2D register;; it now declines when the keyword's predecessor is an identifier/> (name position). |
GL + DX (.Sample is SM4 method syntax; FNA N/A) |
ExVsColorReturn.fx |
B6 — a VERTEX shader whose function-return semantic is : COLOR (writes POSITION via an out param). fxc/mgfxc accept it, but the PS COLOR->SV_Target rewrite broke the VS; the rewrite is now deferred and skips compile vs_* entry points. |
GL + DX + FNA |
ExSamplerRegisterState.fx |
B8 — sampler S : register(s0) = sampler_state { … }; (the register clause appears BEFORE the =). The dropped : mis-routed it to the bare-sampler path, leaking the state block to DXC. |
GL + DX + FNA |
ExSamplerAnnotation.fx |
B9 — sampler2D S = sampler_state { … } < string UIName = "x"; >; (a trailing sampler-level FX annotation). ParseSamplerDecl hard-required ; right after } (FX0001 on <); the annotation is now consumed and stripped. |
GL + DX + FNA |
ExArrayTernaryAssign.fx |
B7 — an array-indexed relational with an assignment in a ternary arm inside a function body, Thresholds[i] < x ? acc = w : acc; (the issue-#106 residual). Once ?/:/[/] are dropped, the x acc = tail satisfies the annotation-shape guard; the global annotation strip is now gated on brace depth 0, so an in-body expression can never be misread. |
GL + DX + FNA |
ExReservedWordUniform.fx |
B10 (a DIFFERENT class — a GLSL reserved-word / reflection-join bug, not a dropped-operator pre-parser one) — a free uniform named after a GLSL reserved word, float noise;, used in the body. On GL, SPIRV-Cross renames it _noise, so the cbuffer/parameter join (matched by name) missed and failed SD0012. The join now falls back to an offset bridge that recovers the parameter by its BaseRegister * 16 byte offset, keeping it exposed under noise. See the third-party Noise.fx note below. |
GL + DX + FNA |
These are exercised by Phase45PreParserRobustnessCorpusTests (compile-asserts
each on its applicable targets); the all-runtime ones are also in the FNA SM3
corpus census. Same scope as above: a valid-effect compile, not pixel-equivalence.
Phantom-parameter set (issue #187 — synthesized GL register backing)
Pins the issue-#187 class (plan/DONE/ISSUE-187-gl-phantom-parameter-compile-fidelity.md): a
numeric uniform whose only reads form an algebraic identity DXC's -spirv backend cancels
(fxc and the DXIL reflection companion do not), so the OpenGL pipeline must SYNTHESIZE the
parameter's register backing. Each fixture pins one synthesis sub-shape found by the
adversarial reviews. All four are project-owned, authored from scratch 2026-08-01, and
asserted structurally by GlPhantomParameterTests (plus the corpus-wide backing sweep).
| File | Sub-shape it guards | Runtimes |
|---|---|---|
ExPhantomNonSquareMatrix.fx |
A float2x4 phantom must be sized by the runtime's TRANSPOSED matrix write model — Columns registers (MonoGame/KNI upload ColumnCount 16-byte rows); sizing by Rows under-allocates and crashes the first EffectPass.Apply. |
GL + DX (census) |
ExPhantomDerivativeUniform.fx |
A phantom in a derivative-using shader: the synthesized declaration must be INSERTED after the #extension GL_OES_standard_derivatives header + #ifdef GL_ES precision block (strict ESSL front ends reject it earlier; desktop GL is lenient, which is how it would slip past desktop gates). |
GL + DX (census) |
ExPhantomSecondCbufferFold.fx |
One live cbuffer + one fully-folded cbuffer: synthesis must APPEND after the live registers and RESIZE the existing declaration ([1] → [2]) — the resize branch no fully-folded fixture can reach. |
GL + DX (census) |
ExPhantomTexLodUniform.fx |
A phantom in an explicit-LOD (SampleLevel) shader: the insert must clear the BALANCED #if __VERSION__ >= 300 … #elif … #extension … #endif TexLod header, whose #extension directives live inside branches (Mesa hard-errors on a mid-shader #extension and takes the GL_ARB_shader_texture_lod branch). |
GL + DX (census) |
Sampler-pair set (Phase 51 A7 — the OpenGL/DX12 shared-SamplerState fix)
Project-owned, authored for the release that re-keyed the GL sampler table on
(texture, sampler) pairs. These are the fixtures the headline fix is proven against, and
they back validation/SamplerPairsGl:
SharedSamplerPair.fx— two textures read through one sharedSamplerState(the classic diffuse+lightmap shape). Ordinary HLSL thatmgfxchas always compiled; ShadowDusk used to reject it outright with the now-retiredSD0216. Two pairs ⇒ two sampler records.SamplerPairMirror.fx— two textures sampled in reverse of declaration order, which is what exposed the second, silent defect: SPIRV-Cross declares combined samplers in first-use order, so counts matched while the texture parameter and the sampler-type byte came out swapped. Deliberately samples asymmetrically so a mis-binding changes the picture. Its two textures hold identical pixels on purpose, so the arm isolates per-pair sampler state — which is also why it is structurally blind to slot NUMBERING, the gap#189fell through. UseSamplerRegisterOrder.fxfor anything about which unit a pair lands on.SamplerRegisterOrder.fx— GitHub issue #189.SpriteSampler : register(s0)andMaskSampler : register(s1), sampled in reverse declaration order, output(sprite.r, mask.g, 0, 1). It is the canonical SpriteBatch custom-effect shape —register(s0)is the sprite texture, becauseSpriteBatchforces it onto unit 0 right afterEffectPass.Apply(). Rendered with a red sprite and a green mask: yellow = slots allocated in declaration order (correct, matches fxc/mgfxc), black = first-use order (the #189 bug). Both channels flip together, so neither outcome can be mistaken for a tolerance artefact. Unlike the two fixtures above it uses distinct textures and leaves unit 0 toSpriteBatch, which is precisely what makes slot allocation observable. Goldens onOpenGL+DirectX_11.SamplerRegisterSparse.fx— GitHub issue #189, the sparse/offset half, and the deliberate complement to the fixture above. Two samplers ats2/s3with nothing ats0/s1, sampled strictly in declaration order, so ordering cannot be what it measures: the only variable is the absolute register value. Compacting to units 0/1 is order-preserving and still wrong, becauseSpriteBatchoverwrites unit 0 with the sprite afterEffectPass.Apply(). BLUE sprite + RED MaskA + GREEN MaskB: yellow = registers honoured, green = compacted (the bug) — only the red channel moves, and the untouched green is the control proving the harness bound anything at all. Keep it in LEGACYsamplersyntax: atps_3_0the legacy sampler is the combined object, so the annotation lands the pair on that exact unit. A modernSamplerState : register(sN)behaves differently — it RESERVES the register and the pair is allocated around it (one texture plusS : register(s0)yieldsps_s1) — so rewriting this fixture in modern syntax would change what it measures. Goldens onOpenGLDirectX_11.
ShaderToy route fixture
shadertoy/GradientToy.fx— the pinned output of convertingGradientToy.glslwith the realShaderToyConverter.validation/ShaderToyRouteGlandvalidation/ShaderToyRouteDxeach assert the converter still emits this exact file before rendering, so converter drift turns the gates red instead of leaving a golden describing a different shader. It is also what surfaced Phase 51 A10: the converter used to emitvs_3_0/ps_3_0in both arms of its#if OPENGLheader, which realmgfxc /Profile:DirectX_11rejects while ShadowDusk accepted it. Both halves are fixed: the header is nowSM4-gated (DirectX gets the*_4_0_level_9_1pair; OpenGL and FNA keep SM3), the fixture is golden-backed on both profiles, and the DirectX target enforces the floor itself (SD0015). Regenerate it withdotnet run --project tools/shadertoy2fx/src/ShadowDusk.ShaderToy.Cli -- tests/fixtures/shaders/shadertoy/GradientToy.glsl -o tests/fixtures/shaders/shadertoy/GradientToy.fx --name GradientToy --technique ShaderToy(normalize to LF), thentools/compile-fixtures.ps1 -Profiles DirectX_11,OpenGL.
DirectX compile-profile floor fixtures (Phase 51 A10)
mgfxc's DirectX_11 shader profile accepts only {vs,ps}_4_0_level_9_1, _4_0_level_9_3,
_4_0, _4_1, and _5_0. Anything else — including _4_0_level_9_0 and every SM6
profile — it refuses with "must be SM 4.0 level 9.1 or higher!". These three fixtures pin
ShadowDusk's matching SD0015 rejection, one per way the condition arises:
ExProfileSm3OnDirectX.fx— a bare literalcompile ps_3_0 MainPS()(no header at all), the shape most real DesktopGL/FNA shaders ship with. Exercises the cheap literal path. Compiles on OpenGL and FNA, where SM3 is correct.ExProfileSm3BothArms.fx— an#if OPENGL … #else …header naming SM3 in both arms: a verbatim capture of whatShaderToyConverteremitted before A10. The compile target is a macro name, so this exercises the DXC-Pexpansion path.ExProfileSm6OnDirectX.fx—compile ps_6_0, which is numerically higher and still refused. It exists so that reimplementing the floor as amajor >= 4comparison is caught by a test rather than by a consumer's failed Content Pipeline build.
How they are used
- Now (no
mgfxcgolden required): compile-level coverage intests/ShadowDusk.Integration.Tests/Tests/CompileExampleFixtureTests.cs— each compiles for OpenGL and produces a structurally valid.mgfx(MGFXsignature, version 10, ≥1 shader blob). This asserts ShadowDusk emits a well-formed, loadable container, not pixel-equivalence. - Later (when
mgfxcis available on a Windows + DirectX SDK box): generatemgfxcgoldens for these intotests/fixtures/golden/OpenGL/and add them to thevalidation/render-and-compare harness to get the full in-engine fidelity bar.
Scope honesty: until those goldens exist, these fresh fixtures prove "ShadowDusk compiles them into a valid effect," not "renders the same as
mgfxc." That stronger claim is still carried only by the original 10.
4. Third-party shader corpus (vendored, real shipping shaders)
Unlike the project-owned fixtures in §3, these are NOT project-owned — they are real, shipping MonoGame
post-process shaders vendored verbatim from the Nez framework
(prime31/Nez, MIT, Copyright (c) 2016 Mike), pinned at commit
6c9d4a87ac62ce36e217cb5e4bbe36d1769dfa4c, upstream dir
DefaultContentSource/effects/. They live under
tests/fixtures/shaders/third-party/Nez/, with the verbatim upstream LICENSE and a
NOTICE.md recording the repo, exact commit, per-file upstream path, license, and the
single modification (a provenance comment header prepended to each .fx; the shader
code itself is byte-for-byte upstream). Licence gate: only MIT / MS-PL / BSD /
Apache-2.0 / public-domain are vendorable — Nez is MIT, so it qualifies; the
MonoGame-docs grayscale tutorial (CC-BY-NC-SA) was explicitly rejected as
non-permissive.
Why these: they broaden the corpus along the language features the project-owned
fixtures under-covered: a literal-bounded for-loop, helper functions called from an
entry point, relational-driven if branches in the body, bloom passes, UV distortion,
vignette, edge-detect, VPOS + float-modulo scanlines, a two-technique VS+PS effect, and
a 1-D-LUT palette swap.
Each shader was compile-classified on all three delivery targets and is wired in only on
the targets it actually compiles on (the rationale per shader is in the directory's
NOTICE.md):
Reclassified 2026-07-31 (Phase 51 A10) — the DirectX column collapsed, and that is the finding, not a regression. Every Nez shader below names a legacy
ps_2_0/ps_3_0(orvs_2_0) compile target outright, with no#if OPENGL … #else …header. MonoGame'sDirectX_11shader profile refuses anything below SM 4.0 level 9.1 — "Invalid profile 'ps_3_0'. Pixel shader 'PixelShaderFunction' must be SM 4.0 level 9.1 or higher!" — and realmgfxc /Profile:DirectX_11was verified to fail every one of them. ShadowDusk used to accept them; since A10 it declines them withSD0015, so the table below now matches the reference compiler. Nez targets DesktopGL, so this is neither a Nez defect nor a ShadowDusk gap: a user who wants one of these on WindowsDX adds the standard cross-platform header, which is exactly what the diagnostic tells them. The DX-reject set is asserted as a reject byThirdPartyShaderCorpusTests.ThirdPartyShader_DirectX11_SubFloorProfile_RejectsWithSd0015— it is covered, not merely dropped. Nothing else about these shaders changed, and no output bytes moved on any target.
| File | Upstream | Targets (compile) | Feature / gap covered | Classification |
|---|---|---|---|---|
GaussianBlur.fx |
Nez (MIT) | GL + FNA | A literal-bounded for-loop accumulating weighted taps over float2[]/float[] array uniforms (the corpus's only GL+FNA SM3 loop). |
GL + FNA (SD0015 on DX) |
BloomCombine.fx |
Nez (MIT) | GL + FNA | Helper fn adjustSaturation() called from the entry; 2nd sampler; lerp/dot/saturate. |
GL + FNA (SD0015 on DX) |
BloomExtract.fx |
Nez (MIT) | GL + FNA | Bloom bright-pass; saturate() threshold remap. |
GL + FNA (SD0015 on DX) |
Twist.fx |
Nez (MIT) | GL + FNA | Relational-driven if (dist < radius) in the body + length/sin/cos UV warp. |
GL + FNA (SD0015 on DX) |
Vignette.fx |
Nez (MIT) | GL + FNA | Radial vignette: dot-based falloff + swizzle, no VS. |
GL + FNA (SD0015 on DX) |
HeatDistortion.fx |
Nez (MIT) | GL + FNA | 2nd sampler declared with explicit AddressU/V = Wrap sampler_state; time-scrolled UV; remap-to-signed. |
GL + FNA (SD0015 on DX) |
Bevels.fx |
Nez (MIT) | GL + FNA | Neighbor-tap edge-detect / emboss (offset tex2D taps, no loop). |
GL + FNA (SD0015 on DX) |
PixelGlitch.fx |
Nez (MIT) | GL + FNA | Helper fn hash11() (frac/floor) called from the entry; row offset. |
GL + FNA (SD0015 on DX) |
SpriteBlinkEffect.fx |
Nez (MIT) | GL + FNA | Tint via lerp by a uniform alpha; VS-output-struct PS. |
GL + FNA (SD0015 on DX) |
Letterbox.fx |
Nez (MIT) | GL + FNA | VPOS screen-space + min() + relational if. VPOS->gl_FragCoord render-equivalence is not asserted. |
GL + FNA (SD0015 on DX), VPOS |
SpriteLines.fx |
Nez (MIT) | GL + FNA | Two techniques (H/V); VPOS + floor + float modulo (%). VPOS render-equivalence not asserted. |
GL + FNA (SD0015 on DX), VPOS |
Crosshatch.fx |
Nez (MIT) | FNA only | Nested if + < relationals + VPOS + float % + an int uniform. Not GL: int uniforms are not modelled on the MonoGame-GL path (loud SD0210, by design). Not DX: compile ps_3_0 is below the DirectX floor (SD0015). |
FNA only |
PaletteCycler.fx |
Nez (MIT) | FNA only | Palette swap via a 1-D LUT (tex1D / sampler1D). Not GL/DX: tex1D has no 1:1 modern Texture method, rejected with a targeted FX0012 that points to FNA (which compiles it natively). |
FNA only |
Reflection.fx |
Nez (MIT) | none (reject-only) | Two techniques, each VS+PS (mirror + water); world-space, half2, frac, relational if. Not GL: the multi-TEXCOORD interpolant block cannot be expressed in std140/std430 by SPIRV-Cross (SD0100). Not FNA: an int/relational construct hits the vkd3d 1.17 SM3 gap (X0000). Not DX (since A10): compile vs_2_0 is below the DirectX floor (SD0015) — the profile mgfxc itself refuses. Retained as a reject-set fixture. |
reject-only |
Noise.fx |
Nez (MIT) | GL + FNA | Film-grain; helper fn rand() (frac/sin/dot) called from the entry. A uniform literally named noise collides with a GLSL reserved word and SPIRV-Cross renames it _noise; this used to break the GL cbuffer/parameter join (SD0012), but the B10 offset-bridge fallback fixed it (see below), so it now compiles on GL too. |
GL + FNA (SD0015 on DX), B10 |
These are exercised by ThirdPartyShaderCorpusTests (compile-asserts each on its
classified targets) and the GL+DX structural census; the all-runtime ones are
also in the FNA SM3 corpus census.
Scope (same as §3): these prove "ShadowDusk compiles them into a well-formed, loadable container," not pixel-equivalence to
mgfxc/fxc. There is no committed golden for them; the render bar stays with thevalidation/*drivers. The VPOS shaders in particular compile on every target but their cross-path VPOS behavior is deliberately left unclaimed.
GLSL reserved-word uniforms on GL (
Noise.fx). A free uniform named after a GLSL reserved word (e.g.noise) is renamed by SPIRV-Cross, which used to break the GL parameter join by name. The pipeline now falls back to matching by register offset, so the parameter stays exposed under its original name (effect.Parameters["noise"]binds); an ambiguous multi-cbuffer shape still fails loudly rather than risk a mis-map. Details:docs/glsl-uniform-naming.md"Design notes".
Gum / Apos.Shapes (the Gum-ecosystem shaders)
Requested by Victor Chelaru / vchelaru (Gum's author; Gum uses Apos.Shapes for its UI shape rendering). Two more vendored sets, both MIT, both classified by an actual compile probe:
tests/fixtures/shaders/third-party/Apos.Shapes/— Apos.Shapes' SDF shape renderer (Apostolique/Apos.Shapes, MIT, Copyright (c) 2021 Jean-David Moisan, commit3fb73b8d…, upstreamSource/Content/apos-shapes.fx).tests/fixtures/shaders/third-party/Gum/— Gum's own sample-project shaders (vchelaru/gum, MIT, Copyright (c) 2013-2024 FlatRedBall LLC, commit771bc5c3…).
| File | Upstream | Targets (compile) | Feature / gap covered | Classification |
|---|---|---|---|---|
Apos.Shapes/apos-shapes.fx |
Apos.Shapes (MIT) | GL + DX | One large VS+PS SDF effect: 10 TEXCOORD interpolants, __KNIFX__/OPENGL macro profile branch, a Newton-iteration for-loop (EllipseSDF), int locals, 11-way if/else shape dispatch, % modulo, ternaries, discard, tex2D, two samplers (one register(s0)), Oklab + gradient math. Not FNA: no SM3/FNA profile branch (its #else selects ps_4_0) + a dense PS exceeds the vkd3d fx_2_0/SM3 ceiling (X0000) — a legit SM limit (Apos.Shapes ships for MonoGame GL/DX, not FNA). |
GL + DX |
Apos.Shapes/apos-shapes-aa.fx |
Apos.Shapes (MIT) | GL + DX | The later derivative-based-antialiasing revision (commit d507a734…, issue #136): ddx/ddy of the SDF and of an interpolated position drive the AA footprint, alongside conditional discard, inlined-helper early returns (SPIRV-Cross's one-shot do-while), genuine Newton loops, and a third sampler (register(s2) blue-noise dither). Carries the AposShapesAa_OpenGl_NoGradientOpInsideDivergentLoop_Issue136 pin: the emitted GL GLSL must never place a gradient op inside a loop with a divergent exit (ANGLE D3D11 zeroes derivatives there). Not FNA: same SM ceiling as above plus the gradient intrinsics. |
GL + DX |
Apos.Shapes/apos-shapes-sm6.fx |
Apos.Shapes (MIT) | GL + DX + Vulkan | The CURRENT upstream revision (commit ea38c6d8…, the issue #145 reproducer): an #elif SM6 branch for Vulkan (vs_6_0/ps_6_0, three Texture2D/SamplerState pairs), a base-2048 packed-color quantization (Pack11/DecodeDigit) replacing the earlier apos-shapes.fx's Cantor-pair packing, a 13-element vertex input, and blue-noise dithering. Render-proven on DX and Vulkan (maxd 0, docs/validation-matrix.md §1/§6) but deliberately NOT the fixture GL's render-proof uses (see the callout above) — its real mgfxc GL compile renders solid black, a confirmed MojoShader/fxc codegen bug. Not FNA: exceeds the vkd3d fx_2_0/SM3 instruction ceiling (SD0305). |
GL + DX + Vulkan (compile); DX + Vulkan (render-proof) |
Gum/MonoGameInCode-Grayscale.fx |
Gum (MIT) | GL + DX + FNA | vs/ps_4_0_level_9_1 profiles, Texture2D + sampler2D + sampler_state, : COLOR0 output, PS-only technique, dot-luminance. |
all-runtime |
Gum/KniInCode-Shader.fx |
Gum (MIT) | FNA only | Legacy D3D9 effect-framework syntax: uniform extern texture, sampler_state { Texture = <…> }, : VIEWPROJ matrix semantic, : COLOR outputs, lowercase pixelshader = compile ps_2_0. Not GL/DX: DXC rejects effect syntax (-Weffects-syntax); only the FNA/fx_2_0 native-effects path accepts it (same shape as PaletteCycler being FNA-only). |
FNA only |
Gum/FnaSample-Shader.fx |
Gum (MIT) | none (honest per-target limits) | The TECHNIQUE()/SAMPLE() #define macro idiom (a technique defined inside a macro), legacy uniform extern texture, : VIEWPROJ, vs_1_1/ps_2_0. FNA recovers the macro technique but declines the sub-SM2 vs_1_1 profile (SD0300); GL keeps SD0010 (the GL macro-model gap, below); DX fails X0000 (vs_1_1/ps_2_0 aren't DX11-compilable). All documented limits, not technique-blindness. |
per-target limits |
These are exercised by ThirdPartyShaderCorpusTests on their classified targets
(with dedicated pins for FnaSample-Shader.fx's per-target rejections) and the
GL+DX structural census. Same scope as the Nez set above: a well-formed-container
compile, not pixel-equivalence (no committed goldens). The one genuinely notable
result is that apos-shapes.fx — the shader Gum's shape rendering actually
depends on — compiles on GL and DX, the targets Gum ships on.
Beyond compile-only: Apos.Shapes is now render-proven on DX, Vulkan, and GL (Phase 51 A3,
2026-07-23). The current-upstream revision apos-shapes-sm6.fx (see
third-party/Apos.Shapes/NOTICE.md — it also compiles GL/DX/Vulkan, FNA excluded on a
legitimate SM ceiling) is pixel-diffed against the real mgfxc golden on DirectX
(validation/VsDrivenDx -- apos, maxd 0 on both ShadowDusk DXBC backends) and Vulkan
(validation/VsDrivenVulkan -- apos, also maxd 0). GL uses a different fixture on purpose: apos-shapes.fx (the Phase 49 pin, below),
not apos-shapes-sm6.fx — that later revision's real mgfxc GL compile is confirmed to render
solid black, a MojoShader/fxc codegen bug, not a ShadowDusk defect. validation/VsDriven -- apos pixel-diffs apos-shapes.fx against the real mgfxc OpenGL golden at maxd 2/255 (see
docs/validation-matrix.md §1/§6). See NOTICE.md and validation/VsDriven's
AposShapesRenderer for the full trace. FNA is permanently excluded (a legitimate SM3
instruction-slot ceiling, not an open rung).
Phase 55 (2026-07-23): the render-proof above exercised exactly one shape (a circle) — now
expanded to the FULL ShapeBatch shape gallery (every Draw*/Fill*/Border* method:
circle, rectangle+corner-radii, line, path, hexagon, triangle, ellipse, arc, ring; gradients,
dashes, rotation), using the REAL Apos.Shapes NuGet package (0.7.7, confirmed byte-identical to
apos-shapes-sm6.fx above modulo one comment) as the render harness via its
ShapeBatch(GraphicsDevice, Effect?) effect-injection constructor — no more hand-rolled vertex
structs. DX11: maxd 0 across all 30 cells on both DXBC backends — the d3dcompiler_47
oracle arm against the real, locally-generated mgfxc golden, the vkd3d arm against the
package's own embedded effect (which disassembles as itself vkd3d-shader-compiled, so it serves
only as a same-toolchain baseline, never an oracle). Vulkan: maxd 0 against the package's
DXC-family embedded effect. DX12: within 1/255 against the real local mgfxc golden, differing on
11 pixels of 402,984 — root-caused 2026-07-31 to the pinned DXC build, not a ShadowDusk defect
(ours dxcoob 1.7.2212.40, the golden's dxcoob 1.8.2505.32; ShadowDusk's own HLSL and flags
through a DXC 1.8 build reproduce the golden's DXIL instruction-for-instruction and render at
maxd 0 — see docs/validation-matrix.md §7).
GL gets a candidate-only visibility check (30/30 shapes render visible content) — no golden
exists for this gallery on GL (the same confirmed MojoShader black-render bug applies to nearly
every shape). See third-party/Apos.Shapes/NOTICE.md §"Phase 55" and
plan/DONE/PHASE-55-apos-shapes-shape-gallery-render-proof.md.
Macro-defined techniques. The DX and FNA paths recover
TECHNIQUE()-macro techniques, so the SM2-fitting MonoGame stock effects compile on FNA (the ones that don't fail for honest SM2-limit reasons). OpenGL does not: the legacy DX9/SM2 branch these effects expand to on GL crashes DXC's SPIR-V codegen, so GL keeps a loudSD0010instead. Tracked inplan/(Phase 41 GAP-1 / GL).
MonoGame's own test effects — the reference compiler's acceptance set (issue #145)
tests/fixtures/shaders/third-party/MonoGame/ vendors the 17 .fx + 2 .fxh assets from
MonoGame/MonoGame Tests/Assets/Effects/ at tag v3.8.5 (Ms-PL, Copyright (C) MonoGame
Foundation, Inc). These are what MonoGame itself builds in its own test suite, and upstream's
per-profile .mgcb files state exactly which effects each backend must compile — including a
Vulkan.mgcb, which is why they were added: issue #145 exposed that the Vulkan proof ran
ten PS-only, matrix-free, modern-syntax fixtures and therefore could not see either bug.
Per-file upstream paths, the .mgcb membership, the measured per-target compile status, and
the reasons behind each non-compile are in the directory's NOTICE.md. The headline additions
to corpus coverage:
Instancing.fx— VS-driven with afloat4x4vertex input onBLENDWEIGHT(four consecutive input locations) plus View/Projection matrices. Exactly the shape issue #145's bug 1 (a transposed matrix on Vulkan) needed to be visible.VertexTextureEffect.fx— vertex-texture fetch with an SM6/SM4/SM3 profile branch.ParameterTypes.fx— the parameter-class/type sweep (scalars, vectors, matrices, arrays, structs).ParserTest.fx/PreprocessorTest.fx/DefinesTest.fx— the reference compiler's own parser and preprocessor torture tests.TextureArrayEffect.fx,CustomSpriteBatchEffect*.fx— array textures, two texture/sampler pairs, and a comparison sampler.
Two real defects surfaced the day they landed, both fixed in the same change: anonymous
technique { … } blocks were rejected outright (FX0001) even though mgfxc compiles them and
writes an empty technique name; and SamplerComparisonState on the FNA target crashed the
whole process inside vkd3d's SM1 lowering (now a loud FX0013).
Coverage note: every fixture in the corpus — these included — is exercised by the corpus-wide
VulkanCorpusStructuralTests gate, which requires each one to either produce a
structurally valid Vulkan container (combined descriptors at binding ≥ 32, unique bindings,
column-major matrices, main entry point, no SPV_GOOGLE_* extensions) or fail with a real
diagnostic. There is no skip list to quietly grow.