guard<T> method
Runs body with a validation scope open, recording whatever the browser
says into debugDrainErrors.
Without this pair nothing WebGPU rejects can ever be seen from Dart.
The API validates asynchronously: createRenderPipeline hands back a
pipeline object whether or not the descriptor was legal, and the complaint
arrives later, on the console, where no Dart program will meet it. A
backend that means to report a refusal has to bracket the call.
Synchronous in and synchronous out, so the object can be handed on while the verdict settles behind it — which is what makes this affordable at all. It is used on the calls that are rare and expensive: making a texture, a buffer, a pipeline, a bind group, and submitting a pass. Wrapping a per-draw call would add a promise per draw.
Implementation
T guard<T>(String what, T Function() body) {
gpuDevice.pushErrorScope(GpuErrorFilter.validation);
final T result;
try {
result = body();
} catch (_) {
// Popped either way: an unbalanced scope makes the *next* pop answer for
// this call's errors, which reports the mistake against whatever ran
// afterwards.
_pending.add(gpuDevice.popErrorScope().toDart.then((GPUError? _) {}));
rethrow;
}
_pending.add(
gpuDevice.popErrorScope().toDart.then((GPUError? error) {
if (error != null) _errors.add('$what: ${error.message}');
}),
);
return result;
}