Skip to content

Keep WebGPU alive when toggling 3D; single adapter/device owner - #445

Merged
ford442 merged 2 commits into
mainfrom
claude/webgpu-3d-teardown-fix
Sep 18, 2026
Merged

ford442 merged 2 commits into
mainfrom
claude/webgpu-3d-teardown-fix

Conversation

@ford442

@ford442 ford442 commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

Toggling 3D mode used to unmount MainLayout / PatternDisplay, destroying the WebGPU device, swapchain, bloom, and compute pipeline, then remount a second PatternDisplay inside the R3F scene. 3D is now a sibling overlay (same invariant-tree pattern as stage mode): the one visualiser stays mounted and is portaled into Studio3D's HTML panel. Adapter/device requests and canvas configure() also go through a single owner so probes cannot race a second GPU or disagree on alphaMode.

Changes

  • 3D toggle: MainLayout stays mounted; App3DModeShell is a fixed overlay. PerformanceStage portals the one PatternDisplay into a stable canvasHost whose DOM parent moves into Studio3D while 3D is open. 3D no longer constructs its own visualiser or picks a 3D-only shader — it shows whatever 2D is showing.
  • Device ownership: webgpuDevice.ts is the only requestAdapter / requestDevice call site. Renderer-selection and capability probes peek/reuse the in-flight request instead of requesting their own adapter.
  • Canvas config: The boot probe uses the same configureCanvasContext() helper as the runtime (premultiplied default), so bloom/screenshot/createImageBitmap cannot disagree with what the probe validated.
  • Tests: tests/threeDModeLayout.test.ts (invariant tree) and tests/webgpuDeviceOwnership.test.ts (single owner + shared configure).

Commits

  • db8a3e9 webgpu: unify adapter requests and canvas config into one owner
  • 4168335 3d: stop tearing down the WebGPU canvas when toggling 3D mode

Summary by CodeRabbit

  • New Features

    • 3D mode now displays the live pattern view within the studio while keeping the main layout active.
    • Pattern displays transition between standard and studio views without restarting.
    • 3D studio presentation now uses a full-screen overlay.
  • Bug Fixes

    • Improved WebGPU capability detection by reusing shared device requests, preventing conflicting GPU initialization.
  • Tests

    • Added coverage for 3D layout behavior, pattern display reuse, and WebGPU device ownership.

ford442 and others added 2 commits September 18, 2026 17:32
Problem B: rendererSelection.ts and deviceCapabilities.ts each called
navigator.gpu.requestAdapter() directly (default and low-power
preference respectively), racing the real device request in
webgpuDevice.ts and risking binding a different GPU on dual-GPU
hardware. webgpuDevice.ts is now the sole requestAdapter/requestDevice
call site: it tracks the latest device request so other modules can
peek/reuse it (peekInFlightWebGPUDeviceRequest,
peekAdapterInfoForCapabilityHint) instead of requesting their own
adapter. The renderer-selection probe degrades to a cheap
navigator.gpu surface check when no real request is in flight yet.

Problem C: the boot probe configured its throwaway canvas with
alphaMode: 'opaque' while the real swapchain defaults to
'premultiplied', so bloom/screenshot/createImageBitmap paths could
disagree with what the probe validated. The probe now configures
through the same configureCanvasContext() helper the runtime uses, so
alphaMode/usage/format can never drift apart. Premultiplied stays the
default — the pattern canvas composites over dark HTML chrome.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
App.tsx used to early-return a completely different tree for 3D mode
(App3DModeShell instead of MainLayout), which unmounted MainLayout /
PerformanceStage / PatternDisplay — destroying the WebGPU device,
swapchain, bloom post-processor and compute pipeline. App3DView then
mounted a *second* PatternDisplay (key={shader3D}) embedded in the R3F
scene via Drei's <Html>, requesting a second device. Toggling 3D was
therefore a full GPU teardown/rebuild: black canvas, possible audio
glitch, and a device-lifecycle race.

3D is a React-Three-Fiber "studio" that embeds the 2D visualiser as
HTML — it never needed a different WebGPU device. This treats it like
stage mode (see MainLayout/ChromeLayout/PerformanceStage's existing
invariant-tree pattern): MainLayout stays mounted unconditionally;
App3DModeShell renders as a sibling, fixed-position overlay instead of
replacing it.

The one PatternDisplay instance is shared between 2D and 3D rather
than duplicated. PerformanceStage now portals it into a stable,
never-recreated container (`canvasHost`, created once via lazy
useState) — createPortal's target identity never changes, so the
portal is never remounted. Only that container's *real DOM parent*
moves: normally PerformanceStage's own on-page slot, or — while 3D is
open — the DOM node Studio3D's R3F <Html> pattern-display panel
exposes via a ref callback threaded down through App.tsx → MainLayout
→ ChromeLayout → PerformanceStage (App3DModeShell → App3DView →
Studio3D on the other side). App3DView no longer constructs its own
PatternDisplay or picks a 3D-only shader (shader3D /
patternv0.21|patternv0.38 is gone — 3D always shows whatever 2D is
currently showing, per the "embeds the 2D visualiser" framing).

Added tests/threeDModeLayout.test.ts (modeled on the existing
stageModeLayout.test.ts) to pin the invariant tree shape, and
tests/webgpuDeviceOwnership.test.ts to pin Problem B/C's single-owner
invariants (only webgpuDevice.ts calls requestAdapter/requestDevice;
the presentation probe shares configureCanvasContext).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 47e2293c-f53d-4bfc-8078-14d6d1b2ca5c

📥 Commits

Reviewing files that changed from the base of the PR and between e8cdf75 and 4168335.

📒 Files selected for processing (12)
  • App.tsx
  • app/App3DModeShell.tsx
  • components/App3DView.tsx
  • components/ChromeLayout.tsx
  • components/MainLayout.tsx
  • components/PerformanceStage.tsx
  • components/Studio3D.tsx
  • src/renderers/rendererSelection.ts
  • tests/threeDModeLayout.test.ts
  • tests/webgpuDeviceOwnership.test.ts
  • utils/deviceCapabilities.ts
  • utils/webgpuDevice.ts
 __________________________________________________
< Are you not entertained? By the bugs I've found? >
 --------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ford442
ford442 merged commit c686bc0 into main Sep 18, 2026
6 of 7 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.

1 participant