Skip to content

fix(signal-viewer): activate the WebGL2 waveform path (mount-order race pinned it to Canvas2D)#32

Merged
kabaka merged 2 commits into
mainfrom
claude/fix-webgl-activation
Jun 16, 2026
Merged

fix(signal-viewer): activate the WebGL2 waveform path (mount-order race pinned it to Canvas2D)#32
kabaka merged 2 commits into
mainfrom
claude/fix-webgl-activation

Conversation

@kabaka

@kabaka kabaka commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

The WebGL2 hybrid renderer shipped in #31 never actually activated in the live Signal Viewer — every session silently ran the Canvas2D fallback, so pan/zoom performance was unchanged. A production Edge trace (scroll + zoom) showed 100% old Canvas2D draw calls (drawLine/drawEnvelope/renderDuringPan) and the same full-canvas GPU texture re-upload (~270–466 MB/frame, 135 dropped frames) — the exact signature of the fallback path. The shipped bundle does contain the WebGL code (confirmed u_clipScale/drawArraysInstanced/WebGLUnavailable in the loaded chunk), so this is a runtime wiring failure, not a stale cache or a missing build.

Root cause — a deterministic mount-order race

tryInitRenderer in SignalViewer.tsx constructed the HybridSignalRenderer as soon as the base chrome canvas mounted, tolerating a null waveform canvas (if (!base) return;). The base <canvas> precedes the transparent WebGL waveform <canvas> in the DOM, so its ref-callback fires first → the renderer was always built as new HybridSignalRenderer(base, /* waveform */ null, …), which pins it to the Canvas2D fallback for the view's lifetime (the rendererRef.current guard then blocks reconstruction when the waveform canvas mounts a moment later). WebGL2 therefore never activated for anyone.

Fix

Require both canvases before constructing (if (!base || !waveform) return;). The waveform canvas is rendered unconditionally and mounts in the same commit, so this never deadlocks; if WebGL2 is genuinely unavailable at runtime, HybridSignalRenderer still falls back internally as designed.

Why CI didn't catch it (and the new guard test)

The fidelity gate and unit tests construct the renderers directly (via a harness), proving they're correct — but nothing asserted that the real SignalViewer actually activates WebGL. This PR adds that missing seam: SignalViewer.webglWiring.test.tsx renders the real viewer and asserts HybridSignalRenderer is constructed with a non-null HTMLCanvasElement as the waveform argument. Verified red on the pre-fix guard (expected null not to be null), green with the fix.

Tests

  • New regression test (red/green verified).
  • Full suite green: 136 files / 2,552 tests; typecheck + lint clean.

After this merges and deploys, a hard-reload should show GPU-accelerated pan/zoom (the renderer from #31 was correct all along — it just was never switched on).

🤖 Generated with Claude Code

https://claude.ai/code/session_012CzEJ1kUhwobqVTnVusLcb


Generated by Claude Code

claude added 2 commits June 16, 2026 23:42
… renderer

The hybrid renderer was constructed as soon as the BASE chrome canvas
mounted, tolerating a null waveform canvas. Because the base <canvas>
precedes the waveform <canvas> in the DOM, its ref callback fires first,
so the renderer was always built with waveform=null — pinning every
session to the Canvas2D fallback for the view's lifetime (the
rendererRef guard blocks reconstruction once the waveform canvas
arrives). WebGL2 therefore never activated in the real Signal Viewer,
even though the renderer itself is correct and the fidelity gate (which
constructs the renderers directly) passed. Require both canvases before
constructing.
…ebGL waveform canvas

Regression guard for the WebGL2-activation bug where tryInitRenderer
constructed HybridSignalRenderer as soon as the base chrome canvas
mounted (`if (!base) return;`), tolerating a null waveform canvas and
pinning the renderer to the Canvas2D fallback forever.

Mounts the real SignalViewer with minimal mocked session data and a spy
HybridSignalRenderer that records constructor args, then asserts the
second argument (the WebGL waveform layer) is a non-null
HTMLCanvasElement distinct from the base canvas. Red against the pre-fix
`if (!base) return;`, green against `if (!base || !waveform) return;`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzEJ1kUhwobqVTnVusLcb
@kabaka kabaka merged commit 1748934 into main Jun 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants