render2d im Browser: WASM/WebGPU-Viewport hinter ?engine=wasm

Integrations-Spike: die native 2D-Engine (src-tauri/render2d) laeuft jetzt als
WASM/WebGPU-Canvas in der App-UI, mit derselben Szene (planToRenderScene) und
ViewBox wie der WebGL2-Pfad; das SVG-Overlay (Text/Griffe/Auswahl) bleibt drueber.

- render2d: neues Feature "web" (wasm-bindgen/web-sys), Fassade WebPlanRenderer
  (new/set_scene/set_view_box/set_paper_scale/resize/render) auf Canvas-Surface.
  Geteilte GPU-Schicht (gpu.rs), native Fensterschicht (winit) unberuehrt.
- Font: cosmic-text findet auf wasm keine Systemfonts -> Inter-Bytes eingebettet
  (assets/Inter.ttf), Renderer.load_font + text_family aus der geladenen Familie.
- Frontend: useWasmPlanRenderer (gleiche Schnittstelle wie useGlPlanRenderer);
  PlanView waehlt die Engine per ?engine=wasm + navigator.gpu, sonst WebGL2/SVG.
- Build: npm-Script build:engine (wasm-pack, target web -> src/engine/pkg,
  gitignored). chromium-shell.sh mit WebGPU-Flags (--enable-unsafe-webgpu/Vulkan).
- Kompat-Shim: wgpu 22 sendet das spec-entfernte Limit maxInterStageShaderComponents,
  das neuere Chromium ablehnen -> im Hook vor requestDevice entfernt.

Verifiziert: cargo test -p render2d gruen (18), tsc gruen, wasm-Build gruen;
Headless-Chromium (?engine=wasm) zeichnet den Grundriss (Waende/Schraffur/
Tuerschwenk/Treppe/Raumtext) im WASM-Viewport, Fallback ohne Flag unveraendert.
This commit is contained in:
2026-07-02 22:30:47 +02:00
parent 6eead6d493
commit 665cbce1f9
12 changed files with 607 additions and 19 deletions
+33 -11
View File
@@ -18,6 +18,7 @@ import { docToLines, type SvgLine } from "../text/renderHtml";
import type { EdgeGrip, Vec2 } from "../model/types";
import type { DraftShape, SnapResult, ToolHandlers, ToolId, ToolMods } from "../tools/types";
import { useGlPlanRenderer } from "./useGlPlanRenderer";
import { useWasmPlanRenderer } from "./useWasmPlanRenderer";
const PX_PER_M = 90; // viewBox-Einheiten je Meter (Modell → SVG-Benutzerraum)
const PAD = 60; // Rand in viewBox-Einheiten
@@ -410,19 +411,40 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
const svgRef = useRef<SVGSVGElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
// WebGL2-GPU-Renderer ist der STANDARD-Pfad. Der SVG-Renderer bleibt als
// automatischer Fallback (nur falls WebGL2/Shader nicht verfügbar) — es gibt
// KEINE User-Umschaltung mehr. `?gl=0` erzwingt zu Debug-Zwecken reines SVG.
const wantGpu =
// Engine-Auswahl. STANDARD-Pfad: WebGL2 (useGlPlanRenderer); der SVG-Renderer
// bleibt automatischer Fallback (nur falls WebGL2/Shader nicht verfügbar), `?gl=0`
// erzwingt reines SVG. INTEGRATIONS-SPIKE: Mit `?engine=wasm` UND vorhandenem
// WebGPU (navigator.gpu) übernimmt statt WebGL2 der native render2d-Renderer als
// WASM/WebGPU-Viewport (useWasmPlanRenderer) — dieselbe Szene, dieselbe ViewBox,
// dasselbe SVG-Overlay (Text/Griffe/Auswahl) darüber. Fehlt WebGPU trotz Flag:
// stiller Fallback auf WebGL2 (+ Warnung).
const engineParam =
typeof window === "undefined"
? null
: new URLSearchParams(window.location.search).get("engine");
const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
const wantWasm = engineParam === "wasm" && hasWebGpu;
useEffect(() => {
if (engineParam === "wasm" && !hasWebGpu) {
console.warn("engine=wasm angefragt, aber navigator.gpu fehlt → WebGL2-Fallback");
}
}, [engineParam, hasWebGpu]);
// WebGL2 nur, wenn NICHT der WASM-Pfad aktiv ist (und `?gl!=0`).
const wantGl =
!wantWasm &&
(typeof window === "undefined"
? true
: new URLSearchParams(window.location.search).get("gl") !== "0";
const { render: renderGl, updateGeometry, ready: glReady } = useGlPlanRenderer(
canvasRef,
wantGpu,
);
// GL WIRKLICH aktiv? Steuert das Umschalten SVG↔GL: bei false rendert der volle
// SVG-Pfad (Fallback), bei true übernimmt die GL-Ebene + Text-Overlay.
: new URLSearchParams(window.location.search).get("gl") !== "0");
// Beide Hooks unbedingt aufrufen (Hook-Regeln); je nach Flag ist genau einer aktiv.
const glHook = useGlPlanRenderer(canvasRef, wantGl);
const wasmHook = useWasmPlanRenderer(canvasRef, wantWasm);
const activeEngine = wantWasm ? wasmHook : glHook;
const { render: renderGl, updateGeometry, ready: glReady } = activeEngine;
// „GPU-Canvas montieren?" — beide GPU-Pfade brauchen das Canvas (an der ABSICHT
// montiert, nicht am ready, sonst könnte die Engine mangels Canvas nie starten).
const wantGpu = wantGl || wantWasm;
// Engine WIRKLICH aktiv? Steuert das Umschalten SVG↔GPU: bei false rendert der
// volle SVG-Pfad (Fallback), bei true übernimmt die GPU-Ebene + Text-Overlay.
const useGpuRenderer = wantGpu && glReady;
// Stabiler Papier-Massstab-Nenner N (1:N) als REFERENZ für die Print-Strich-