# WebGPU Device Loss Handling ## What Is Device Loss? Device loss occurs when the GPU driver cannot continue processing commands. Causes include: - Driver crashes - Extreme resource pressure - Long-running shaders (GPU watchdog triggers after ~10 seconds) - Driver updates - Significant device configuration changes When a device is lost, the `GPUDevice` object and **all objects created with it become unusable**. All buffers, textures, pipelines, and GPU memory are discarded. ## Listening for Device Loss Detect loss by attaching a callback to the device's `lost` promise: ```javascript const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { return; } const device = await adapter.requestDevice(); device.lost.then((info) => { console.error('WebGPU device lost:', info.message); // Handle recovery }); ``` **Important:** Don't `await` this promise directly - it will block indefinitely if loss never occurs. ### Device Loss Information The `GPUDeviceLostInfo` object provides: | Property | Description | |----------|-------------| | `reason` | `'destroyed'` (intentional via `destroy()`) or `'unknown'` (unexpected) | | `message` | Human-readable debugging info (don't parse programmatically) | ```javascript device.lost.then((info) => { if (info.reason === 'unknown') { // Unexpected loss - attempt recovery handleUnexpectedDeviceLoss(); } else { // Intentional destruction - expected behavior } }); ``` ### Devices Starting Lost `adapter.requestDevice()` always returns a `GPUDevice`, but it may already be lost if creation failed. This occurs when the adapter was "consumed" (used previously) or "expired." **Best practice:** Always get a new adapter right before requesting a device. ## Recovery Strategies ### Minimal Recovery (Page Reload) For simple applications: ```javascript device.lost.then((info) => { if (info.reason === 'unknown') { // Warn user before reload alert('Graphics error occurred. The page will reload.'); location.reload(); } }); ``` ### Restart GPU Content Only (Recommended for Three.js) Recreate the device and reconfigure the canvas without full page reload: ```javascript import * as THREE from 'three/webgpu'; let renderer; let scene, camera; async function initWebGPU() { renderer = new THREE.WebGPURenderer(); await renderer.init(); // Access the underlying WebGPU device const device = renderer.backend.device; device.lost.then((info) => { console.error('Device lost:', info.message); if (info.reason === 'unknown') { // Dispose current renderer renderer.dispose(); // Reinitialize initWebGPU(); } }); // Configure canvas renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Recreate scene content setupScene(); } function setupScene() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // ... add meshes, lights, etc. } initWebGPU(); ``` ### Restore with Application State For applications with user progress or configuration: ```javascript let appState = { cameraPosition: { x: 0, y: 5, z: 10 }, settings: {}, // Don't save transient data like particle positions }; // Save state periodically function saveState() { appState.cameraPosition = { x: camera.position.x, y: camera.position.y, z: camera.position.z }; localStorage.setItem('appState', JSON.stringify(appState)); } // Restore on recovery async function initWebGPU() { renderer = new THREE.WebGPURenderer(); await renderer.init(); const savedState = localStorage.getItem('appState'); if (savedState) { appState = JSON.parse(savedState); } setupScene(); // Restore camera position camera.position.set( appState.cameraPosition.x, appState.cameraPosition.y, appState.cameraPosition.z ); renderer.backend.device.lost.then((info) => { if (info.reason === 'unknown') { saveState(); renderer.dispose(); initWebGPU(); } }); } ``` ## When Recovery Fails If `requestAdapter()` returns `null` after device loss, the OS or browser has blocked GPU access: ```javascript async function initWebGPU() { const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { // Check if this is initial failure or post-loss failure if (hadPreviousDevice) { showMessage('GPU access lost. Please restart your browser.'); } else { showMessage('WebGPU is not supported on this device.'); } return; } // Continue with device creation... } ``` ## Testing Device Loss ### Using destroy() Call `device.destroy()` to simulate loss: ```javascript let simulatedLoss = false; function simulateDeviceLoss() { simulatedLoss = true; renderer.backend.device.destroy(); } // In your device.lost handler: device.lost.then((info) => { if (info.reason === 'unknown' || simulatedLoss) { simulatedLoss = false; // Treat as unexpected loss for testing handleDeviceLoss(); } }); // Add debug keybinding window.addEventListener('keydown', (e) => { if (e.key === 'L' && e.ctrlKey && e.shiftKey) { simulateDeviceLoss(); } }); ``` **Limitations of destroy():** - Unmaps buffers immediately (real loss doesn't) - Always allows device recovery (real loss may not) ### Chrome GPU Process Crash Testing Navigate to `about:gpucrash` in a **separate tab** to crash the GPU process. Chrome enforces escalating restrictions: | Crash | Effect | |-------|--------| | 1st | New adapters allowed | | 2nd within 2 min | Adapter requests fail (resets on page refresh) | | 3rd within 2 min | All pages blocked (reset after 2 min or browser restart) | | 3-6 within 5 min | GPU process stops restarting; browser restart required | ### Chrome Testing Flags Bypass crash limits for development: ```bash # macOS /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --disable-domain-blocking-for-3d-apis \ --disable-gpu-process-crash-limit # Windows chrome.exe --disable-domain-blocking-for-3d-apis --disable-gpu-process-crash-limit # Linux google-chrome --disable-domain-blocking-for-3d-apis --disable-gpu-process-crash-limit ``` ## Complete Example ```javascript import * as THREE from 'three/webgpu'; import { color, time, oscSine } from 'three/tsl'; let renderer, scene, camera, mesh; let hadPreviousDevice = false; async function init() { // Check WebGPU support if (!navigator.gpu) { showError('WebGPU not supported'); return; } // Create renderer renderer = new THREE.WebGPURenderer({ antialias: true }); try { await renderer.init(); } catch (e) { if (hadPreviousDevice) { showError('GPU recovery failed. Please restart browser.'); } else { showError('Failed to initialize WebGPU.'); } return; } hadPreviousDevice = true; // Setup device loss handler const device = renderer.backend.device; device.lost.then(handleDeviceLoss); // Setup scene renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshStandardNodeMaterial(); material.colorNode = color(0x00ff00).mul(oscSine(time)); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light); scene.add(new THREE.AmbientLight(0x404040)); animate(); } function handleDeviceLoss(info) { console.error('Device lost:', info.reason, info.message); if (info.reason === 'unknown') { // Cleanup if (renderer) { renderer.domElement.remove(); renderer.dispose(); } // Attempt recovery after short delay setTimeout(() => { init(); }, 100); } } function animate() { if (!renderer) return; requestAnimationFrame(animate); mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; renderer.render(scene, camera); } function showError(message) { const div = document.createElement('div'); div.textContent = message; div.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background:#f44;color:#fff;border-radius:8px;'; document.body.appendChild(div); } init(); ``` ## Best Practices 1. **Always listen for device loss** - Even if you just show an error message 2. **Get a fresh adapter before each device request** - The GPU hardware may have changed 3. **Don't parse the message field** - It's implementation-specific and changes between browsers 4. **Save critical application state** - Restore user progress after recovery 5. **Don't save transient state** - Particle positions, physics state can be reset 6. **Test your recovery path** - Use `destroy()` and Chrome's `about:gpucrash` 7. **Handle adapter failure gracefully** - Distinguish between initial failure and post-loss failure 8. **Add a short delay before recovery** - Give the system time to stabilize