docs(spec): add desktop + web (WebGPU) platform support

- WebGPU-only web target (Bevy forces WebGPU/WebGL2 at build time; WebGL2
  lacks storage buffers our planets feature needs)
- Add §13 Platform support: build toolchain (trunk/wasm32), backend gating,
  fallback message, web defaults (render_scale 0.75, steps 200)
- Add src/web.rs, web/index.html, Trunk.toml to file structure
- Add web-sys dep; add web build + perf verification milestones
- Update storage-buffer risk note: valid on native + WebGPU
This commit is contained in:
xfy 2026-07-09 18:30:56 +08:00
parent 60bfdb9ca6
commit c1bfe2b25e

View File

@ -2,7 +2,7 @@
**Date:** 2026-07-09 **Date:** 2026-07-09
**Status:** Draft (awaiting user review) **Status:** Draft (awaiting user review)
**Project:** `singularity-rs` (Rust, edition 2024, Bevy 0.19) **Project:** `singularity-rs` (Rust, edition 2024, Bevy 0.19) — **desktop + web (WebGPU)**
## 0. Dependencies ## 0. Dependencies
@ -11,9 +11,13 @@
bevy = "0.19" # engine (first release on Rust edition 2024) bevy = "0.19" # engine (first release on Rust edition 2024)
bevy_egui = "0.41" # UI panel; depends on bevy ^0.19 + egui ^0.35 bevy_egui = "0.41" # UI panel; depends on bevy ^0.19 + egui ^0.35
# (rand/noise for procedural stars & disk texture — chosen in plan phase) # (rand/noise for procedural stars & disk texture — chosen in plan phase)
[dependencies.web-sys] # web only; see §13
version = "0.3"
features = ["Window"] # expanded in plan phase as needed
``` ```
Bevy feature flags: default. No heavy features needed (no 3D scene, no audio). The full-screen `Material2d` path uses Bevy's core pipeline only. Bevy feature flags: default. No heavy features needed (no 3D scene, no audio). The full-screen `Material2d` path uses Bevy's core pipeline only. Storage buffers (planets) are usable on both native and WebGPU.
## 1. Goal ## 1. Goal
@ -159,6 +163,7 @@ src/
params.rs # BlackHoleParams resource (CPU mirror of uniforms) params.rs # BlackHoleParams resource (CPU mirror of uniforms)
camera.rs # Orbit camera input controller camera.rs # Orbit camera input controller
ui.rs # egui "Controls" panel (collapsible sections) ui.rs # egui "Controls" panel (collapsible sections)
web.rs # #[cfg(wasm32)] glue: WebGPU backend, fallback message, canvas
scene/ scene/
mod.rs mod.rs
disk.rs # Disk parameters & defaults disk.rs # Disk parameters & defaults
@ -177,6 +182,9 @@ assets/shaders/
stars.wgsl # Procedural starfield stars.wgsl # Procedural starfield
skybox.wgsl # Cubemap sampling skybox.wgsl # Cubemap sampling
common.wgsl # Shared structs (SphereData, params, camera), constants common.wgsl # Shared structs (SphereData, params, camera), constants
web/ # web build inputs (Trunk)
index.html # canvas + fallback message container
Trunk.toml # trunk build config (wasm32-unknown-unknown)
``` ```
## 9. Parameters (`BlackHoleParams`) ## 9. Parameters (`BlackHoleParams`)
@ -224,14 +232,43 @@ This is primarily a visual artifact; verification strategy:
5. Doppler asymmetry: one side brighter, bright side shifts as camera orbits. 5. Doppler asymmetry: one side brighter, bright side shifts as camera orbits.
6. Lensing of stars visible near the hole edge. 6. Lensing of stars visible near the hole edge.
7. (Feature) grid bends near the hole; (feature) planet arcs near the hole. 7. (Feature) grid bends near the hole; (feature) planet arcs near the hole.
- Performance budget check: confirm ≥60fps at `render_scale=1.0`, Steps=300, in Phase 1. - Performance budget check: confirm ≥60fps at `render_scale=1.0`, Steps=300, in Phase 1 on desktop; confirm interactive (≥30fps) at `render_scale=0.75`, Steps=200 in a WebGPU browser.
- **Web build check:** `trunk build --release` produces a wasm artifact that loads in Chrome/Edge, renders the same scene, the egui panel is usable, and a non-WebGPU browser shows the fallback message instead of a blank canvas.
## 12. Risks & mitigations ## 12. Risks & mitigations
| Risk | Mitigation | | Risk | Mitigation |
|---|---| |---|---|
| RK4 too slow at 300 steps/full-res | Make steps + render_scale live params; profile early. Phase 1 default can drop to 200 steps. | | RK4 too slow at 300 steps/full-res | Make steps + render_scale live params; profile early. Phase 1 default can drop to 200 steps. |
| WGSL storage-buffer support edge cases for planets | Bound loop with `MAX_PLANETS` constant; fall back to uniform array if needed. | | WGSL storage-buffer support edge cases for planets | Bound loop with `MAX_PLANETS` constant; storage buffers are valid on both native and WebGPU (our only web target). |
| Flamm grid adds noise/visual clutter | Off by default; additive + faded; separate toggle. | | Flamm grid adds noise/visual clutter | Off by default; additive + faded; separate toggle. |
| Kerr math instability (Phase 2) | Isolated to Phase 2; adaptive step; does not block Phase 1. | | Kerr math instability (Phase 2) | Isolated to Phase 2; adaptive step; does not block Phase 1. |
| Secondary images absent (loop terminates early) | Explicit non-termination policy across photon sphere (Section 4). | | Secondary images absent (loop terminates early) | Explicit non-termination policy across photon sphere (Section 4). |
| Web performance lower than desktop | Web target starts at `render_scale=0.75`, steps=200 (tunable in UI); lazy/integration cost is GPU-bound regardless of platform. |
| Browser WebGPU not available | Detect `navigator.gpu` failure, show a plain HTML/CSS message via the `web` shim telling the user to use a WebGPU-capable browser (Chrome/Edge/modern Firefox & Safari). |
## 13. Platform support (desktop + web/WebGPU)
The app runs on **desktop** (native wgpu: Vulkan/Metal/D3D12) and **web** via **WebGPU** (WebGL2 is intentionally not targeted — see constraints below).
### 13.1 Why WebGPU only (not WebGL2)
- Bevy/wgpu forces a compile-time choice between WebGPU and WebGL2; they cannot coexist in one binary (bevy issue #13168).
- **WebGL2 has no storage buffers** (`max_storage_buffers_per_stage = 0`), which our planets feature depends on. WebGPU supports them, as does native.
- WebGPU is now in Chrome, Edge, and recent Firefox/Safari. For unsupported browsers we show a fallback message rather than silently failing.
### 13.2 Build & toolchain
- **Desktop:** `cargo run --release` (standard wgpu backend auto-selected by OS).
- **Web:** target `wasm32-unknown-unknown`. Build with `trunk` (recommended) or `wasm-bindgen-cli`. A `Trunk.toml` + `index.html` are part of the deliverable. `trunk serve` for dev, `trunk build --release` for the deployable artifact.
- Bevy web setup: `DefaultPlugins.set(RenderPlugin { wgpu_backends: WebGPU })` on web builds; native uses default backends. Gated by `#[cfg(target_arch = "wasm32")]`.
- Asset loading on web: shaders live under `assets/shaders/` and are loaded via Bevy's asset system, which works on web (paths, not absolute). No filesystem access assumed.
- `bevy_egui` works on both native and web (it handles its own input/winit wiring); no UI changes needed between platforms.
### 13.3 Cross-platform code hygiene
- All WGSL is backend-agnostic; no `#ifdef`-style shader branching is needed (we don't use WebGL2).
- Rust code uses `#[cfg(target_arch = "wasm32")]` only for: (a) forcing the WebGPU backend, (b) a WebGPU-availability check + fallback message, (c) canvas/resizing glue. All simulation/render logic is identical across platforms.
- Avoid `std::time`/blocking I/O patterns that misbehave on wasm; use Bevy's `Time` resource and async asset loading.
### 13.4 Web-specific parameters/UX
- Default `render_scale` lowered to 0.75 and `steps` to 200 on web (auto-detected via `cfg`) to keep frame times reasonable; both still live-tunable in the egui panel.
- Canvas resizes to the browser window; `render_scale` is relative to that.
- Pointer (mouse + touch) input routed the same way as desktop: egui panel gets priority when hovered, else the orbit controller consumes it.