gpuChecked<T> function

Future<T> gpuChecked<T>(
  1. GPUDevice device,
  2. String what,
  3. T body()
)

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;
}