fix(ui-map): inverse stencil mask for the visibility fog (Safari pan perf, stage 2) #22
Reference in New Issue
Block a user
Delete Branch "feature/ui-map-fog-inverse-mask"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
After stage 1 (#21, render-on-demand) the idle / whole-system freeze was
gone, but panning a loaded report (
KNNTS041) with "visible hyperspace"on was still heavy in Safari. The fog still cut its visibility holes by opaque
overpaint — ~260 near-world-sized opaque circles blended over the fog every
rendered frame, a fill-rate cliff for Safari's WebGPU / Apple's tile-based GPU.
Stage 2 (this PR) — inverse stencil mask
Replace the overpaint with an inverse stencil mask:
setVisibilityFogdraws the
FOG_COLORrectangle(s) intofogLayerand collects the visibilitycircles into one
Graphicsset asfogLayer.setMask({ mask, inverse: true }),so the fog shows everywhere except the union of the circles.
rectangle fill + a stencil pass (no colour writes) — cheap on Apple's TBDR
GPU.
fogPaintOpsand its unit tests are unchanged — the circle ops now feed themask instead of an overpaint.
Verification
hole cut out of the fog field.
svelte-checkclean; Vitest 707/707; map e2e (fog toggle, render-on-demand,no-inertia, wrap) green on chromium + webkit.
Docs
ui/docs/renderer.mdfog section (inverse-mask mechanism, rationale).ui/PLAN.mdPhase 29 decision 9.🤖 Generated with Claude Code
Stage 1 (render-on-demand) removed the idle / whole-system freeze, but panning a loaded map with "visible hyperspace" on stayed heavy in Safari: the fog still cut its visibility holes by opaque overpaint — on KNNTS041 that is ~260 near-world-sized opaque circles blended over the fog every rendered frame, a fill-rate cliff for Safari's WebGPU / Apple's tile-based GPU. Replace the overpaint with an INVERSE stencil mask: setVisibilityFog now draws the FOG_COLOR rectangle(s) into fogLayer and collects the visibility circles into one Graphics set as fogLayer.setMask({ mask, inverse: true }), so the fog shows everywhere except the union of the circles. Per-frame cost drops from dozens of blended opaque circle fills to one rect fill + a stencil pass (no colour writes), which Apple's TBDR GPU handles cheaply, and the fog stays fully vector — crisp at any zoom. fogPaintOps and its unit tests are unchanged (the circle ops now feed the mask instead of an overpaint). Verified with a high-contrast screenshot during development (fog field with a correct circle-union hole) plus the existing fog / render-on-demand e2e green on chromium + webkit. Docs: renderer.md fog section + PLAN.md Phase 29 decision 9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>