移动skill位置
This commit is contained in:
359
.codex/skills/webgpu-threejs-tsl/docs/device-loss.md
Normal file
359
.codex/skills/webgpu-threejs-tsl/docs/device-loss.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user