Skip to content

Commit

Permalink
make it possible to build error-scope-wrapper.ts into show-error.js
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Jun 28, 2024
1 parent 509ebb8 commit 4889983
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 130 deletions.
222 changes: 113 additions & 109 deletions src/error-scope-wrapper.ts
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);

}
21 changes: 21 additions & 0 deletions src/object-to-device.ts
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>();
29 changes: 8 additions & 21 deletions src/shared-state.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
DeviceResource,
s_objToDevice,
} from './object-to-device.js';
import { BindGroupLayoutDescriptorPlus } from './pipeline.js';
import {
assert,
Expand All @@ -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<DeviceResource, GPUDevice>();
export {
DeviceResource,
s_objToDevice,
};

export type Destroyable = GPUTexture | GPUBuffer | GPUQuerySet | GPUDevice;

Expand Down

0 comments on commit 4889983

Please sign in to comment.