@vectorvesper/motionWebGPU · Device health

watchGPUDevice

A WebGPU device can be taken away for the same reasons a WebGL context can, and it tells you in a completely different way. WebGL fires an event. WebGPU resolves a promise it handed you when the device was created. Code written for the event never hears the promise, so the canvas stops drawing with nothing thrown and a clean console.

watchGPUDevice tells the runtime when a WebGPU device dies. Hand it a device, and it hands back a function that stops watching.

A loss increments the same generation counter a lost WebGL context does. A scene gate keyed on that counter rebuilds against a device you request fresh, so a recovery path written for WebGL works here with nothing changed. The counter never cared which graphics API died.

Through React Three Fiber you never call this yourself. Spread useRenderQuality's props and the adapter finds the device and watches it for you.

Added in 4.1.0. No WebGPU type packages required: the parameter covers the one field this reads, so a real device satisfies it either way.

What your browser exposes

LIVE PROBE
Demo loads on scroll

No button breaks this one. The only loss a page can cause is destroying the device itself, which is exactly the case the runtime ignores, so a “lose the device” control would have to fake it. The context-loss lab kills real contexts and drives the same counter.

Quick start

Request a device, hand it over, stop watching when you tear it down.

Both orders are safe, because a destroyed device is ignored on its reason alone. Stopping first just means the resolution arrives with nothing listening.

Why a destroyed device is ignored

The one branch in this function exists because the WebGL side shipped without it.

The reason on a lost device is destroyed when your own code called destroy(), and unknown when the device actually failed. Only the second is worth rebuilding for.

Treating the first as a failure is not a hypothetical mistake. On the WebGL side, dropping a context during unmount reported a loss after its replacement had already reported healthy. That incremented the generation, which remounted everything, which unmounted more contexts, which reported more losses. One page reached generation 18 in a second with nobody touching it.

Coalescing, added in 4.0.3, covers the other half of the same problem. Several devices dying together, one driver reset across four canvases, count as a single reset, so the page rebuilds once rather than four times.

Stopping is a flag rather than a detachment, because a promise cannot be un-awaited. The handler still runs after you call stop(); what it no longer does is report. That is why the return value is worth keeping.

API Reference

Syntax

Reports the device healthy on receipt, then reports a loss if it dies for a reason other than being destroyed.

Parameters

Parameters.fieldTypeDefaultDescription
deviceGPUDeviceLike(none)Anything carrying a lost promise. A real GPUDevice satisfies this structurally, as does an engine's wrapper around one.

Return Value

StopWatchingDevice

Stops reporting. Idempotent, and safe to call after the device has already been lost.

Loss reasons

What the resolved value means, and what the runtime does with it.

Loss reasons.fieldMeaningReported
"destroyed"Your code called destroy().Nothing. A teardown is not a failure.
"unknown"Driver reset, tab eviction, the OS reclaiming the GPU.A loss. The generation moves and scenes rebuild.
undefinedAn implementation that resolves without info.A loss, because it is not a destroy.

Lifecycle & Invariant Guarantees

  • A destroyed device is not a lost device: Tearing a scene down cannot trigger a rebuild of the page that is tearing it down.
  • One reset is one rebuild: Losses arriving within 250ms count once, so a driver reset that takes four devices rebuilds the page once. Needs 4.0.3 or newer.
  • Holding a device reports health: Receiving a device ends a recovery. The replacement announcing itself is the signal that the scene is drawable again.
  • A non-conformant promise cannot break the page: The spec says the promise resolves. An implementation that rejects instead reports nothing rather than surfacing an unhandled rejection.

Production Examples

Battle-tested production patterns ready to copy directly into your codebase.

The generation is the recovery. A new key means a device requested fresh.

When NOT to use this

The scene renders through React Three Fiber.
→ instead useRenderQuality. It finds the device and watches it, and attaches the WebGL listener too.
You hold a raw WebGL context rather than a WebGPU device.
→ instead A webglcontextlost listener that calls preventDefault, reporting to getRendererHealth().
You only want to know whether the browser has WebGPU at all.
→ instead The webgpu flag on AdaptiveQuality's device signals. It needs no adapter.
The page draws with Canvas 2D or CSS.
→ instead Nothing. Neither can lose a device this way.

Rules & Gotchas

  • Stop watching in the cleanup that destroys the device: A watcher outliving the component that owns the device is how a teardown ends up reported as a failure.
  • Request a new device on a new generation: A lost device cannot be revived. Recovery means requesting another, which is what keying the subtree on generation makes React do.
  • Do not await the lost promise in a render path: It resolves only when the device dies, which on a healthy machine is never.
  • Do not read navigator.gpu as proof of a working device: A blocklisted driver exposes the API and then refuses the adapter, so the request can still fail.

Related Motion Components

See it live in the lab: /lab/context-loss