diff options
-rw-r--r-- | extensions/webgpu/01_webgpu.js | 153 | ||||
-rw-r--r-- | extensions/webgpu/buffer.rs | 13 |
2 files changed, 112 insertions, 54 deletions
diff --git a/extensions/webgpu/01_webgpu.js b/extensions/webgpu/01_webgpu.js index 8856a9a17..096dfa722 100644 --- a/extensions/webgpu/01_webgpu.js +++ b/extensions/webgpu/01_webgpu.js @@ -17,6 +17,7 @@ ArrayBuffer, ArrayBufferIsView, ArrayIsArray, + ArrayPrototypeFilter, ArrayPrototypeMap, ArrayPrototypePop, ArrayPrototypePush, @@ -25,6 +26,10 @@ ObjectDefineProperty, ObjectFreeze, Promise, + PromiseAll, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, PromiseResolve, Set, SetPrototypeEntries, @@ -147,12 +152,14 @@ } class GPUOutOfMemoryError extends Error { + name = "GPUOutOfMemoryError"; constructor() { - super(); + super("device out of memory"); } } class GPUValidationError extends Error { + name = "GPUValidationError"; /** @param {string} message */ constructor(message) { const prefix = "Failed to construct 'GPUValidationError'"; @@ -266,7 +273,7 @@ }); const nonGuaranteedFeatures = descriptor.nonGuaranteedFeatures ?? []; for (const feature of nonGuaranteedFeatures) { - if (!SetPrototypeHas(this[_adapter].features, feature)) { + if (!SetPrototypeHas(this[_adapter].features[_features], feature)) { throw new TypeError( `${prefix}: nonGuaranteedFeatures must be a subset of the adapter features.`, ); @@ -562,7 +569,7 @@ /** * @typedef ErrorScope * @property {string} filter - * @property {GPUError | undefined} error + * @property {Promise<void>[]} operations */ /** @@ -617,42 +624,73 @@ /** @param {{ type: string, value: string | null } | undefined} err */ pushError(err) { - if (err) { - switch (err.type) { - case "lost": - this.isLost = true; - this.resolveLost( - createGPUDeviceLostInfo(undefined, "device was lost"), - ); - break; - case "validation": - case "out-of-memory": - for ( - let i = this.errorScopeStack.length - 1; - i >= 0; - i-- - ) { - const scope = this.errorScopeStack[i]; - if (scope.filter == err.type) { - if (!scope.error) { - switch (err.type) { - case "validation": - scope.error = new GPUValidationError( - err.value ?? "validation error", - ); - break; - case "out-of-memory": - scope.error = new GPUOutOfMemoryError(); - break; - } - } - return; - } - } - // TODO(lucacasonato): emit a UncapturedErrorEvent - break; + this.pushErrorPromise(PromiseResolve(err)); + } + + /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ + pushErrorPromise(promise) { + const operation = PromisePrototypeThen(promise, (err) => { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + return PromiseReject( + new GPUValidationError(err.value ?? "validation error"), + ); + case "out-of-memory": + return PromiseReject(new GPUOutOfMemoryError()); + } } + }); + + const validationStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "validation", + ); + const validationScope = validationStack[validationStack.length - 1]; + const validationFilteredPromise = PromisePrototypeCatch( + operation, + (err) => { + if (err instanceof GPUValidationError) return PromiseReject(err); + return PromiseResolve(); + }, + ); + if (validationScope) { + ArrayPrototypePush( + validationScope.operations, + validationFilteredPromise, + ); + } else { + PromisePrototypeCatch(validationFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + + const oomStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "out-of-memory", + ); + const oomScope = oomStack[oomStack.length - 1]; + const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { + if (err instanceof GPUOutOfMemoryError) return PromiseReject(err); + return PromiseResolve(); + }); + if (oomScope) { + ArrayPrototypePush(oomScope.operations, oomFilteredPromise); + } else { + PromisePrototypeCatch(oomFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); } + // prevent uncaptured promise rejections + PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); } } @@ -1296,7 +1334,7 @@ context: "Argument 1", }); const device = assertDevice(this, { prefix, context: "this" }); - ArrayPrototypePush(device.errorScopeStack, { filter, error: undefined }); + ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); } /** @@ -1305,7 +1343,7 @@ // deno-lint-ignore require-await async popErrorScope() { webidl.assertBranded(this, GPUDevice); - const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; const device = assertDevice(this, { prefix, context: "this" }); if (device.isLost) { throw new DOMException("Device has been lost.", "OperationError"); @@ -1313,11 +1351,16 @@ const scope = ArrayPrototypePop(device.errorScopeStack); if (!scope) { throw new DOMException( - "There are no error scopes on that stack.", + "There are no error scopes on the error scope stack.", "OperationError", ); } - return scope.error ?? null; + const operations = PromiseAll(scope.operations); + return PromisePrototypeThen( + operations, + () => PromiseResolve(null), + (err) => PromiseResolve(err), + ); } [SymbolFor("Deno.privateCustomInspect")](inspect) { @@ -1686,17 +1729,24 @@ this[_mapMode] = mode; this[_state] = "mapping pending"; - const { err } = await core.opAsync( - "op_webgpu_buffer_get_map_async", - { - bufferRid, - deviceRid: device.rid, - mode, - offset, - size: rangeSize, - }, + const promise = PromisePrototypeThen( + core.opAsync( + "op_webgpu_buffer_get_map_async", + { + bufferRid, + deviceRid: device.rid, + mode, + offset, + size: rangeSize, + }, + ), + ({ err }) => err, ); - device.pushError(err); + device.pushErrorPromise(promise); + const err = await promise; + if (err) { + throw new DOMException("validation error occured", "OperationError"); + } this[_state] = "mapped"; this[_mappingRange] = [offset, offset + rangeSize]; /** @type {[ArrayBuffer, number, number][] | null} */ @@ -1729,6 +1779,7 @@ } else { rangeSize = size; } + const mappedRanges = this[_mappedRanges]; if (!mappedRanges) { throw new DOMException(`${prefix}: invalid state.`, "OperationError"); diff --git a/extensions/webgpu/buffer.rs b/extensions/webgpu/buffer.rs index 8e5075d8a..c9c93eeb6 100644 --- a/extensions/webgpu/buffer.rs +++ b/extensions/webgpu/buffer.rs @@ -2,6 +2,7 @@ use deno_core::error::bad_resource_id; use deno_core::error::null_opbuf; +use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::channel::oneshot; use deno_core::OpState; @@ -56,7 +57,8 @@ pub fn op_webgpu_create_buffer( let descriptor = wgpu_core::resource::BufferDescriptor { label: args.label.map(Cow::from), size: args.size, - usage: wgpu_types::BufferUsage::from_bits(args.usage).unwrap(), + usage: wgpu_types::BufferUsage::from_bits(args.usage) + .ok_or_else(|| type_error("usage is not valid"))?, mapped_at_creation: args.mapped_at_creation.unwrap_or(false), }; @@ -117,7 +119,7 @@ pub async fn op_webgpu_buffer_get_map_async( } // TODO(lucacasonato): error handling - gfx_select!(buffer => instance.buffer_map_async( + let maybe_err = gfx_select!(buffer => instance.buffer_map_async( buffer, args.offset..(args.offset + args.size), wgpu_core::resource::BufferMapOperation { @@ -129,7 +131,12 @@ pub async fn op_webgpu_buffer_get_map_async( callback: buffer_map_future_wrapper, user_data: sender_ptr, } - ))?; + )) + .err(); + + if maybe_err.is_some() { + return Ok(WebGpuResult::maybe_err(maybe_err)); + } } let done = Rc::new(RefCell::new(false)); |