From 48899838f78ba974fa6096a1f9710c859f9dcdd5 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Fri, 28 Jun 2024 14:56:52 -0700 Subject: [PATCH] make it possible to build error-scope-wrapper.ts into show-error.js --- src/error-scope-wrapper.ts | 222 +++++++++++++++++++------------------ src/object-to-device.ts | 21 ++++ src/shared-state.ts | 29 ++--- 3 files changed, 142 insertions(+), 130 deletions(-) create mode 100644 src/object-to-device.ts diff --git a/src/error-scope-wrapper.ts b/src/error-scope-wrapper.ts index 2b4eae3..4d25a00 100644 --- a/src/error-scope-wrapper.ts +++ b/src/error-scope-wrapper.ts @@ -1,119 +1,123 @@ +/* eslint-disable no-inner-declarations */ import { s_objToDevice -} from './shared-state.js'; - -const deviceToErrorScopeStack: WeakMap[]}[]> = new WeakMap(); -const origPushErrorScope = GPUDevice.prototype.pushErrorScope; -const origPopErrorScope = GPUDevice.prototype.popErrorScope; - -type AnyFunction = (...args: any[]) => any; - -function errorWrapper(this: any, device: GPUDevice, fnName: string, origFn: T, ...args: Parameters): ReturnType { - const stack = new Error(); - origPushErrorScope.call(device, 'validation'); - const result = origFn.call(this, ...args); - - const errorScopeStack = deviceToErrorScopeStack.get(device)!; - const currentErrorScope = errorScopeStack.findLast(scope => scope.filter === 'validation'); - - const promise = origPopErrorScope.call(device) - .then(error => { - // If there was a currentErrorScope when we added pushed then remove our promise - if (currentErrorScope) { - const ndx = currentErrorScope.errors.indexOf(promise); - if (ndx) { - currentErrorScope.errors.splice(ndx, 1); +} from './object-to-device.js'; + +if (typeof GPUDevice !== 'undefined') { + + const deviceToErrorScopeStack: WeakMap[]}[]> = new WeakMap(); + const origPushErrorScope = GPUDevice.prototype.pushErrorScope; + const origPopErrorScope = GPUDevice.prototype.popErrorScope; + + type AnyFunction = (...args: any[]) => any; + + function errorWrapper(this: any, device: GPUDevice, fnName: string, origFn: T, ...args: Parameters): ReturnType { + const stack = new Error(); + origPushErrorScope.call(device, 'validation'); + const result = origFn.call(this, ...args); + + const errorScopeStack = deviceToErrorScopeStack.get(device)!; + const currentErrorScope = errorScopeStack.findLast(scope => scope.filter === 'validation'); + + const promise = origPopErrorScope.call(device) + .then(error => { + // If there was a currentErrorScope when we added pushed then remove our promise + if (currentErrorScope) { + const ndx = currentErrorScope.errors.indexOf(promise); + if (ndx) { + currentErrorScope.errors.splice(ndx, 1); + } + } else { + // there was no currentErrorScope so emit the error + if (error) { + device.dispatchEvent(new GPUUncapturedErrorEvent('uncapturederror', { error })); + } } - } else { - // there was no currentErrorScope so emit the error + + // show it if (error) { - device.dispatchEvent(new GPUUncapturedErrorEvent('uncapturederror', { error })); + console.error('WebGPU ERROR in:', fnName, args); + console.error(error.message); + console.error(stack.stack); } - } - // show it - if (error) { - console.error('WebGPU ERROR in:', fnName, args); - console.error(error.message); - console.error(stack.stack); - } + // return it (as a promise) + return error; + }); - // return it (as a promise) - return error; - }); + if (currentErrorScope) { + currentErrorScope.errors.push(promise); + } + return result; + } - if (currentErrorScope) { - currentErrorScope.errors.push(promise); + function addErrorWrapper(api: T, fnName: keyof T['prototype'] & PropertyKey): void { + const origFn = api.prototype[fnName] as AnyFunction; + api.prototype[fnName] = function (this: any, ...args: any[]) { + return errorWrapper.call(this, this, fnName.toString(), origFn, ...args); + }; } - return result; -} - -function addErrorWrapper(api: T, fnName: keyof T['prototype'] & PropertyKey): void { - const origFn = api.prototype[fnName] as AnyFunction; - api.prototype[fnName] = function (this: any, ...args: any[]) { - return errorWrapper.call(this, this, fnName.toString(), origFn, ...args); - }; -} - -function addErrorWrapperWithDevice(api: T, fnName: keyof T['prototype'] & PropertyKey): void { - const origFn = api.prototype[fnName] as AnyFunction; - api.prototype[fnName] = function (this: any, ...args: any[]) { - const device = s_objToDevice.get(this as GPUQueue)!; - return errorWrapper.call(this, device, fnName.toString(), origFn, ...args); - }; -} - -/** - * given a class returns all the method names. - */ -function getAPIFunctionNames(api: T) { - return Object.entries(Object.getOwnPropertyDescriptors(api.prototype)) - .filter(([, info]) => info.enumerable && typeof info.value === 'function') - .map(([name]) => name as keyof T['prototype'] & PropertyKey); -} - -const skip = new Set([ - 'pushErrorScope', - 'popErrorScope', - 'destroy', -]); -getAPIFunctionNames(GPUDevice) - .filter(n => !skip.has(n)) - .forEach(n => addErrorWrapper(GPUDevice, n)); -getAPIFunctionNames(GPUQueue) - .forEach(n => addErrorWrapperWithDevice(GPUQueue, n)); - -GPUDevice.prototype.pushErrorScope = (function (origFn) { - return function (this: GPUDevice, filter: GPUErrorFilter) { - origFn.call(this, filter); - const errorScopeStack = deviceToErrorScopeStack.get(this); - errorScopeStack!.push({filter, errors: []}); - }; -})(GPUDevice.prototype.pushErrorScope); - -GPUDevice.prototype.popErrorScope = (function (origFn) { - return async function (this: GPUDevice) { - const errorScopeStack = deviceToErrorScopeStack.get(this); - const errorScope = errorScopeStack!.pop(); - if (errorScope === undefined) { - throw new DOMException('popErrorScope called on empty error scope stack', 'OperationError'); - } - const errPromise = origFn.call(this); - return errorScope.errors.pop() ?? errPromise; - }; -})(GPUDevice.prototype.popErrorScope); - -GPUAdapter.prototype.requestDevice = (function (origFn) { - return async function (this: GPUAdapter, ...args) { - const device = await origFn.call(this, ...args); - if (device) { - device.addEventListener('uncapturederror', function (e) { - console.error((e as GPUUncapturedErrorEvent).error.message); - }); - deviceToErrorScopeStack.set(device, []); - s_objToDevice.set(device.queue, device); - } - return device; - }; -})(GPUAdapter.prototype.requestDevice); + function addErrorWrapperWithDevice(api: T, fnName: keyof T['prototype'] & PropertyKey): void { + const origFn = api.prototype[fnName] as AnyFunction; + api.prototype[fnName] = function (this: any, ...args: any[]) { + const device = s_objToDevice.get(this as GPUQueue)!; + return errorWrapper.call(this, device, fnName.toString(), origFn, ...args); + }; + } + + /** + * given a class returns all the method names. + */ + function getAPIFunctionNames(api: T) { + return Object.entries(Object.getOwnPropertyDescriptors(api.prototype)) + .filter(([, info]) => info.enumerable && typeof info.value === 'function') + .map(([name]) => name as keyof T['prototype'] & PropertyKey); + } + + const skip = new Set([ + 'pushErrorScope', + 'popErrorScope', + 'destroy', + ]); + getAPIFunctionNames(GPUDevice) + .filter(n => !skip.has(n)) + .forEach(n => addErrorWrapper(GPUDevice, n)); + getAPIFunctionNames(GPUQueue) + .forEach(n => addErrorWrapperWithDevice(GPUQueue, n)); + + GPUDevice.prototype.pushErrorScope = (function (origFn) { + return function (this: GPUDevice, filter: GPUErrorFilter) { + origFn.call(this, filter); + const errorScopeStack = deviceToErrorScopeStack.get(this); + errorScopeStack!.push({filter, errors: []}); + }; + })(GPUDevice.prototype.pushErrorScope); + + GPUDevice.prototype.popErrorScope = (function (origFn) { + return async function (this: GPUDevice) { + const errorScopeStack = deviceToErrorScopeStack.get(this); + const errorScope = errorScopeStack!.pop(); + if (errorScope === undefined) { + throw new DOMException('popErrorScope called on empty error scope stack', 'OperationError'); + } + const errPromise = origFn.call(this); + return errorScope.errors.pop() ?? errPromise; + }; + })(GPUDevice.prototype.popErrorScope); + + GPUAdapter.prototype.requestDevice = (function (origFn) { + return async function (this: GPUAdapter, ...args) { + const device = await origFn.call(this, ...args); + if (device) { + device.addEventListener('uncapturederror', function (e) { + console.error((e as GPUUncapturedErrorEvent).error.message); + }); + deviceToErrorScopeStack.set(device, []); + s_objToDevice.set(device.queue, device); + } + return device; + }; + })(GPUAdapter.prototype.requestDevice); + +} \ No newline at end of file diff --git a/src/object-to-device.ts b/src/object-to-device.ts new file mode 100644 index 0000000..2b107ce --- /dev/null +++ b/src/object-to-device.ts @@ -0,0 +1,21 @@ +export type DeviceResource = + | GPUBindGroup + | GPUBindGroupLayout + | GPUBuffer + | GPUCanvasContext + | GPUCommandEncoder + | GPUComputePassEncoder + | GPUComputePipeline + | GPUExternalTexture + | GPUPipelineLayout + | GPUQuerySet + | GPUQueue + | GPURenderBundle + | GPURenderBundleEncoder + | GPURenderPassEncoder + | GPURenderPipeline + | GPUSampler + | GPUShaderModule + | GPUTexture + +export const s_objToDevice = new WeakMap(); diff --git a/src/shared-state.ts b/src/shared-state.ts index 739a905..9ac1797 100644 --- a/src/shared-state.ts +++ b/src/shared-state.ts @@ -1,3 +1,7 @@ +import { + DeviceResource, + s_objToDevice, +} from './object-to-device.js'; import { BindGroupLayoutDescriptorPlus } from './pipeline.js'; import { assert, @@ -7,27 +11,10 @@ import { wrapFunctionBefore, } from './wrap-api.js'; -export type DeviceResource = - | GPUBindGroup - | GPUBindGroupLayout - | GPUBuffer - | GPUCanvasContext - | GPUCommandEncoder - | GPUComputePassEncoder - | GPUComputePipeline - | GPUExternalTexture - | GPUPipelineLayout - | GPUQuerySet - | GPUQueue - | GPURenderBundle - | GPURenderBundleEncoder - | GPURenderPassEncoder - | GPURenderPipeline - | GPUSampler - | GPUShaderModule - | GPUTexture - -export const s_objToDevice = new WeakMap(); +export { + DeviceResource, + s_objToDevice, +}; export type Destroyable = GPUTexture | GPUBuffer | GPUQuerySet | GPUDevice;