# 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
