Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions .claude/skills/project-export/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ XnaFiddle **always** runs the user's code in the browser via KNI's **BlazorGL**

The export dialog has a single **runtime** selector (`_exportRuntime`: KNI / MonoGame / FNA); the platform checkboxes are *within* that family. So a multi-platform export is always one family — **KNI and MonoGame can never be combined**, and FNA is single-target. This is enforced in the UI (`SetExportRuntime`, the runtime radios), so `ExportMultiPlatform`'s shared common project only ever sees one family. The exporter itself hard-blocks only the FNA-mixing case (`Export(targets,…)` throws); the KNI/MonoGame split is a UI guarantee. Design consequence: per-target logic never has to reconcile two framework families in one solution.

## MonoGame 3.8.5 (preview) — policy and the `.Native` convention
## MonoGame the `.Native` shared-reference convention

**Policy — do not relitigate, do not warn.** MonoGame 3.8.5 is in preview but near shipping. Treat it as a first-class, supported target — NOT risky/experimental to be avoided or hedged. **Never warn the user that 3.8.5 is "preview."** We actively track and support it so we are ready the moment stable drops. When work touches MonoGame, prefer adopting 3.8.5's conventions.
MonoGame (currently 3.8.5, GA) is a first-class export target: `Directory.Build.props` has a single `MonoGameFrameworkVersion`, and `WindowsDX12`/`DesktopVK` (the DX12/Vulkan `MonoGame.Framework.Native` + `MonoGame.Runtime.*` backends) are regular, always-available targets like DesktopGL/WindowsDX/Android — no version gating, no preview/stable split anywhere in the codebase.

**`.Native` is the renderer-agnostic compile reference for shared libraries.** `MonoGame.Framework.Native` is a backend-agnostic *managed* framework assembly — no graphics backend baked in — that functions like a reference assembly / `netstandard` lowest-common-denominator. The 3.8.5 `mg2dstartkit` template references it from the shared `.Core` library with `PrivateAssets=All`; each platform head then supplies the concrete backend (`MonoGame.Framework.DesktopGL` / `WindowsDX` / etc. for classic backends; `MonoGame.Framework.Native` + native `MonoGame.Runtime.*.{DX12,Vulkan}` for the new backends). Compiling the shared lib against `.Native` prevents leaking renderer-specific API. Going forward, exported shared/common projects should follow this convention.
**`.Native` is the renderer-agnostic compile reference for shared libraries.** `MonoGame.Framework.Native` is a backend-agnostic *managed* framework assembly — no graphics backend baked in — that functions like a reference assembly / `netstandard` lowest-common-denominator (the convention MonoGame's own `mg2dstartkit` template uses). `GenerateCommonCsproj` in `ProjectExporter.cs` unconditionally references `MonoGame.Framework.Native` with `PrivateAssets=All` for the shared/common project whenever any MonoGame target is present; each platform head then supplies its concrete backend (`MonoGame.Framework.DesktopGL`/`WindowsDX`/`Android` for classic backends; `MonoGame.Framework.Native` + native `MonoGame.Runtime.*.{DX12,Vulkan}` for the new backends). Compiling the shared lib against `.Native` prevents leaking renderer-specific API.

**Two independent layers — do not conflate:**
- *Managed reference* — `.Native` is the agnostic contract; ANY backend's `MonoGame.Framework.dll` satisfies it at runtime. Classic and native backends are interchangeable at this layer (the template compiles `.Core` against `.Native` yet ships all-classic heads).
- *Compiled content* — effects / `.xnb` do NOT cross between the classic MGCB pipeline (GL/DX) and the new native Content Builder (DX12/Vulkan). This is the real incompatibility (e.g. Apos.Shapes / Gum shaders failing on DX12/VK) and it is orthogonal to the reference choice.

**Exporter note:** `ProjectExporter.cs` currently hardwires `MonoGame.Framework.DesktopGL` as the shared-project compile reference (~line 411, the `isMonoGame` branch). The convention-correct reference is `MonoGame.Framework.Native`.
- *Managed reference* — `.Native` is the agnostic contract; ANY backend's `MonoGame.Framework.dll` satisfies it at runtime. Classic and native backends are interchangeable at this layer.
- *Compiled content* — effects / `.xnb` do NOT cross between the classic MGCB pipeline (GL/DX) and the new native Content Builder (DX12/Vulkan). This is the real incompatibility (e.g. Apos.Shapes / Gum shaders failing on DX12/VK) and it is orthogonal to the reference choice. See `BuildPackageList`'s `MonoGameWindowsDX12`/`MonoGameDesktopVK` branch.

## The core design contract

Expand All @@ -64,7 +62,7 @@ Verified by inspecting `.nupkg` entries, `Content.mgcb`, and file timestamps.
- At the **consuming project's build**, `MonoGame.Content.Builder.Task` runs MGCB and compiles `apos-shapes.fx` → `.xnb` (EffectImporter/EffectProcessor). `Content.mgcb`'s `/outputDir:bin/$(Platform)` + `/intermediateDir:obj/$(Platform)` are relative to the **`.mgcb`'s own location**, so MGCB writes the output AND its incremental cache **inside the package cache**: `~/.nuget/packages/<lib>/<ver>/buildTransitive/Content/{bin,obj}/<Platform>/…`. The `.xnb` is then copied into the consuming project's output `Content/`.
- So a compiled `.xnb` **does** live in the NuGet cache — as **build output, not shipped content**. To distinguish shipped vs. built: list the immutable `.nupkg` entries (not the extracted folder) and cross-check timestamps (built `.xnb` is newer than the extracted source and differs per platform/build).
- **Clean-rebuild gotcha:** deleting the consuming project's `bin`/`obj` does **not** force a content recompile — MGCB's incremental cache lives in the **package folder**, so it reuses the cached `.xnb`. To simulate a fresh user, delete `…/buildTransitive/Content/{bin,obj}` (or the whole `<lib>/<ver>/` package folder, which re-extracts source only). A genuinely fresh user has only the `.fx`, so their first build always compiles it.
- This MGCB path exists only for **classic** targets (DesktopGL/WindowsDX, GL/DX). DX12/Vulkan use the separate native Content Builder — why library effects (Apos.Shapes/Gum) don't build/load there. See the *compiled content* layer in the 3.8.5 section.
- This MGCB path exists only for **classic** targets (DesktopGL/WindowsDX, GL/DX). DX12/Vulkan use the separate native Content Builder — why library effects (Apos.Shapes/Gum) don't build/load there. See the *compiled content* layer in the MonoGame `.Native` section.

## Not yet documented (grow only on confusion)

Expand Down
6 changes: 1 addition & 5 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@
<KniWasmJsVersion>8.0.11</KniWasmJsVersion>

<!-- MonoGame framework — not used by XnaFiddle itself, only in exported projects -->
<MonoGameFrameworkVersion>3.8.4.1</MonoGameFrameworkVersion>
<!-- MonoGame preview — an experimental, opt-in alternative offered by the export
version selector. Restoring a third-party lib against this prerelease may surface
dependency conflicts; that's expected for a preview, not an XnaFiddle bug. -->
<MonoGameFrameworkPreviewVersion>3.8.5-preview.6</MonoGameFrameworkPreviewVersion>
<MonoGameFrameworkVersion>3.8.5</MonoGameFrameworkVersion>

<!-- FNA.NET — opinionated third-party FNA fork, used only in exported FnaDesktop projects.
A single package supplies the framework and bundles native libs (win/macOS). -->
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ Click the **Export** button in the toolbar to download your fiddle as a complete
| Runtime | Platforms |
|---|---|
| KNI | DesktopGL, WindowsDX, Android, BlazorGL (Browser) |
| MonoGame | DesktopGL, WindowsDX, Android |
| MonoGame | DesktopGL, WindowsDX, Android, WindowsDX12, DesktopVK |

The exported project includes the correct NuGet packages, entry point (`Program.cs` / `Activity1.cs` / `Index.razor`), and any assets you've loaded. Third-party libraries (Gum, Apos.Shapes, MonoGame.Extended, FontStashSharp, Aether.Physics2D, KernSmith) are detected automatically from your code and included in the `.csproj`.

Expand Down
18 changes: 2 additions & 16 deletions XnaFiddle.BlazorGL/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@
// FNA is a single desktop target — hide every non-Desktop platform.
if (_exportRuntime == ExportRuntime.Fna && p != ExportPlatform.DesktopGL)
continue;
// WindowsDX12 and DesktopVK are MonoGame 3.8.5 preview-only backends.
if ((p == ExportPlatform.WindowsDX12 || p == ExportPlatform.DesktopVK) && !IsPreviewBackendAvailable)
// WindowsDX12 and DesktopVK are MonoGame-only backends.
if ((p == ExportPlatform.WindowsDX12 || p == ExportPlatform.DesktopVK) && _exportRuntime != ExportRuntime.MonoGame)
continue;
<label style="display: flex; align-items: center; gap: 5px; cursor: pointer; color: #ccc;">
<input type="checkbox" checked="@(_selectedPlatforms.Contains(p))"
Expand All @@ -374,20 +374,6 @@
}
</div>

@* MonoGame version selector, only shown when the MonoGame runtime is chosen. *@
@if (_exportRuntime == ExportRuntime.MonoGame)
{
<div style="display: flex; flex-direction: column; gap: 4px;">
<span style="color: #aaa; margin-bottom: 2px;">MonoGame version:</span>
<select @bind="_monoGameVersion" @bind:after="PruneUnavailablePlatforms"
style="background: #3c3c3c; color: #d4d4d4; border: 1px solid #555; padding: 3px 8px;
font-size: 12px; font-family: inherit;">
<option value="@PackageVersions.MonoGameFramework">@PackageVersions.MonoGameFramework (stable)</option>
<option value="@PackageVersions.MonoGameFrameworkPreview">@PackageVersions.MonoGameFrameworkPreview (preview - experimental)</option>
</select>
</div>
}

@* Shader-compilation choice — only meaningful for MonoGame (the classic MGCB pipeline)
and only when the fiddle actually has shader (.fx) tabs to compile. *@
@if (_exportRuntime == ExportRuntime.MonoGame && _shaderTabs.Count > 0)
Expand Down
20 changes: 5 additions & 15 deletions XnaFiddle.BlazorGL/Pages/Index.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ enum ExportPlatform { DesktopGL, WindowsDX, Android, BlazorGL, WindowsDX12, Desk
bool _isExporting;
ExportRuntime _exportRuntime = ExportRuntime.Kni;
HashSet<ExportPlatform> _selectedPlatforms = new() { ExportPlatform.DesktopGL };
// MonoGame framework version chosen in the export panel's version selector. Only meaningful
// when the MonoGame runtime is selected; defaults to the stable release. The preview option
// is experimental — a third-party lib restore conflict on it is expected, not our bug.
string _monoGameVersion = PackageVersions.MonoGameFramework;
// How exported .fx shaders are compiled. Runtime ShadowDusk is the default and the only option
// that works across every target; the MonoGame Content Pipeline (MGCB, build-time .xnb) is an
// opt-in honored only on classic MonoGame targets (the export dialog shows it just for MonoGame).
Expand Down Expand Up @@ -1773,16 +1769,12 @@ void SetExportRuntime(ExportRuntime runtime)
PruneUnavailablePlatforms();
}

// WindowsDX12 and DesktopVK are MonoGame 3.8.5 preview-only backends. Keep them offered
// (and selected) only while the MonoGame runtime and the preview version are both chosen;
// otherwise drop them so a hidden checkbox can't silently stay in the export.
bool IsPreviewBackendAvailable =>
_exportRuntime == ExportRuntime.MonoGame
&& _monoGameVersion == PackageVersions.MonoGameFrameworkPreview;

void PruneUnavailablePlatforms()
{
if (!IsPreviewBackendAvailable)
// WindowsDX12 and DesktopVK are MonoGame-only backends (not available for KNI or FNA
// runtimes). Keep them offered (and selected) only while the MonoGame runtime is chosen;
// otherwise drop them so a hidden checkbox can't silently stay in the export.
if (_exportRuntime != ExportRuntime.MonoGame)
{
_selectedPlatforms.Remove(ExportPlatform.WindowsDX12);
_selectedPlatforms.Remove(ExportPlatform.DesktopVK);
Expand Down Expand Up @@ -1812,8 +1804,6 @@ private async Task ExportProject()

var assets = InMemoryContentManager.Files;
var targets = GetExportTargets();
// Pass the chosen MonoGame version only for MonoGame exports; KNI/FNA ignore it (null).
string monoGameVersion = _exportRuntime == ExportRuntime.MonoGame ? _monoGameVersion : null;

// Ship each open shader's live .fx SOURCE so the exported project recompiles it at runtime
// via ShadowDusk (issue #39). Re-collect fresh — _shareShaders is only refreshed on
Expand All @@ -1833,7 +1823,7 @@ private async Task ExportProject()
? _shaderCompileMode
: ShaderCompileMode.ShadowDusk;
byte[] zipBytes = ProjectExporter.Export(code, targets, projectName, assets: assets.Count > 0 ? assets : null,
libraryRegistry: LibraryRegistry, monoGameVersion: monoGameVersion, shaders: shaders, shaderCompileMode: shaderMode);
libraryRegistry: LibraryRegistry, shaders: shaders, shaderCompileMode: shaderMode);
string base64 = Convert.ToBase64String(zipBytes);
await JsRuntime.InvokeVoidAsync("downloadFile", projectName + ".zip", base64);
}
Expand Down
Loading