vitest-environment-webgpu-node 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -10
- package/README.md +4 -2
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +38 -15
- package/dist/canvas.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/canvas.ts +147 -0
- package/src/index.ts +88 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
## [vitest-environment-webgpu-node-v1.0.
|
|
2
|
-
|
|
3
|
-
### ⚠ BREAKING CHANGES
|
|
4
|
-
|
|
5
|
-
* **screenshot:** the matcher is async and must be awaited; reference paths need an image extension.
|
|
6
|
-
|
|
7
|
-
### Features
|
|
8
|
-
|
|
9
|
-
* **screenshot:** accept canvases, images and any image format in toMatchScreenshot ([edaabc4](https://github.com/bhouston/vitest-gpu/commit/edaabc43ac0ab895601565129a8db38121cfa5d3)), closes [#1](https://github.com/bhouston/vitest-gpu/issues/1)
|
|
1
|
+
## [vitest-environment-webgpu-node-v1.0.1](https://github.com/bhouston/vitest-gpu/compare/vitest-environment-webgpu-node-v1.0.0...vitest-environment-webgpu-node-v1.0.1) (2026-09-18)
|
|
10
2
|
|
|
11
3
|
### Bug Fixes
|
|
12
4
|
|
|
13
|
-
*
|
|
5
|
+
* restore globals after GPU environment teardown ([4232c31](https://github.com/bhouston/vitest-gpu/commit/4232c31e0fa056ee91acd9f270cb0fbed8ca3c04)), closes [#17](https://github.com/bhouston/vitest-gpu/issues/17)
|
|
6
|
+
* ship sources referenced by package maps ([8db482e](https://github.com/bhouston/vitest-gpu/commit/8db482ed56714f375b53d9a30df3f080a86fb81d)), closes [#23](https://github.com/bhouston/vitest-gpu/issues/23)
|
|
7
|
+
* support Vitest 3 environment transforms ([446f7f9](https://github.com/bhouston/vitest-gpu/commit/446f7f9041d61c3e5e66a111575f167389c0919c)), closes [#16](https://github.com/bhouston/vitest-gpu/issues/16)
|
|
8
|
+
* **webgpu:** validate canvas readback formats ([b4c711d](https://github.com/bhouston/vitest-gpu/commit/b4c711d86eda2914d4041583e472d588a00205c7)), closes [#18](https://github.com/bhouston/vitest-gpu/issues/18)
|
package/README.md
CHANGED
|
@@ -55,5 +55,7 @@ await expect(canvas).toMatchScreenshot('scene.png');
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
`readPixels()` returns `{ width, height, data }` RGBA8 pixels, ready for
|
|
58
|
-
[`vitest-screenshot`](../vitest-screenshot).
|
|
59
|
-
`
|
|
58
|
+
[`vitest-screenshot`](../vitest-screenshot). Readback supports `rgba8unorm`, `rgba8unorm-srgb`,
|
|
59
|
+
`bgra8unorm`, and `bgra8unorm-srgb`; other canvas formats remain valid for rendering but
|
|
60
|
+
`readPixels()` rejects them with an unsupported-format error. `asElement()` is the same object typed
|
|
61
|
+
as an `HTMLCanvasElement` for library signatures that demand one.
|
package/dist/canvas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAa5E,iIAAiI;AACjI,qBAAa,qBAAqB;;IAIpB,QAAQ,CAAC,MAAM,EAAE,cAAc;IAA3C,YAAqB,MAAM,EAAE,cAAc,EAAI;IAE/C,SAAS,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAG9C;IAED,WAAW,IAAI,IAAI,CAGlB;IAED,gBAAgB,IAAI,sBAAsB,GAAG,IAAI,CAEhD;IAED,iBAAiB,IAAI,UAAU,CAY9B;IAED,gFAAgF;IAC1E,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAmCrC;CAMF;AAED,6FAA6F;AAC7F,qBAAa,cAAc;;IAMhB,KAAK;IACL,MAAM;IALf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAG5C,YACS,KAAK,SAAM,EACX,MAAM,SAAM,EACjB;IAEJ,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,qBAAqB,CAAC;IAChD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAAC;IAKrD,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAE/B;IAED,uFAAuF;IACvF,SAAS,IAAI,iBAAiB,CAE7B;IAED,qBAAqB,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAE1G;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExC;IAED,gBAAgB,IAAI,IAAI,CAAG;IAC3B,mBAAmB,IAAI,IAAI,CAAG;IAC9B,aAAa,IAAI,OAAO,CAEvB;CACF;AAED,eAAO,MAAM,YAAY,UAAW,MAAM,UAAU,MAAM,KAAG,cAAmD,CAAC"}
|
package/dist/canvas.js
CHANGED
|
@@ -2,6 +2,12 @@ const COPY_SRC = 0x01;
|
|
|
2
2
|
const RENDER_ATTACHMENT = 0x10;
|
|
3
3
|
const MAP_READ = 0x01;
|
|
4
4
|
const COPY_DST = 0x08;
|
|
5
|
+
const READABLE_FORMATS = new Set([
|
|
6
|
+
'rgba8unorm',
|
|
7
|
+
'rgba8unorm-srgb',
|
|
8
|
+
'bgra8unorm',
|
|
9
|
+
'bgra8unorm-srgb',
|
|
10
|
+
]);
|
|
5
11
|
/** Minimal `GPUCanvasContext`: `getCurrentTexture()` is a texture the size of the canvas, kept until reconfigured or resized. */
|
|
6
12
|
export class HeadlessCanvasContext {
|
|
7
13
|
canvas;
|
|
@@ -38,26 +44,43 @@ export class HeadlessCanvasContext {
|
|
|
38
44
|
}
|
|
39
45
|
/** Copies the current texture to the CPU. BGRA formats are swizzled to RGBA. */
|
|
40
46
|
async readPixels() {
|
|
47
|
+
const config = this.#config;
|
|
48
|
+
if (!config)
|
|
49
|
+
throw new Error('readPixels() called before configure()');
|
|
50
|
+
const { device, format } = config;
|
|
51
|
+
if (!READABLE_FORMATS.has(format)) {
|
|
52
|
+
throw new Error(`readPixels() does not support canvas format "${format}"; supported formats are rgba8unorm, rgba8unorm-srgb, bgra8unorm, and bgra8unorm-srgb`);
|
|
53
|
+
}
|
|
41
54
|
const texture = this.getCurrentTexture();
|
|
42
|
-
const { device, format } = this.#config;
|
|
43
55
|
const { width, height } = texture;
|
|
44
56
|
const bytesPerRow = Math.ceil((width * 4) / 256) * 256;
|
|
45
57
|
const buffer = device.createBuffer({ size: bytesPerRow * height, usage: MAP_READ | COPY_DST });
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
let mapped = false;
|
|
59
|
+
try {
|
|
60
|
+
const encoder = device.createCommandEncoder();
|
|
61
|
+
encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [width, height]);
|
|
62
|
+
device.queue.submit([encoder.finish()]);
|
|
63
|
+
await buffer.mapAsync(MAP_READ);
|
|
64
|
+
mapped = true;
|
|
65
|
+
const padded = new Uint8Array(buffer.getMappedRange());
|
|
66
|
+
const data = new Uint8Array(width * height * 4);
|
|
67
|
+
for (let y = 0; y < height; y++)
|
|
68
|
+
data.set(padded.subarray(y * bytesPerRow, y * bytesPerRow + width * 4), y * width * 4);
|
|
69
|
+
if (format.startsWith('bgra')) {
|
|
70
|
+
for (let i = 0; i < data.length; i += 4)
|
|
71
|
+
[data[i], data[i + 2]] = [data[i + 2], data[i]];
|
|
72
|
+
}
|
|
73
|
+
return { width, height, data };
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
try {
|
|
77
|
+
if (mapped)
|
|
78
|
+
buffer.unmap();
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
buffer.destroy();
|
|
82
|
+
}
|
|
59
83
|
}
|
|
60
|
-
return { width, height, data };
|
|
61
84
|
}
|
|
62
85
|
#drop() {
|
|
63
86
|
this.#texture?.destroy();
|
package/dist/canvas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAGA,MAAM,QAAQ,GAAG,IAAI,CAAC;AACtB,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC;AACtB,MAAM,QAAQ,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import type { Environment } from 'vitest/environments';
|
|
2
1
|
export { createCanvas, HeadlessCanvas, HeadlessCanvasContext, type RgbaImage } from './canvas.js';
|
|
3
2
|
/** Set under `test.environmentOptions.webgpuNode` in your Vitest config. */
|
|
4
3
|
export type WebgpuNodeOptions = {
|
|
5
4
|
/** Dawn options, e.g. `['backend=vulkan', 'enable-dawn-features=allow_unsafe_apis']`. */
|
|
6
5
|
dawnOptions?: string[];
|
|
7
6
|
};
|
|
8
|
-
declare const _default:
|
|
7
|
+
declare const _default: {
|
|
8
|
+
name: string;
|
|
9
|
+
transformMode: "ssr";
|
|
10
|
+
viteEnvironment: "ssr";
|
|
11
|
+
setup(global: Record<string, unknown>, { webgpuNode }: {
|
|
12
|
+
webgpuNode?: WebgpuNodeOptions;
|
|
13
|
+
}): {
|
|
14
|
+
teardown(_global?: Record<string, unknown>): void;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
9
17
|
export default _default;
|
|
10
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAElG,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;;;;;kBA0Cc,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAuB;QAAE,UAAU,CAAC,EAAE,iBAAiB,CAAA;KAAE;2BA8BvE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,21 +25,52 @@ const domShims = () => {
|
|
|
25
25
|
cancelAnimationFrame: clearTimeout,
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
|
+
const restore = (properties) => {
|
|
29
|
+
for (const { target, key, descriptor } of properties.toReversed()) {
|
|
30
|
+
if (descriptor)
|
|
31
|
+
Object.defineProperty(target, key, descriptor);
|
|
32
|
+
else
|
|
33
|
+
Reflect.deleteProperty(target, key);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
28
36
|
export default {
|
|
29
37
|
name: 'webgpu-node',
|
|
38
|
+
transformMode: 'ssr',
|
|
30
39
|
viteEnvironment: 'ssr',
|
|
31
40
|
setup(global, { webgpuNode = {} }) {
|
|
32
41
|
const shims = { ...globals, ...domShims() };
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const modified = [];
|
|
43
|
+
try {
|
|
44
|
+
for (const key of Object.keys(shims)) {
|
|
45
|
+
if (key in global)
|
|
46
|
+
continue;
|
|
47
|
+
modified.push({ target: global, key, descriptor: Object.getOwnPropertyDescriptor(global, key) });
|
|
48
|
+
global[key] = shims[key];
|
|
49
|
+
}
|
|
50
|
+
if (global.navigator == null) {
|
|
51
|
+
modified.push({
|
|
52
|
+
target: global,
|
|
53
|
+
key: 'navigator',
|
|
54
|
+
descriptor: Object.getOwnPropertyDescriptor(global, 'navigator'),
|
|
55
|
+
});
|
|
56
|
+
global.navigator = {};
|
|
57
|
+
}
|
|
58
|
+
const navigator = global.navigator;
|
|
59
|
+
modified.push({ target: navigator, key: 'gpu', descriptor: Object.getOwnPropertyDescriptor(navigator, 'gpu') });
|
|
60
|
+
Object.defineProperty(navigator, 'gpu', {
|
|
61
|
+
value: create(webgpuNode.dawnOptions ?? []),
|
|
62
|
+
writable: false,
|
|
63
|
+
enumerable: false,
|
|
64
|
+
configurable: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
restore(modified);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
38
71
|
return {
|
|
39
|
-
teardown(
|
|
40
|
-
|
|
41
|
-
for (const key of added)
|
|
42
|
-
delete g[key];
|
|
72
|
+
teardown(_global) {
|
|
73
|
+
restore(modified);
|
|
43
74
|
},
|
|
44
75
|
};
|
|
45
76
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAkB,MAAM,aAAa,CAAC;AAQlG,oHAAoH;AACpH,MAAM,qBAAqB,GAAG,CAAC,QAAgC,EAAkB,EAAE,CACjF,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAE5D,MAAM,QAAQ,GAAG,GAA4B,EAAE;IAC7C,MAAM,MAAM,GAAG;QACb,gBAAgB,EAAE,CAAC;QACnB,qBAAqB;QACrB,oBAAoB,EAAE,YAAY;QAClC,gBAAgB,KAAI,CAAC;QACrB,mBAAmB,KAAI,CAAC;KACzB,CAAC;IACF,OAAO;QACL,iBAAiB,EAAE,cAAc;QACjC,QAAQ,EAAE;YACR,aAAa,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,eAAe,EAAE,CAAC,GAAW,EAAE,GAAW,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7F,gBAAgB,KAAI,CAAC;YACrB,mBAAmB,KAAI,CAAC;SACzB;QACD,MAAM;QACN,IAAI,EAAE,MAAM;QACZ,qBAAqB;QACrB,oBAAoB,EAAE,YAAY;KACnC,CAAC;AACJ,CAAC,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-environment-webgpu-node",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Vitest environment that provides real headless WebGPU (Dawn via the webgpu package) in Node",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dawn",
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"dist/**/*.js.map",
|
|
27
27
|
"dist/**/*.d.ts",
|
|
28
28
|
"dist/**/*.d.ts.map",
|
|
29
|
+
"src/**/*.ts",
|
|
30
|
+
"!src/**/*.test.ts",
|
|
29
31
|
"CHANGELOG.md"
|
|
30
32
|
],
|
|
31
33
|
"type": "module",
|
package/src/canvas.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** RGBA8 pixels, top row first. Feed it to `vitest-screenshot`'s `toMatchScreenshot`. */
|
|
2
|
+
export type RgbaImage = { width: number; height: number; data: Uint8Array };
|
|
3
|
+
|
|
4
|
+
const COPY_SRC = 0x01;
|
|
5
|
+
const RENDER_ATTACHMENT = 0x10;
|
|
6
|
+
const MAP_READ = 0x01;
|
|
7
|
+
const COPY_DST = 0x08;
|
|
8
|
+
const READABLE_FORMATS: ReadonlySet<GPUTextureFormat> = new Set([
|
|
9
|
+
'rgba8unorm',
|
|
10
|
+
'rgba8unorm-srgb',
|
|
11
|
+
'bgra8unorm',
|
|
12
|
+
'bgra8unorm-srgb',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
/** Minimal `GPUCanvasContext`: `getCurrentTexture()` is a texture the size of the canvas, kept until reconfigured or resized. */
|
|
16
|
+
export class HeadlessCanvasContext {
|
|
17
|
+
#config: GPUCanvasConfiguration | undefined;
|
|
18
|
+
#texture: GPUTexture | undefined;
|
|
19
|
+
|
|
20
|
+
constructor(readonly canvas: HeadlessCanvas) {}
|
|
21
|
+
|
|
22
|
+
configure(config: GPUCanvasConfiguration): void {
|
|
23
|
+
this.#config = config;
|
|
24
|
+
this.#drop();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
unconfigure(): void {
|
|
28
|
+
this.#config = undefined;
|
|
29
|
+
this.#drop();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getConfiguration(): GPUCanvasConfiguration | null {
|
|
33
|
+
return this.#config ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getCurrentTexture(): GPUTexture {
|
|
37
|
+
const config = this.#config;
|
|
38
|
+
if (!config) throw new Error('getCurrentTexture() called before configure()');
|
|
39
|
+
const { width, height } = this.canvas;
|
|
40
|
+
if (this.#texture && (this.#texture.width !== width || this.#texture.height !== height)) this.#drop();
|
|
41
|
+
this.#texture ??= config.device.createTexture({
|
|
42
|
+
size: [width, height],
|
|
43
|
+
format: config.format,
|
|
44
|
+
usage: (config.usage ?? RENDER_ATTACHMENT) | RENDER_ATTACHMENT | COPY_SRC,
|
|
45
|
+
viewFormats: config.viewFormats,
|
|
46
|
+
});
|
|
47
|
+
return this.#texture;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Copies the current texture to the CPU. BGRA formats are swizzled to RGBA. */
|
|
51
|
+
async readPixels(): Promise<RgbaImage> {
|
|
52
|
+
const config = this.#config;
|
|
53
|
+
if (!config) throw new Error('readPixels() called before configure()');
|
|
54
|
+
const { device, format } = config;
|
|
55
|
+
if (!READABLE_FORMATS.has(format)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`readPixels() does not support canvas format "${format}"; supported formats are rgba8unorm, rgba8unorm-srgb, bgra8unorm, and bgra8unorm-srgb`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const texture = this.getCurrentTexture();
|
|
61
|
+
const { width, height } = texture;
|
|
62
|
+
const bytesPerRow = Math.ceil((width * 4) / 256) * 256;
|
|
63
|
+
const buffer = device.createBuffer({ size: bytesPerRow * height, usage: MAP_READ | COPY_DST });
|
|
64
|
+
let mapped = false;
|
|
65
|
+
try {
|
|
66
|
+
const encoder = device.createCommandEncoder();
|
|
67
|
+
encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [width, height]);
|
|
68
|
+
device.queue.submit([encoder.finish()]);
|
|
69
|
+
await buffer.mapAsync(MAP_READ);
|
|
70
|
+
mapped = true;
|
|
71
|
+
const padded = new Uint8Array(buffer.getMappedRange());
|
|
72
|
+
const data = new Uint8Array(width * height * 4);
|
|
73
|
+
for (let y = 0; y < height; y++)
|
|
74
|
+
data.set(padded.subarray(y * bytesPerRow, y * bytesPerRow + width * 4), y * width * 4);
|
|
75
|
+
if (format.startsWith('bgra')) {
|
|
76
|
+
for (let i = 0; i < data.length; i += 4) [data[i], data[i + 2]] = [data[i + 2]!, data[i]!];
|
|
77
|
+
}
|
|
78
|
+
return { width, height, data };
|
|
79
|
+
} finally {
|
|
80
|
+
try {
|
|
81
|
+
if (mapped) buffer.unmap();
|
|
82
|
+
} finally {
|
|
83
|
+
buffer.destroy();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#drop(): void {
|
|
89
|
+
this.#texture?.destroy();
|
|
90
|
+
this.#texture = undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Enough of `HTMLCanvasElement` for three.js `WebGPURenderer`, Babylon Lite and friends. */
|
|
95
|
+
export class HeadlessCanvas {
|
|
96
|
+
readonly #context = new HeadlessCanvasContext(this);
|
|
97
|
+
readonly style: Record<string, string> = {};
|
|
98
|
+
readonly #attributes = new Map<string, string>();
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
public width = 300,
|
|
102
|
+
public height = 150,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
get clientWidth(): number {
|
|
106
|
+
return this.width;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get clientHeight(): number {
|
|
110
|
+
return this.height;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getContext(id: 'webgpu'): HeadlessCanvasContext;
|
|
114
|
+
getContext(id: string): HeadlessCanvasContext | null;
|
|
115
|
+
getContext(id: string): HeadlessCanvasContext | null {
|
|
116
|
+
return id === 'webgpu' ? this.#context : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
readPixels(): Promise<RgbaImage> {
|
|
120
|
+
return this.#context.readPixels();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** This same object, typed as an element for libraries whose signatures demand one. */
|
|
124
|
+
asElement(): HTMLCanvasElement {
|
|
125
|
+
return this as unknown as HTMLCanvasElement;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getBoundingClientRect(): { x: number; y: number; width: number; height: number; top: number; left: number } {
|
|
129
|
+
return { x: 0, y: 0, top: 0, left: 0, width: this.width, height: this.height };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setAttribute(name: string, value: string): void {
|
|
133
|
+
this.#attributes.set(name, value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getAttribute(name: string): string | null {
|
|
137
|
+
return this.#attributes.get(name) ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
addEventListener(): void {}
|
|
141
|
+
removeEventListener(): void {}
|
|
142
|
+
dispatchEvent(): boolean {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const createCanvas = (width: number, height: number): HeadlessCanvas => new HeadlessCanvas(width, height);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Environment } from 'vitest/environments';
|
|
2
|
+
import { create, globals } from 'webgpu';
|
|
3
|
+
import { HeadlessCanvas } from './canvas.js';
|
|
4
|
+
|
|
5
|
+
export { createCanvas, HeadlessCanvas, HeadlessCanvasContext, type RgbaImage } from './canvas.js';
|
|
6
|
+
|
|
7
|
+
/** Set under `test.environmentOptions.webgpuNode` in your Vitest config. */
|
|
8
|
+
export type WebgpuNodeOptions = {
|
|
9
|
+
/** Dawn options, e.g. `['backend=vulkan', 'enable-dawn-features=allow_unsafe_apis']`. */
|
|
10
|
+
dawnOptions?: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Just enough DOM for libraries that make their own canvas and drive a frame loop. Only installed when missing. */
|
|
14
|
+
const requestAnimationFrame = (callback: (time: number) => void): NodeJS.Timeout =>
|
|
15
|
+
setTimeout(() => callback(performance.now()), 16).unref();
|
|
16
|
+
|
|
17
|
+
const domShims = (): Record<string, unknown> => {
|
|
18
|
+
const window = {
|
|
19
|
+
devicePixelRatio: 1,
|
|
20
|
+
requestAnimationFrame,
|
|
21
|
+
cancelAnimationFrame: clearTimeout,
|
|
22
|
+
addEventListener() {},
|
|
23
|
+
removeEventListener() {},
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
HTMLCanvasElement: HeadlessCanvas,
|
|
27
|
+
document: {
|
|
28
|
+
createElement: (tag: string) => (tag === 'canvas' ? new HeadlessCanvas() : {}),
|
|
29
|
+
createElementNS: (_ns: string, tag: string) => (tag === 'canvas' ? new HeadlessCanvas() : {}),
|
|
30
|
+
addEventListener() {},
|
|
31
|
+
removeEventListener() {},
|
|
32
|
+
},
|
|
33
|
+
window,
|
|
34
|
+
self: window,
|
|
35
|
+
requestAnimationFrame,
|
|
36
|
+
cancelAnimationFrame: clearTimeout,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type SavedProperty = { target: object; key: PropertyKey; descriptor?: PropertyDescriptor };
|
|
41
|
+
|
|
42
|
+
const restore = (properties: SavedProperty[]): void => {
|
|
43
|
+
for (const { target, key, descriptor } of properties.toReversed()) {
|
|
44
|
+
if (descriptor) Object.defineProperty(target, key, descriptor);
|
|
45
|
+
else Reflect.deleteProperty(target, key);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
name: 'webgpu-node',
|
|
51
|
+
transformMode: 'ssr',
|
|
52
|
+
viteEnvironment: 'ssr',
|
|
53
|
+
setup(global: Record<string, unknown>, { webgpuNode = {} }: { webgpuNode?: WebgpuNodeOptions }) {
|
|
54
|
+
const shims = { ...globals, ...domShims() } as Record<string, unknown>;
|
|
55
|
+
const modified: SavedProperty[] = [];
|
|
56
|
+
try {
|
|
57
|
+
for (const key of Object.keys(shims)) {
|
|
58
|
+
if (key in global) continue;
|
|
59
|
+
modified.push({ target: global, key, descriptor: Object.getOwnPropertyDescriptor(global, key) });
|
|
60
|
+
global[key] = shims[key];
|
|
61
|
+
}
|
|
62
|
+
if (global.navigator == null) {
|
|
63
|
+
modified.push({
|
|
64
|
+
target: global,
|
|
65
|
+
key: 'navigator',
|
|
66
|
+
descriptor: Object.getOwnPropertyDescriptor(global, 'navigator'),
|
|
67
|
+
});
|
|
68
|
+
global.navigator = {};
|
|
69
|
+
}
|
|
70
|
+
const navigator = global.navigator as object;
|
|
71
|
+
modified.push({ target: navigator, key: 'gpu', descriptor: Object.getOwnPropertyDescriptor(navigator, 'gpu') });
|
|
72
|
+
Object.defineProperty(navigator, 'gpu', {
|
|
73
|
+
value: create(webgpuNode.dawnOptions ?? []),
|
|
74
|
+
writable: false,
|
|
75
|
+
enumerable: false,
|
|
76
|
+
configurable: true,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
restore(modified);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
teardown(_global?: Record<string, unknown>) {
|
|
84
|
+
restore(modified);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
} satisfies Environment;
|