-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
make it possible to build error-scope-wrapper.ts into show-error.js
- Loading branch information
Showing
3 changed files
with
142 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,123 @@ | ||
/* eslint-disable no-inner-declarations */ | ||
import { | ||
s_objToDevice | ||
} from './shared-state.js'; | ||
|
||
const deviceToErrorScopeStack: WeakMap<GPUDevice, {filter: GPUErrorFilter, errors: Promise<GPUError | null>[]}[]> = new WeakMap(); | ||
const origPushErrorScope = GPUDevice.prototype.pushErrorScope; | ||
const origPopErrorScope = GPUDevice.prototype.popErrorScope; | ||
|
||
type AnyFunction = (...args: any[]) => any; | ||
|
||
function errorWrapper<T extends AnyFunction>(this: any, device: GPUDevice, fnName: string, origFn: T, ...args: Parameters<T>): ReturnType<T> { | ||
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<GPUDevice, {filter: GPUErrorFilter, errors: Promise<GPUError | null>[]}[]> = new WeakMap(); | ||
const origPushErrorScope = GPUDevice.prototype.pushErrorScope; | ||
const origPopErrorScope = GPUDevice.prototype.popErrorScope; | ||
|
||
type AnyFunction = (...args: any[]) => any; | ||
|
||
function errorWrapper<T extends AnyFunction>(this: any, device: GPUDevice, fnName: string, origFn: T, ...args: Parameters<T>): ReturnType<T> { | ||
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<T extends { prototype: any }>(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<T extends { prototype: any }>(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<T extends { prototype: any }>(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<T extends { prototype: any }>(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<T extends { prototype: any }>(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<T extends { prototype: any }>(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); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<DeviceResource, GPUDevice>(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters