Skip to content

Commit a54a963

Browse files
vchelaruclaude
andauthored
Add MonoGame 3.8.5 Content Builder export mode (closes DX12/Vulkan shader gate) (#120)
* Flip MonoGame export to the stable 3.8.5 GA release 3.8.5 shipped stable on nuget.org, replacing the preview line. Removes the now-dead preview/stable duality (version props, UI selector, .Native version-gating, the monoGameVersion override) — WindowsDX12 and DesktopVK are now regular, always-available MonoGame targets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add MonoGame 3.8.5 Content Builder export mode (closes DX12/Vulkan shader gate) Opt-in ContentBuildMode.ContentBuilder routes assets and shaders through MonoGame's new code-first Content Builder (Builder.cs + BuildContent.targets) instead of raw/ShadowDusk/MGCB, superseding ShaderCompileMode when chosen. Unlike mgcb.exe it isn't limited to a fixed platform list, so it also closes the DX12/Vulkan shader gate from issue #52. MonoGame.Content.Builder.Task stays installed on classic targets for buildTransitive-only libraries (Apos.Shapes/Gum) that the new pipeline can't see. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add SM6/Vulkan shader branch to all 16 example .fx files MonoGame's DX12/Vulkan Content Builder compiles via DXC, which requires Shader Model 6 (vs_6_0/ps_6_0), rejects the legacy sampler2D/tex2D combo, and requires SV_Target0 instead of COLOR/COLOR0 for the pixel output. None of the example shaders had an SM6 branch, so ContentBuilder exports to MonoGameWindowsDX12/MonoGameDesktopVK failed to compile every shader. Adds an `#elif defined(SM6) || defined(VULKAN)` branch to each file's shader-model defines, a matching Texture2D<float4>/SamplerState declaration for the texture(s), and a fully separate SM6 pixel-shader entry point (ShadowDusk's OpenGL translator requires the classic `: COLOR` / `tex2D(...)` entry point to stay literal and unsplit, so the two shader models get independent function bodies rather than sharing one via macros or a split signature). The `#if OPENGL` / classic branches are untouched. Verified: ShadowDuskCLI /Profile:OpenGL produces byte-identical .mgfx output before/after for all 16 files; a real `dotnet build` of a ContentBuildMode.ContentBuilder export targeting MonoGameWindowsDX12 compiles all 16 shaders with zero errors; full XnaFiddle.Tests suite (337 tests) passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Bump ShadowDusk to 0.12.0, wire MonoGameDesktopVK shaders via its new Vulkan backend ShadowDusk 0.12.0 adds a real Vulkan backend that compiles the same plain .fx source as every other target (no SM6 source rewrite needed, verified against the pre-SM6-branch form of Grayscale.fx via ShadowDuskCLI and a real dotnet build). GetShaderExportInfo now wires MonoGameDesktopVK through ShadowDusk.Compiler/PlatformTarget.Vulkan, closing the DesktopVK half of issue #52; MonoGameWindowsDX12 is unchanged (still gated, still needs ContentBuildMode.ContentBuilder for shaders). Verified the 0.11.0 -> 0.12.0 GL codegen fidelity change (pow/division strength-reduction) produces byte-identical .mgfx for all 16 bundled example shaders, so no rendering regression risk there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Collapse export dialog's two shader/content radio groups into one "Shader compilation:" and "Content pipeline:" silently overrode each other and both used the words "Content Pipeline"/"MGCB" for different things. Replaced with a single "Content strategy:" group (ShadowDusk / Classic MGCB / MonoGame Content Builder) backed by one UI-only ContentStrategy enum that maps to ProjectExporter's existing (ShaderCompileMode, ContentBuildMode) pair. No changes to ProjectExporter's public API. * Rework MonoGame export into orthogonal content/shader axes Splits the 3-way content strategy choice into two independent enums: ContentBuildMode (Raw/ClassicMgcb/ContentBuilder) for non-shader assets, and ShaderCompileMode (ShadowDusk/Native, renamed from ContentPipeline) for .fx compilation. ShadowDusk is now the default shader compiler under every content strategy, not just Raw - previously Content Builder mode silently forced shaders through its own native pipeline. ClassicMgcb becomes a real asset pipeline for the first time: .png/.wav compile via TextureImporter/WavImporter, with ColorKeyEnabled=False forced to stop MonoGame's default magenta color-keying from punching holes in shipped textures. DX12 was NOT wired into GetShaderExportInfo - checked ShadowDusk's NuGet feed and PlatformTarget.cs; still 0.12.0 with no DirectX12 backend (README lists it "Not yet"). CompilesShippedShaders(DX12, ShadowDusk, ContentBuilder) stays gated until ShadowDusk ships one. Fixed a bug surfaced by the axis decoupling: a ShadowDusk-mode .fx can now stay in the head's own Content/ under ContentBuildMode.ContentBuilder, but the csproj's copy-to-output gate assumed nothing ever did, so the shader shipped in the zip but never reached the build output. UI: two independent radio groups replace the single 3-way choice; Raw shows a fixed "ShadowDusk (fixed)" label since it has no native pipeline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix .slnx startup project and drop redundant "(default)" radio labels Multi-platform exports listed the shared Common library and Content Builder console project before any actual game project — Visual Studio picks the first .slnx entry as the default startup project, so users had to manually retarget it every time. Platform-head projects now come first. Also refreshes the two now-stale skill docs (project-export, shaders) and CLAUDE.md's skill-maintenance policy for the orthogonal content/shader axes rework, and removes "(default)" from radio labels that snuck back in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent b1d95bb commit a54a963

26 files changed

Lines changed: 1553 additions & 179 deletions

.claude/skills/project-export/SKILL.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,22 @@ Third-party library packages are added per-target by scanning the user's source
4848

4949
Exports honor the contract above for shaders by shipping the **`.fx` source** (into `Content/`) plus a **ShadowDusk `PackageReference`**, and recompiling at runtime — no XNB, no MGCB. `Export` takes a `shaders` (`name.fx -> HLSL`) map. The seam: the shared/common project references **`ShadowDusk.Core`** (the `IShaderCompiler` interface, net8.0, no natives) and the generated content manager has an `Effect` branch that compiles against it; each **per-platform** project references the concrete compiler (`ShadowDusk.Compiler` desktop+FNA / `ShadowDusk.Wasm` Blazor) and its entry point injects it + the `PlatformTarget` (GL vs DX vs `Fna` is just that value; FNA emits legacy D3D9 `.fxb` instead of `.mgfx`). `ProjectExporter.SupportsRuntimeShaders(target)` is the single source of truth for which targets are wired (desktop GL/DX + Blazor + FNA Desktop); Android/iOS and MonoGame DX12/VK are gated (ship `.fx`, no compiler) — issue #52. Full detail lives in the **`shaders`** skill.
5050

51-
### Opt-in MGCB shader mode (`ShaderCompileMode.ContentPipeline`)
51+
### Two orthogonal axes: `ContentBuildMode` and `ShaderCompileMode` (issue #52 follow-up)
5252

53-
`Export` takes a `shaderCompileMode` (default `ShadowDusk`). `ContentPipeline` routes user `.fx` through the **classic MonoGame Content Pipeline** (build-time `.xnb`) instead of ShadowDusk — for a canonical, ShadowDusk-free MonoGame project. It is honored **only on classic MonoGame targets** (`IsMonoGameClassic`: DesktopGL/WindowsDX/Android); every other target ignores it and stays ShadowDusk. Per-target strategy resolves to ShadowDusk / Mgcb / Gated via `UsesShadowDuskShaders` / `UsesMgcbShaders`; `CompilesShippedShaders(target, mode)` is the mode-aware "will shaders load here" predicate the dialog uses for the gated-platform message.
53+
`Export` takes two independent enums instead of one conflated choice:
5454

55-
MGCB path mechanics: emit a `Content.mgcb` (EffectImporter/EffectProcessor per `.fx`) + a `<MonoGameContentReference>`, drop the `.fx`/`.mgcb` from the raw `<None>` copy (Exclude on Include forms, `<None Remove>` on the single-platform Update form), and **suppress** all ShadowDusk wiring for that head (no package, no entry-point injection, no `Effect` branch — `Content.Load<Effect>` falls through `RawContentManager` to the stock loader reading the `.xnb`). One **shared** `Content.mgcb` serves every head: `MonoGame.Content.Builder.Task` overrides `/platform` (and out/intermediate dirs) per project from `$(MonoGamePlatform)` at build time, so the file's `/platform` line is just a default. `/profile` is **not** overridden — set to `HiDef` to match how the editor runs shaders. The existing MGCB infra (`NeedsMgcbToolManifest` = `IsMonoGameClassic`, the dotnet-mgcb manifest, the Mark-of-the-Web unblock) is reused unchanged.
55+
- **`ContentBuildMode`** — how non-shader `Content/` assets get built: `Raw` (ship as-is, the default), `ClassicMgcb` (legacy `dotnet-mgcb`; `.png`/`.wav` compile to `.xnb` via `TextureImporter`/`WavImporter`, classic MonoGame targets only — `IsMonoGameClassic`), or `ContentBuilder` (MonoGame 3.8.5 GA's code-first Content Builder, wired for **every** MonoGame target including DX12/Vulkan since it isn't limited to a fixed platform list).
56+
- **`ShaderCompileMode`** — how `.fx` gets compiled: `ShadowDusk` (default; ship source, recompile at runtime — same as the in-browser editor) or `Native` (compile at build time via whichever pipeline `ContentBuildMode` selected). **`Native` is a per-content-strategy opt-out from ShadowDusk, not a third content strategy** — ShadowDusk is the default shader compiler under all three `ContentBuildMode` values, including `ClassicMgcb`/`ContentBuilder`. Picking a content strategy alone does not force shaders through its native compiler.
57+
58+
Resolution predicates (`ProjectExporter.cs`): `UsesClassicMgcbAssets`/`UsesContentBuilder` resolve axis A per-target; `UsesClassicMgcbShaders`/`UsesContentBuilderShaders` resolve axis B *against* axis A (`Native` is a no-op unless the matching content strategy is also selected for that target); `UsesNativeShaders` is their union; `UsesShadowDuskShaders` is "supported target and Native wasn't actually selected here" (so e.g. `Native`+`ClassicMgcb` on `MonoGameDesktopVK`, which has no classic MGCB, falls through to ShadowDusk). `CompilesShippedShaders(target, shaderMode, contentMode)` is the combined "will shaders load here at all" predicate the dialog uses for the gated-platform message. `SupportsContentBuilder(target)` gates offering the Content Builder radio (every MonoGame target).
59+
60+
**Asset routing** (`GenerateCsproj`/`Export`): `PipelineAssetImporters` lists the only two extensions with a real stock importer (`.png`→`TextureImporter`/`TextureProcessor` with `ColorKeyEnabled=False` forced — MonoGame's default color-keys pure magenta to transparent, which would silently punch holes in a shipped `.png`; `.wav`→`WavImporter`/`SoundEffectProcessor`). `NonPipelineAssetExtensions` (`.achx`, `.fnt`, `.ttf`, `.ember`, `.xnb`, tilemap/level/text-data formats) always ships raw regardless of `ContentBuildMode` — no importer exists for them, and `.xnb` is already-compiled output. Under `ContentBuilder`, pipeline-eligible assets route into the separate `{projectName}.Content/Assets/` folder (picked up by its `WildcardRule("*")`); everything else, including a `ShadowDusk`-mode `.fx`, stays in the head's own `Content/`. Under `ClassicMgcb`, `.png`/`.wav` stay physically in `Content/` (same as `Raw`) — only the csproj's copy-to-output `<None>` exclusion changes for them, since `Content.mgcb` compiles them in place.
61+
62+
MGCB path mechanics (`GenerateContentMgcb`): emits one `Content.mgcb` per export covering whichever of shaders/assets route through it — an `EffectImporter`/`EffectProcessor` block per `.fx` when `useClassicMgcbShaders`, plus an importer/processor block per `PipelineAssetImporters`-matched asset independently (so a `ClassicMgcb` export with `ShadowDusk` shaders still gets a valid `.mgcb` covering just its `.png`/`.wav`). `MonoGame.Content.Builder.Task` overrides `/platform` (and out/intermediate dirs) per project from `$(MonoGamePlatform)` at build time, so the file's `/platform` line is just a default. `/profile` is **not** overridden — set to `HiDef` to match how the editor runs shaders. The existing MGCB infra (`NeedsMgcbToolManifest` = `IsMonoGameClassic`, the dotnet-mgcb manifest, the Mark-of-the-Web unblock) is reused unchanged.
63+
64+
**Export dialog UI** (`Index.razor`/`.cs`): two independent radio groups — "Content strategy:" (Raw / Classic MGCB / Content Builder) always shown for MonoGame, and "Shader compiler:" (ShadowDusk / Native) shown only when content strategy ≠ `Raw`. `EffectiveContentModes()` collapses back to `(ShadowDusk, Raw)` when the runtime isn't MonoGame.
65+
66+
**MonoGame DX12 gate:** `MonoGameWindowsDX12` shaders stay gated under ShadowDusk (no DX12 backend there) — only `ContentBuildMode.ContentBuilder` + `ShaderCompileMode.Native` closes that gap today. `MonoGameDesktopVK` has a ShadowDusk Vulkan backend, so it isn't gated under the default `ShadowDusk` mode.
5667

5768
## Library MGCB content compiles into the NuGet cache at build time (not shipped)
5869

.claude/skills/shaders/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ User-facing pattern: `Content.Load<Effect>("Name")` in `LoadContent`, optionally
6767
| **Share URL** (`#code=...&shaders=...`) | Yes — `_shareShaders` JSON in a `&shaders=` fragment |
6868
| **Snippet** (`#snippet=...`) | Yes — `SnippetModel.Shaders` (`List<ShaderFile>`). Not consumed by `SnippetExpander`; the page applies them separately via `ApplyShaderFilesAsync` |
6969
| **Gist** | Partly — the copy-to-clipboard puts **only the C#**; the UI warns the user to add each `.fx` as a separate gist file (same filename). Gist **import** reads every `.fx` file back into tabs |
70-
| **Export** | Yes (issue #39) — the `.fx` **source** ships in the export `Content/` and the exported project recompiles it at runtime via ShadowDusk (no XNB/MGCB), exactly like the editor. `ProjectExporter.Export` takes a `shaders` (`name.fx -> HLSL`) map; the export call site re-collects via `CollectShaderFilesAsync()`. Wired for desktop GL/DX (`ShadowDusk.Compiler`, `PlatformTarget.OpenGL`/`DirectX`), Blazor (`ShadowDusk.Wasm`, awaits `InitializeAsync` before the render loop), FNA (`ShadowDusk.Compiler`, `PlatformTarget.Fna` → legacy D3D9 `.fxb`, loaded by FNA's MojoShader `Effect` ctor — issue #54), and Android (`ShadowDusk.Compiler` with on-device native backends — issue #88). iOS and MonoGame DX12/VK are **gated** (ship `.fx`, no compiler wired) — issue #52. **Opt-in alternative:** `Export`'s `shaderCompileMode` (default `ShadowDusk`) can be `ContentPipeline` to compile user `.fx` via classic **MGCB** at build time (`.xnb`) instead — honored only on classic MonoGame targets, producing a canonical ShadowDusk-free project. See the `project-export` skill. |
70+
| **Export** | Yes (issue #39) — the `.fx` **source** ships in the export `Content/` (or the Content Builder's `Assets/`, see below) and, by default, the exported project recompiles it at runtime via ShadowDusk, exactly like the editor. `ProjectExporter.Export` takes a `shaders` (`name.fx -> HLSL`) map; the export call site re-collects via `CollectShaderFilesAsync()`. Wired for desktop GL/DX (`ShadowDusk.Compiler`, `PlatformTarget.OpenGL`/`DirectX`), Blazor (`ShadowDusk.Wasm`, awaits `InitializeAsync` before the render loop), FNA (`ShadowDusk.Compiler`, `PlatformTarget.Fna` → legacy D3D9 `.fxb`, loaded by FNA's MojoShader `Effect` ctor — issue #54), Android (`ShadowDusk.Compiler` with on-device native backends — issue #88), and MonoGame DesktopVK (ShadowDusk 0.12.0+ Vulkan backend). iOS and MonoGame DX12 are **gated** under ShadowDusk (ship `.fx`, no compiler wired) — issue #52. **Opt-in alternative:** `Export`'s `shaderCompileMode` (default `ShadowDusk`) can be `Native` (renamed from `ContentPipeline`) to compile user `.fx` at build time via whichever `ContentBuildMode` content strategy is selected (classic MGCB's `EffectProcessor`, or the Content Builder's own pipeline) instead — `Native` under `ContentBuilder` is also the only way to close the MonoGame DX12 gate. See the `project-export` skill for the full two-axis (`ContentBuildMode` x `ShaderCompileMode`) model. |
7171

7272
`ShaderFile` (`ShaderFile.cs`) is the model: `Name` (filename incl. `.fx`) + `Source` (HLSL). `CollectShaderFilesAsync` snapshots open tabs into `_shareShaders` (refreshed on share/snippet build because shader edits don't fire the C#-only content callback). `ApplyShaderFilesAsync` clears stale tabs then re-opens the payload's shaders.
7373

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ XnaFiddle is a standalone KNI game runner with an in-browser C# editor. It is a
88

99
On **any** task — research, maintenance, new features, bug fixes, refactors, docs, reviews — first check whether an available skill applies, and load it before proceeding. Skills (e.g. `file-loading`, `intellisense`, `verify`, `run`, `code-review`) carry condensed, repo-specific knowledge that prevents rediscovering subsystem details from scratch. When a skill matches the work, invoke it; if none fit, say so briefly and continue. This check is in addition to, not a replacement for, the Agent Workflow below.
1010

11-
**Proactively suggest a new or updated skill** when — and only when — you finish work having re-derived reusable subsystem knowledge that no skill covers, or having found an existing skill stale/wrong. Don't wait to be asked; surface it as a suggestion and let the user decide (don't create one unprompted). But the bar is **high**: every skill line costs tokens on every relevant load, so err toward *not* proposing. Only suggest when you're confident the knowledge will be re-hit by a concrete upcoming task, not for one-off facts the code already records or that this conversation alone needed. When unsure, stay silent.
11+
**Finding an existing skill stale or wrong** → fix it directly, no suggestion or confirmation needed. This is correcting an inaccuracy, not a new-content judgment call.
12+
13+
**Proactively suggest a new skill** when — and only when — you finish work having re-derived reusable subsystem knowledge that no skill covers. Don't wait to be asked; surface it as a suggestion and let the user decide (don't create one unprompted). But the bar is **high**: every skill line costs tokens on every relevant load, so err toward *not* proposing. Only suggest when you're confident the knowledge will be re-hit by a concrete upcoming task, not for one-off facts the code already records or that this conversation alone needed. When unsure, stay silent.
1214

1315
## Agent Workflow
1416

Directory.Build.props

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,19 @@
6868
property feeds the editor's PackageReference AND the exporter's PackageVersions.ShadowDusk.
6969
0.5.0+ is required: it adds InitializeAsync() + a synchronous Compile(), which the exported
7070
project needs to compile lazily inside the synchronous Content.Load<Effect> call site. -->
71-
<ShadowDuskVersion>0.11.0</ShadowDuskVersion>
71+
<ShadowDuskVersion>0.12.0</ShadowDuskVersion>
72+
73+
<!-- MonoGame 3.8.5 GA Content Builder (opt-in ContentBuildMode.ContentBuilder) — the new
74+
code-first content pipeline's own dependencies, pinned to what MonoGame's 3.8.5 GA
75+
templates reference. MonoGame.Framework.Content.Pipeline and MonoGame.Framework.Native
76+
reuse MonoGameFrameworkVersion above instead of getting their own property. -->
77+
<MonoGameLibraryFreeTypeVersion>2.13.2.3</MonoGameLibraryFreeTypeVersion>
78+
<MonoGameLibraryMojoShaderVersion>1.0.0.5</MonoGameLibraryMojoShaderVersion>
79+
<MonoGameToolBasisuVersion>2.0.2.1</MonoGameToolBasisuVersion>
80+
<MonoGameToolCrunchVersion>1.0.4.7</MonoGameToolCrunchVersion>
81+
<MonoGameToolDxcVersion>1.8.2505.11</MonoGameToolDxcVersion>
82+
<MonoGameToolFFmpegVersion>7.0.0.10</MonoGameToolFFmpegVersion>
83+
<MonoGameToolFFprobeVersion>7.0.0.10</MonoGameToolFFprobeVersion>
7284
</PropertyGroup>
7385

7486
</Project>

XnaFiddle.BlazorGL/Examples/Bloom.BloomCombine.fx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@
22
#define SV_POSITION POSITION
33
#define VS_SHADERMODEL vs_3_0
44
#define PS_SHADERMODEL ps_3_0
5+
#elif defined(SM6) || defined(VULKAN)
6+
// MonoGame's native Content Builder (DX12/Vulkan) compiles via DXC, which requires Shader
7+
// Model 6 and rejects the classic sampler2D/tex2D combo below -- see the Texture2D/SamplerState
8+
// branch and the SM6 sampling call sites further down.
9+
#define VS_SHADERMODEL vs_6_0
10+
#define PS_SHADERMODEL ps_6_0
511
#else
612
#define VS_SHADERMODEL vs_4_0_level_9_1
713
#define PS_SHADERMODEL ps_4_0_level_9_1
814
#endif
915

16+
#if defined(SM6) || defined(VULKAN)
17+
Texture2D<float4> SpriteTexture : register(t0); // slot 0 — SpriteBatch binds the drawn (BLOOM) texture here
18+
SamplerState BloomSampler : register(s0);
19+
20+
Texture2D<float4> BaseTexture : register(t1); // the original scene, set from C# as a parameter
21+
SamplerState BaseSampler : register(s1);
22+
#else
1023
Texture2D SpriteTexture; // slot 0 — SpriteBatch binds the drawn (BLOOM) texture here
1124
sampler2D BloomSampler = sampler_state { Texture = <SpriteTexture>; };
1225

1326
Texture2D BaseTexture; // the original scene, set from C# as a parameter
1427
sampler2D BaseSampler = sampler_state { Texture = <BaseTexture>; };
28+
#endif
1529

1630
float BloomIntensity;
1731
float BaseIntensity;
@@ -32,6 +46,29 @@ float3 AdjustSaturation(float3 color, float saturation)
3246
return lerp(grey.xxx, color, saturation);
3347
}
3448

49+
// ShadowDusk's OpenGL translator (and the real MonoGame Content Builder's DXC pass) rewrite/validate
50+
// the pixel shader entry point by matching a literal, unconditioned `: COLOR`/`tex2D(...)` shape --
51+
// splitting the signature or a call site across an #if breaks that (verified against the real
52+
// ShadowDuskCLI tool and a real DX12 Content Builder build while fixing issue #52's SM6 gap: SM6
53+
// also requires `SV_Target0` instead of `COLOR`). So the two shader models get fully separate,
54+
// self-contained entry points instead of one shared function with an internal #if.
55+
#if defined(SM6) || defined(VULKAN)
56+
float4 MainPS(VertexShaderOutput input) : SV_Target0
57+
{
58+
float3 bloom = SpriteTexture.Sample(BloomSampler, input.TextureCoordinates).rgb;
59+
float3 base = BaseTexture.Sample(BaseSampler, input.TextureCoordinates).rgb;
60+
61+
bloom = AdjustSaturation(bloom, BloomSaturation) * BloomIntensity;
62+
base = AdjustSaturation(base, BaseSaturation) * BaseIntensity;
63+
64+
// Darken the base where the bloom is strong so bright glows don't wash out to white,
65+
// then add. With the intensities at 1 this is a screen-style blend that stays in
66+
// [0,1] and cannot clip — which is what keeps the glow the right hue.
67+
base *= (1.0 - saturate(bloom));
68+
69+
return float4(base + bloom, 1.0);
70+
}
71+
#else
3572
float4 MainPS(VertexShaderOutput input) : COLOR
3673
{
3774
float3 bloom = tex2D(BloomSampler, input.TextureCoordinates).rgb;
@@ -47,5 +84,6 @@ float4 MainPS(VertexShaderOutput input) : COLOR
4784

4885
return float4(base + bloom, 1.0);
4986
}
87+
#endif
5088

5189
technique BasicColorDrawing { pass P0 { PixelShader = compile PS_SHADERMODEL MainPS(); } }

XnaFiddle.BlazorGL/Examples/Bloom.BloomExtract.fx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@
22
#define SV_POSITION POSITION
33
#define VS_SHADERMODEL vs_3_0
44
#define PS_SHADERMODEL ps_3_0
5+
#elif defined(SM6) || defined(VULKAN)
6+
// MonoGame's native Content Builder (DX12/Vulkan) compiles via DXC, which requires Shader
7+
// Model 6 and rejects the classic sampler2D/tex2D combo below -- see the Texture2D/SamplerState
8+
// branch and the SM6 sampling call site further down.
9+
#define VS_SHADERMODEL vs_6_0
10+
#define PS_SHADERMODEL ps_6_0
511
#else
612
#define VS_SHADERMODEL vs_4_0_level_9_1
713
#define PS_SHADERMODEL ps_4_0_level_9_1
814
#endif
915

16+
#if defined(SM6) || defined(VULKAN)
17+
Texture2D<float4> SpriteTexture : register(t0);
18+
SamplerState SpriteTextureSampler : register(s0);
19+
#else
1020
Texture2D SpriteTexture;
1121

1222
sampler2D SpriteTextureSampler = sampler_state
1323
{
1424
Texture = <SpriteTexture>;
1525
};
26+
#endif
1627

1728
// Bright-pass cutoff in [0,1]. A pixel blooms only when its brightness (max channel)
1829
// exceeds Threshold; the whole color is then scaled uniformly by how far above it is,
@@ -28,6 +39,25 @@ struct VertexShaderOutput
2839
float2 TextureCoordinates : TEXCOORD0;
2940
};
3041

42+
// ShadowDusk's OpenGL translator (and the real MonoGame Content Builder's DXC pass) rewrite/validate
43+
// the pixel shader entry point by matching a literal, unconditioned `: COLOR`/`tex2D(...)` shape --
44+
// splitting the signature or a call site across an #if breaks that (verified against the real
45+
// ShadowDuskCLI tool and a real DX12 Content Builder build while fixing issue #52's SM6 gap: SM6
46+
// also requires `SV_Target0` instead of `COLOR`). So the two shader models get fully separate,
47+
// self-contained entry points instead of one shared function with an internal #if.
48+
#if defined(SM6) || defined(VULKAN)
49+
float4 MainPS(VertexShaderOutput input) : SV_Target0
50+
{
51+
float4 c = SpriteTexture.Sample(SpriteTextureSampler, input.TextureCoordinates);
52+
// Gate on overall brightness (the max channel) and scale the ORIGINAL color by the
53+
// same factor on every channel. Uniform scaling preserves hue, so an orange square
54+
// blooms orange and cyan blooms cyan. (The old per-channel threshold crushed each
55+
// color's weaker channel, collapsing every neon hue toward a pure R/G/B primary.)
56+
float brightness = max(c.r, max(c.g, c.b));
57+
float k = saturate((brightness - Threshold) / max(1.0 - Threshold, 0.0001));
58+
return float4(c.rgb * k, 1.0) * input.Color;
59+
}
60+
#else
3161
float4 MainPS(VertexShaderOutput input) : COLOR
3262
{
3363
float4 c = tex2D(SpriteTextureSampler, input.TextureCoordinates);
@@ -39,6 +69,7 @@ float4 MainPS(VertexShaderOutput input) : COLOR
3969
float k = saturate((brightness - Threshold) / max(1.0 - Threshold, 0.0001));
4070
return float4(c.rgb * k, 1.0) * input.Color;
4171
}
72+
#endif
4273

4374
technique BasicColorDrawing
4475
{

0 commit comments

Comments
 (0)