CNA-GAP-064: WEBGPU does not propagate a real device loss: OnDeviceLost only logs, so DeviceLost, the device status and the draw gate react only to the debug hooks

CNA snapshot 009d40f5  ·  Known Issues › Functional gaps  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

WebGPURenderer sets its lost flag, closes CanBeginDrawEXT() and raises DeviceLost only inside DebugSimulateContextLoss; the callback registered for a real loss only writes to stderr. CNA's own plan (WEBGPU-182) records the driver-reported path as not done and unverified, so this is an incomplete feature rather than a broken contract.

Identifier
CNA-GAP-064
Category
Functional gap
Subsystem
Graphics & renderers
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
GraphicsDevice::DeviceLost / DeviceReset, getGraphicsDeviceStatusProperty() and the CanBeginDrawEXT() gate behind GraphicsDeviceManager::BeginDraw, on the WEBGPU renderer

Expected behaviour

XNA raises DeviceLost and reports GraphicsDeviceStatus::Lost when the device is lost, and DirectX 9, 11 and 12, Direct2D and Vulkan raise it from their real error paths through GraphicsRendererCreateArgs::deviceEventCallback. That channel is opt-in, though, and for WEBGPU nothing documented promises a real loss reaches it: webgpu-renderer.md describes recovery through the debug hooks only, and plan_webgpu.md WEBGPU-182 closes with 'what is NOT done is the driver-reported path'. The same document adds a WebGPU-specific rule: a lost device must be gated before the acquire, because wgpuSurfaceGetCurrentTexture on a surface whose device is lost panics inside wgpu-native and aborts the process, and CanBeginDrawEXT() returning false is what stands between a lost device and that abort.

Actual behaviour at TARGET

RequestDeviceOnlyEXT (also reached through RequestAdapterAndDevice) registers the free function OnDeviceLost as deviceLostCallbackInfo.callback with no user data. OnDeviceLost prints "CNA WebGPU device lost (...)" to stderr and does nothing else; without a renderer pointer it cannot set deviceLost_, raise RendererDeviceEvent::Lost or close CanBeginDrawEXT() (which returns !deviceLost_). The only code that does those things is WebGPURenderer::DebugSimulateContextLoss, and the only recovery is DebugRestoreContext; both are debug hooks the renderer never calls itself. When a real loss reaches the callback, the game receives no DeviceLost, GraphicsDeviceStatus stays Normal, BeginDraw keeps returning true and the renderer continues its frame loop against the lost device; nothing rebuilds it. The header comment of DebugSimulateContextLoss says the callback 'stays wired for a real driver-reported loss', which overstates what the callback does.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. That the callback propagates nothing is certain from the code. How often a real loss reaches it is not: natively, CNA measured that the pinned wgpu-native v29.0.1.1 delivers no device-lost callback for an application-initiated destroy (WEBGPU-180), and whether a driver-reported loss delivers one was not measured; in a browser, CNA states that the callback fires but derived that from the emdawnwebgpu port's JavaScript without a browser confirmation. What the browser does with the renderer's continued calls on a lost device was not established here. Natively, if a later pin does deliver the callback, the open gate leaves exactly the acquire path CNA measured to abort the process.

Focused reproduction

// Illustrative; not compiled or run for this entry. Web target (emdawnwebgpu), where CNA states the
// device-lost callback fires. Destroy the device from outside the renderer's debug hook:
wgpuDeviceDestroy(webGpuRenderer.Device());
// Expected: DeviceLost raised, GraphicsDeviceStatus == Lost, CanBeginDrawEXT() == false.
// Actual per the code: one "CNA WebGPU device lost (...)" line on stderr; status stays Normal and the
// next BeginDraw proceeds.

Current tests

webgpu_context_recovery_test.cpp verifies the debug-hook path end to end (the gate goes false and back, each event fires once, resources survive). No test delivers a device-lost callback or asserts that one reaches GraphicsDevice.

Regression test

Pass the renderer as the callback's userdata1 and have it set deviceLost_ and raise RendererDeviceEvent::Lost (ignoring the loss CNA itself causes in DebugSimulateContextLoss); then a browser test in the WEBGPU-196 style that destroys the device from outside the renderer and asserts that DeviceLost fired and CanBeginDrawEXT() is false.

Blast radius

WEBGPU only, most visibly on the web target. Games that rely on DeviceLost, DeviceReset or GraphicsDeviceStatus to pause, save or rebuild never hear of a real loss, and the renderer keeps drawing against a dead device. DirectX 9, 11 and 12, Direct2D and Vulkan raise Lost from their own real error paths and are unaffected.

Workaround

No workaround is known.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.