Skip to content

fix(examples): reuse the luma.gl device when StrictMode remounts DeckGlOverlay - #668

Merged
kylebarron merged 1 commit into
mainfrom
kyle/fix-dev-webgl-attach-race
Sep 23, 2026
Merged

kylebarron merged 1 commit into
mainfrom
kyle/fix-dev-webgl-attach-race

Conversation

@kylebarron

@kylebarron kylebarron commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

I noticed issues with rendering Zarr-based examples locally. Those same Zarr-based examples work in the published examples online and when built in production mode locally.

It seems that there's a specific interaction between vite, react strict mode, maplibre, and deck.gl.

The below workaround seems to work locally.


Note

This PR description was written by Claude (Claude Code) on behalf of @kylebarron, not by @kylebarron.

Fixes the Zarr examples (aef-mosaic, dynamical-zarr-ecmwf, nldas-icechunk) rendering nothing under vite dev, with this error:

Error: WebGL context already attached to device webgl-device-1

Production builds were never affected.

Cause

  • React StrictMode (development only) mounts DeckGlOverlay twice. In interleaved mode, each mount makes MapboxOverlay create a new Deck on MapLibre's WebGL context. So deck A is created, finalized about 1.5ms later, and replaced by deck B.
  • Both decks attach a luma.gl device to that context at the same time. luma's attach checks for an existing device, awaits some async work, then creates one, throwing if a device appeared in between. If both checks run before either device exists, deck B throws. B is the deck that survives, so nothing renders.
  • deck.gl only guards against this in one of its two code paths. It passes _reuseDevices: true when it creates its own device (deck.ts#L1469-L1473), with a comment about exactly this, but not when it attaches to an existing context (deck.ts#L434-L440).
  • Whether the two attaches collide depends on how Vite bundles luma. In the Zarr examples, luma's device code ends up in the same chunk as the attach code, so both attaches resume within 0.2ms and collide. In the COG examples it's a separate, lazily loaded chunk, so the second attach runs about 9ms later, finds the first device, and reuses it.

Fix

DeckGlOverlay now passes deviceProps: { _reuseDevices: true }, merged with any deviceProps the caller supplies. Deck B then reuses deck A's device instead of throwing. That's safe:

  • neither Deck.finalize() nor luma's AnimationLoop.destroy() destroys a device attached to an external context;
  • deck A's startup stops once it sees it was finalized.

In production there's normally only one deck, so the flag changes nothing there.

Testing

Checked in Chrome under vite dev:

  • aef-mosaic, dynamical-zarr-ecmwf and nldas-icechunk now render, with no console errors.
  • land-cover (COG) is unaffected.
  • Tracing luma's device attach confirmed that the second deck now reuses webgl-device-1 instead of throwing.

pnpm -r typecheck and pnpm check pass.

🤖 Written by Claude Code

…GlOverlay

In development, React StrictMode mounts DeckGlOverlay twice, so the
interleaved MapboxOverlay creates two Decks on MapLibre's WebGL context
back to back. Both attach a luma.gl device concurrently, and in the Zarr
examples the second attach threw "WebGL context already attached to
device", so nothing rendered. Pass deviceProps._reuseDevices so the
second Deck reuses the device instead, as deck.gl already does for
devices it creates itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label Sep 23, 2026
@kylebarron
kylebarron enabled auto-merge (squash) September 23, 2026 20:10
@kylebarron
kylebarron merged commit 63ebdd8 into main Sep 23, 2026
4 checks passed
@kylebarron
kylebarron deleted the kyle/fix-dev-webgl-attach-race branch September 23, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant