Sitelet https://github.com/gpuweb/gpuweb/pull/521/files
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions design/ErrorHandling.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ partial interface GPUDevice {
`GPUAdapter.requestDevice` requests a device from the adapter.
It returns a Promise which resolves when a device is ready.
The Promise may not resolve for a long time - it resolves when the browser is ready for the application to bring up (or restore) its content.
If the adapter is unable to create a device (i.e. because the adapter was lost), the Promise rejects.
If the adapter is lost and therefore unable to create a device, `requestDevice()` returns null.
If the `options` are invalid (e.g. they exceed the limits of the adapter), `requestDevice()` rejects.

The `GPUDevice` may be lost if something goes fatally wrong on the device (e.g. unexpected driver error, crash, or native device loss).
The `GPUDevice` provides a promise, `device.lost`, which resolves when the device is lost.
Expand All @@ -50,6 +51,11 @@ The device and all objects created from the device have become invalid.
All further operations on the device and its objects are errors.
The `"validationerror"` event will no longer fire. (This makes all further operations no-ops.)

An app should never give up on getting WebGPU access due to
`requestDevice` returning null or `GPUDevice.lost` resolving.
It should only give up based on a `requestAdapter` rejection.
(It should also give up on a `requestDevice` rejection, as that indicates an app programming error.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels to me that "should give up" and "shouldn't give up" distinction is too high level. We could maybe specify it in terms of "this is a function of Xxx", which implies to the user whether they should try again or not. I.e. we can say that the rejection is deterministic based on the adapter capabilities and the provided options. This is better than "should give up" because it also allows for a case where the user probes for different limits, so they would actually want to try again with different options.
At the same time, we'd say that returning null is an indication of a temporary state that doesn't depend on the parameters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is just the design doc, so we can make it more formal in spec)

requestDevice rejects if you ask for higher limits than its adapter has. Apps shouldn't be doing that (they should check the adapter limits instead) making this rejection an app programming error.

requestAdapter rejects if no adapters could possibly be returned. Given the only adapter request option right now is powerPreference, the options should never have an impact on whether or not you get an adapter. Perhaps in the future it would though.


### Example Code

```js
Expand All @@ -66,47 +72,43 @@ class MyRenderer {
this.initFallback();
}
}
async initWebGPU() {
await this.ensureDevice();
// ... Upload resources, etc.
}
initFallback() { /* try WebGL, 2D Canvas, or other fallback */ }
async ensureDevice() {
async initWebGPU() {
// Stop rendering. (If there was already a device, WebGPU calls made before
// the app notices the device is lost are okay - they are no-ops.)
this.device = null;

// Keep current adapter (but make a new one if there isn't a current one.)
// If we can't get an adapter, ensureDevice rejects and the app falls back.
await ensureAdapter();

try {
await ensureDeviceOnCurrentAdapter();
// Got a device.
return;
} catch (e) {
console.error("device request failed", e);
// That failed; try a new adapter entirely.
await tryEnsureDeviceOnCurrentAdapter();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this call to tryEnsureDeviceOnCurrentAdapter() can be folded inside the loop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first one tries to reuse the current adapter while the later ones get a new adapter, though maybe this is wrong. I think this code still needs some revision.

// If the device is null, the adapter was lost. Try a new adapter.
// Continue doing this until one is found or an error is thrown.
while (!this.device) {
this.adapter = null;
// If we can't get a new adapter, it causes ensureDevice to reject and the app to fall back.
await ensureAdapter();
await ensureDeviceOnCurrentAdapter();
await tryEnsureDeviceOnCurrentAdapter();
}

// ... Upload resources, etc.
}
async ensureAdapter() {
async tryEnsureDeviceOnCurrentAdapter() {
// If no adapter, get one.
// If we can't, rejects and the app falls back.
if (!this.adapter) {
// If no adapter, get one.
// (If requestAdapter rejects, no matching adapter is available. Exit to fallback.)
this.adapter = await gpu.requestAdapter({ /* options */ });
}
}
async ensureDeviceOnCurrentAdapter() {

// Try to get a device.
// null => try new adapter
// rejection => options were invalid (app programming error)
this.device = await this.adapter.requestDevice({ /* options */ });
this.device.lost.then((info) => {
// Device was lost.
console.error("device lost", info);
// Try to get a device again.
this.ensureDevice();
if (!this.device) {
return;
}
// When the device is lost, just try to get a device again.
device.lost.then((info) => {
console.error("Device was lost.", info);
this.initWebGPU();
});
}
}
Expand Down