vitest-environment-webgpu-node 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Houston
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # vitest-environment-webgpu-node
2
+
3
+ Real, headless WebGPU inside Vitest, with no browser. Powered by Google's Dawn through the
4
+ [`webgpu`](https://github.com/dawn-gpu/node-webgpu) npm package.
5
+
6
+ The environment puts `navigator.gpu` and every `GPU*` class and constant (`GPUBufferUsage`,
7
+ `GPUShaderStage`, ...) on the global object before each test file and removes them afterwards.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pnpm add -D vitest vitest-environment-webgpu-node
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ // vitest.config.ts
19
+ export default defineConfig({
20
+ test: {
21
+ environment: 'webgpu-node',
22
+ environmentOptions: { webgpuNode: { dawnOptions: ['backend=vulkan'] } },
23
+ },
24
+ });
25
+ ```
26
+
27
+ ```ts
28
+ import { expect, it } from 'vitest';
29
+
30
+ it('runs a compute shader', async () => {
31
+ const device = await (await navigator.gpu.requestAdapter())!.requestDevice();
32
+ // ...createShaderModule, dispatch, copy to a MAP_READ buffer, mapAsync, assert
33
+ });
34
+ ```
35
+
36
+ `dawnOptions` are passed straight to Dawn: `backend=<null|d3d11|d3d12|metal|vulkan|opengl|opengles>`,
37
+ `adapter=<name>`, `enable-dawn-features=...`, `disable-dawn-features=...`.
38
+
39
+ ## Canvas
40
+
41
+ Dawn has no `<canvas>`, so the environment ships a headless one whose `getContext('webgpu')` returns a
42
+ `GPUCanvasContext` backed by a texture. `document.createElement('canvas')`, `HTMLCanvasElement`, `window`,
43
+ `self` and `requestAnimationFrame` are installed too (only when missing), which is enough for three.js
44
+ `WebGPURenderer`, Babylon Lite and vgpu to run unchanged.
45
+
46
+ ```ts
47
+ import * as THREE from 'three/webgpu';
48
+ import { createCanvas } from 'vitest-environment-webgpu-node';
49
+
50
+ const canvas = createCanvas(256, 256);
51
+ const renderer = new THREE.WebGPURenderer({ canvas: canvas.asElement() });
52
+ await renderer.init();
53
+ await renderer.renderAsync(scene, camera);
54
+ expect(await canvas.readPixels()).toMatchScreenshot('scene');
55
+ ```
56
+
57
+ `readPixels()` returns `{ width, height, data }` RGBA8 pixels, ready for
58
+ [`vitest-screenshot`](../vitest-screenshot). `asElement()` is the same object typed as an
59
+ `HTMLCanvasElement` for library signatures that demand one.
@@ -0,0 +1,48 @@
1
+ /** RGBA8 pixels, top row first. Feed it to `vitest-screenshot`'s `toMatchScreenshot`. */
2
+ export type RgbaImage = {
3
+ width: number;
4
+ height: number;
5
+ data: Uint8Array;
6
+ };
7
+ /** Minimal `GPUCanvasContext`: `getCurrentTexture()` is a texture the size of the canvas, kept until reconfigured or resized. */
8
+ export declare class HeadlessCanvasContext {
9
+ #private;
10
+ readonly canvas: HeadlessCanvas;
11
+ constructor(canvas: HeadlessCanvas);
12
+ configure(config: GPUCanvasConfiguration): void;
13
+ unconfigure(): void;
14
+ getConfiguration(): GPUCanvasConfiguration | null;
15
+ getCurrentTexture(): GPUTexture;
16
+ /** Copies the current texture to the CPU. BGRA formats are swizzled to RGBA. */
17
+ readPixels(): Promise<RgbaImage>;
18
+ }
19
+ /** Enough of `HTMLCanvasElement` for three.js `WebGPURenderer`, Babylon Lite and friends. */
20
+ export declare class HeadlessCanvas {
21
+ #private;
22
+ width: number;
23
+ height: number;
24
+ readonly style: Record<string, string>;
25
+ constructor(width?: number, height?: number);
26
+ get clientWidth(): number;
27
+ get clientHeight(): number;
28
+ getContext(id: 'webgpu'): HeadlessCanvasContext;
29
+ getContext(id: string): HeadlessCanvasContext | null;
30
+ readPixels(): Promise<RgbaImage>;
31
+ /** This same object, typed as an element for libraries whose signatures demand one. */
32
+ asElement(): HTMLCanvasElement;
33
+ getBoundingClientRect(): {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ top: number;
39
+ left: number;
40
+ };
41
+ setAttribute(name: string, value: string): void;
42
+ getAttribute(name: string): string | null;
43
+ addEventListener(): void;
44
+ removeEventListener(): void;
45
+ dispatchEvent(): boolean;
46
+ }
47
+ export declare const createCanvas: (width: number, height: number) => HeadlessCanvas;
48
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +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;AAO5E,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,CAoBrC;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 ADDED
@@ -0,0 +1,110 @@
1
+ const COPY_SRC = 0x01;
2
+ const RENDER_ATTACHMENT = 0x10;
3
+ const MAP_READ = 0x01;
4
+ const COPY_DST = 0x08;
5
+ /** Minimal `GPUCanvasContext`: `getCurrentTexture()` is a texture the size of the canvas, kept until reconfigured or resized. */
6
+ export class HeadlessCanvasContext {
7
+ canvas;
8
+ #config;
9
+ #texture;
10
+ constructor(canvas) {
11
+ this.canvas = canvas;
12
+ }
13
+ configure(config) {
14
+ this.#config = config;
15
+ this.#drop();
16
+ }
17
+ unconfigure() {
18
+ this.#config = undefined;
19
+ this.#drop();
20
+ }
21
+ getConfiguration() {
22
+ return this.#config ?? null;
23
+ }
24
+ getCurrentTexture() {
25
+ const config = this.#config;
26
+ if (!config)
27
+ throw new Error('getCurrentTexture() called before configure()');
28
+ const { width, height } = this.canvas;
29
+ if (this.#texture && (this.#texture.width !== width || this.#texture.height !== height))
30
+ this.#drop();
31
+ this.#texture ??= config.device.createTexture({
32
+ size: [width, height],
33
+ format: config.format,
34
+ usage: (config.usage ?? RENDER_ATTACHMENT) | RENDER_ATTACHMENT | COPY_SRC,
35
+ viewFormats: config.viewFormats,
36
+ });
37
+ return this.#texture;
38
+ }
39
+ /** Copies the current texture to the CPU. BGRA formats are swizzled to RGBA. */
40
+ async readPixels() {
41
+ const texture = this.getCurrentTexture();
42
+ const { device, format } = this.#config;
43
+ const { width, height } = texture;
44
+ const bytesPerRow = Math.ceil((width * 4) / 256) * 256;
45
+ const buffer = device.createBuffer({ size: bytesPerRow * height, usage: MAP_READ | COPY_DST });
46
+ const encoder = device.createCommandEncoder();
47
+ encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [width, height]);
48
+ device.queue.submit([encoder.finish()]);
49
+ await buffer.mapAsync(MAP_READ);
50
+ const padded = new Uint8Array(buffer.getMappedRange());
51
+ const data = new Uint8Array(width * height * 4);
52
+ for (let y = 0; y < height; y++)
53
+ data.set(padded.subarray(y * bytesPerRow, y * bytesPerRow + width * 4), y * width * 4);
54
+ buffer.unmap();
55
+ buffer.destroy();
56
+ if (format.startsWith('bgra')) {
57
+ for (let i = 0; i < data.length; i += 4)
58
+ [data[i], data[i + 2]] = [data[i + 2], data[i]];
59
+ }
60
+ return { width, height, data };
61
+ }
62
+ #drop() {
63
+ this.#texture?.destroy();
64
+ this.#texture = undefined;
65
+ }
66
+ }
67
+ /** Enough of `HTMLCanvasElement` for three.js `WebGPURenderer`, Babylon Lite and friends. */
68
+ export class HeadlessCanvas {
69
+ width;
70
+ height;
71
+ #context = new HeadlessCanvasContext(this);
72
+ style = {};
73
+ #attributes = new Map();
74
+ constructor(width = 300, height = 150) {
75
+ this.width = width;
76
+ this.height = height;
77
+ }
78
+ get clientWidth() {
79
+ return this.width;
80
+ }
81
+ get clientHeight() {
82
+ return this.height;
83
+ }
84
+ getContext(id) {
85
+ return id === 'webgpu' ? this.#context : null;
86
+ }
87
+ readPixels() {
88
+ return this.#context.readPixels();
89
+ }
90
+ /** This same object, typed as an element for libraries whose signatures demand one. */
91
+ asElement() {
92
+ return this;
93
+ }
94
+ getBoundingClientRect() {
95
+ return { x: 0, y: 0, top: 0, left: 0, width: this.width, height: this.height };
96
+ }
97
+ setAttribute(name, value) {
98
+ this.#attributes.set(name, value);
99
+ }
100
+ getAttribute(name) {
101
+ return this.#attributes.get(name) ?? null;
102
+ }
103
+ addEventListener() { }
104
+ removeEventListener() { }
105
+ dispatchEvent() {
106
+ return true;
107
+ }
108
+ }
109
+ export const createCanvas = (width, height) => new HeadlessCanvas(width, height);
110
+ //# sourceMappingURL=canvas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Environment } from 'vitest/environments';
2
+ export { createCanvas, HeadlessCanvas, HeadlessCanvasContext, type RgbaImage } from './canvas.js';
3
+ /** Set under `test.environmentOptions.webgpuNode` in your Vitest config. */
4
+ export type WebgpuNodeOptions = {
5
+ /** Dawn options, e.g. `['backend=vulkan', 'enable-dawn-features=allow_unsafe_apis']`. */
6
+ dawnOptions?: string[];
7
+ };
8
+ declare const _default: Environment;
9
+ export default _default;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,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;wBA6Bc,WAAW"}
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ import { create, globals } from 'webgpu';
2
+ import { HeadlessCanvas } from './canvas.js';
3
+ export { createCanvas, HeadlessCanvas, HeadlessCanvasContext } from './canvas.js';
4
+ /** Just enough DOM for libraries that make their own canvas and drive a frame loop. Only installed when missing. */
5
+ const requestAnimationFrame = (callback) => setTimeout(() => callback(performance.now()), 16).unref();
6
+ const domShims = () => {
7
+ const window = {
8
+ devicePixelRatio: 1,
9
+ requestAnimationFrame,
10
+ cancelAnimationFrame: clearTimeout,
11
+ addEventListener() { },
12
+ removeEventListener() { },
13
+ };
14
+ return {
15
+ HTMLCanvasElement: HeadlessCanvas,
16
+ document: {
17
+ createElement: (tag) => (tag === 'canvas' ? new HeadlessCanvas() : {}),
18
+ createElementNS: (_ns, tag) => (tag === 'canvas' ? new HeadlessCanvas() : {}),
19
+ addEventListener() { },
20
+ removeEventListener() { },
21
+ },
22
+ window,
23
+ self: window,
24
+ requestAnimationFrame,
25
+ cancelAnimationFrame: clearTimeout,
26
+ };
27
+ };
28
+ export default {
29
+ name: 'webgpu-node',
30
+ viteEnvironment: 'ssr',
31
+ setup(global, { webgpuNode = {} }) {
32
+ const shims = { ...globals, ...domShims() };
33
+ const added = Object.keys(shims).filter((key) => !(key in global));
34
+ for (const key of added)
35
+ global[key] = shims[key];
36
+ const navigator = (global.navigator ??= {});
37
+ Object.defineProperty(navigator, 'gpu', { value: create(webgpuNode.dawnOptions ?? []), configurable: true });
38
+ return {
39
+ teardown(g) {
40
+ delete navigator.gpu;
41
+ for (const key of added)
42
+ delete g[key];
43
+ },
44
+ };
45
+ },
46
+ };
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,eAA4B;IAC1B,IAAI,EAAE,aAAa;IACnB,eAAe,EAAE,KAAK;IACtB,KAAK,CAAC,MAA+B,EAAE,EAAE,UAAU,GAAG,EAAE,EAAsC;QAC5F,MAAM,KAAK,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,EAA6B,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC;QACnE,KAAK,MAAM,GAAG,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,EAAE,CAAkB,CAAC;QAC7D,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7G,OAAO;YACL,QAAQ,CAAC,CAA0B;gBACjC,OAAO,SAAS,CAAC,GAAG,CAAC;gBACrB,KAAK,MAAM,GAAG,IAAI,KAAK;oBAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;YACzC,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "vitest-environment-webgpu-node",
3
+ "version": "0.1.0",
4
+ "description": "Vitest environment that provides real headless WebGPU (Dawn via the webgpu package) in Node",
5
+ "keywords": [
6
+ "dawn",
7
+ "headless",
8
+ "testing",
9
+ "vitest",
10
+ "vitest-environment",
11
+ "webgpu"
12
+ ],
13
+ "homepage": "https://github.com/bhouston/vitest-gpu#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/bhouston/vitest-gpu/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Ben Houston <ben@ben3d.ca> (https://ben3d.ca)",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/bhouston/vitest-gpu.git",
22
+ "directory": "packages/vitest-environment-webgpu-node"
23
+ },
24
+ "files": [
25
+ "dist/**/*.js",
26
+ "dist/**/*.js.map",
27
+ "dist/**/*.d.ts",
28
+ "dist/**/*.d.ts.map",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -b",
45
+ "tsc": "tsc -b"
46
+ },
47
+ "dependencies": {
48
+ "@webgpu/types": "^0.1.72",
49
+ "webgpu": "^0.6.1"
50
+ },
51
+ "peerDependencies": {
52
+ "vitest": ">=3 <5"
53
+ },
54
+ "engines": {
55
+ "node": ">=20.0.0"
56
+ }
57
+ }