gpuChecked<T> function
Runs body with a validation scope open, and throws where the browser
complained.
This is the whole reason a backend on this API can report anything.
WebGPU validates asynchronously: createRenderPipeline hands back a
pipeline object whether or not the descriptor was legal, and the complaint
goes to the browser console some time later, where no Dart program will ever
see it. A GraphicsDevice that means to throw — and the conformance suite's
refusal checks are written expecting one — has to bracket the call in a
scope and await the answer.
The cost is that every guarded call becomes asynchronous, which is why this
takes a synchronous body and returns its result: the object exists
immediately and only the verdict is awaited, so a backend can hand the
object on and let the check settle behind it.
Validation only. Out-of-memory and internal errors want a different response from a caller — freeing something, or giving up — and a scope catches one filter, so mixing them here would report an allocation failure as a programming mistake.
Implementation
Future<T> gpuChecked<T>(
GPUDevice device,
String what,
T Function() body,
) async {
device.pushErrorScope(GpuErrorFilter.validation);
final T result;
try {
result = body();
} catch (_) {
// The scope is popped either way: leaving one open makes the *next*
// pop answer for this call's errors, which reports the mistake against
// whatever ran afterwards.
await device.popErrorScope().toDart;
rethrow;
}
final error = await device.popErrorScope().toDart;
if (error != null) throw GpuDeviceError(what, error.message);
return result;
}