wgsl-test 0.2.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.
Files changed (40) hide show
  1. package/README.md +111 -0
  2. package/dist/index.d.ts +230 -0
  3. package/dist/index.js +11556 -0
  4. package/dist/index.js.map +1 -0
  5. package/images/lemur256.png +0 -0
  6. package/images/lemur512.png +0 -0
  7. package/package.json +61 -0
  8. package/src/CompileShader.ts +185 -0
  9. package/src/ExampleImages.ts +27 -0
  10. package/src/ImageHelpers.ts +38 -0
  11. package/src/ShaderModuleLoader.ts +46 -0
  12. package/src/TestComputeShader.ts +178 -0
  13. package/src/TestFragmentShader.ts +216 -0
  14. package/src/WebGPUTestSetup.ts +30 -0
  15. package/src/index.ts +41 -0
  16. package/src/test/ImageSnapshot.test.ts +212 -0
  17. package/src/test/PackageImport.test.ts +110 -0
  18. package/src/test/TestComputeShader.test.ts +231 -0
  19. package/src/test/TestFragmentShader.test.ts +397 -0
  20. package/src/test/__image_snapshots__/box-blur.png +0 -0
  21. package/src/test/__image_snapshots__/edge-detection.png +0 -0
  22. package/src/test/__image_snapshots__/effects-checkerboard.png +0 -0
  23. package/src/test/__image_snapshots__/grayscale.png +0 -0
  24. package/src/test/__image_snapshots__/lemur-sharpen.png +0 -0
  25. package/src/test/__image_snapshots__/solid-red-small.png +0 -0
  26. package/src/test/__image_snapshots__/solid_red.png +0 -0
  27. package/src/test/fixtures/test_shader_pkg/package.json +10 -0
  28. package/src/test/fixtures/test_shader_pkg/shaders/algorithms/compute_multiply.wgsl +5 -0
  29. package/src/test/fixtures/test_shader_pkg/shaders/compute_sum.wgsl +5 -0
  30. package/src/test/fixtures/test_shader_pkg/shaders/effects/checkerboard.wgsl +11 -0
  31. package/src/test/fixtures/test_shader_pkg/shaders/foo/bar/zap.wesl +5 -0
  32. package/src/test/fixtures/test_shader_pkg/shaders/foo/bar.wesl +4 -0
  33. package/src/test/fixtures/test_shader_pkg/shaders/legacy.wgsl +3 -0
  34. package/src/test/fixtures/test_shader_pkg/shaders/math.wesl +3 -0
  35. package/src/test/fixtures/test_shader_pkg/shaders/nested/deeper/func.wesl +3 -0
  36. package/src/test/fixtures/test_shader_pkg/shaders/priority.wesl +9 -0
  37. package/src/test/fixtures/test_shader_pkg/shaders/solid_red.wgsl +4 -0
  38. package/src/test/fixtures/test_shader_pkg/shaders/utils.wesl +3 -0
  39. package/src/test/fixtures/test_shader_pkg/wesl.toml +3 -0
  40. package/src/test/fixtures/test_shader_pkg/weslBundle.js +16 -0
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # wgsl-test
2
+
3
+ Write GPU shader tests as easily as regular unit tests. Test WGSL and WESL shaders with vitest or your favorite Node.js test framework.
4
+
5
+ - **Test WGSL shaders** - Works with standard `.wgsl` files, no new syntax required
6
+ - **Test WESL shaders** - Import and compose shader dependencies via WESL
7
+ - **Visual regression testing** - Snapshot comparison catches rendering changes
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install wgsl-test
13
+ ```
14
+
15
+ Quick start in 3 steps:
16
+
17
+ 1. Write your shader function in WGSL or WESL as normal
18
+ 2. Use `testCompute()`, `testFragment()`, `testFragmentImage()`, or `expectFragmentImage()` to test your shader with inline source or from files
19
+ 3. Assert the results with your test framework
20
+
21
+ ## Testing Compute Shaders
22
+
23
+ The default choice for unit testing shader functions. Flexible and explicit.
24
+
25
+ Use `testCompute()` to test compute shader logic. A `test::results` buffer is automatically provided:
26
+
27
+ ```typescript
28
+ import { testCompute, getGPUDevice } from "wgsl-test";
29
+
30
+ const device = await getGPUDevice();
31
+
32
+ const src = `
33
+ import package::hash::lowbias32;
34
+
35
+ @compute @workgroup_size(1)
36
+ fn main() {
37
+ test::results[0] = lowbias32(0u);
38
+ test::results[1] = lowbias32(42u);
39
+ }
40
+ `;
41
+
42
+ const result = await testCompute({ device, src, size: 2 });
43
+ // result = [0, 388445122]
44
+ ```
45
+
46
+ **[See API.md for complete API documentation →](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wgsl-test/API.md#testcompute)**
47
+
48
+ ## Testing Fragment Shaders
49
+
50
+ For unit testing shader functions that only run in fragment shaders. Tests a single pixel output.
51
+
52
+ Use `testFragment()` to test fragment shader rendering.
53
+
54
+ ```rs
55
+ /// shaders/foo.wesl
56
+ fn bar(p: vec4f) -> vec4f {
57
+ return 2 * sqrt(p);
58
+ }
59
+ ```
60
+
61
+ ```typescript
62
+ const src = `
63
+ @fragment
64
+ fn fs_main(@builtin(position) pos:vec4f) -> @location(0) vec4f {
65
+ return foo::bar(pos * 2);
66
+ }
67
+ `;
68
+
69
+ const result = await testFragment({ device, src });
70
+ // result = [2.828, 1.414, 0.0, 2.0] // vec4f color at pixel (0,0)
71
+ ```
72
+
73
+ **[See API.md for derivatives, input textures, uniforms, and more →](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wgsl-test/API.md#testfragment)**
74
+
75
+ ## Visual Regression Testing
76
+
77
+ Higher level testing, good for regression. Tests don't provide much numeric descriptive value but catch visual changes automatically.
78
+
79
+ Test complete rendered images:
80
+
81
+ ```typescript
82
+ import { expectFragmentImage, imageMatcher } from "wgsl-test";
83
+
84
+ imageMatcher(); // Setup once
85
+
86
+ test("blur shader matches snapshot", async () => {
87
+ await expectFragmentImage(device, "effects/blur.wgsl", {
88
+ projectDir: import.meta.url,
89
+ size: [256, 256],
90
+ });
91
+ // Snapshot automatically compared against __image_snapshots__/effects-blur.png
92
+ });
93
+ ```
94
+
95
+ Snapshot comparison automatically detects rendering changes. Update snapshots with `vitest -u` when changes are intentional.
96
+
97
+ **[See API.md for snapshot workflow and visual regression testing →](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wgsl-test/API.md#visual-regression-testing)**
98
+
99
+ ## API Documentation
100
+
101
+ - **[API.md](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wgsl-test/API.md)** - Complete API reference with detailed examples
102
+ - **[API.md#complete-test-example](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wgsl-test/API.md#complete-test-example)** - Full vitest test setup with beforeAll/afterAll
103
+ - **[Examples](https://github.com/wgsl-tooling-wg/wesl-js/tree/main/tools/examples)** - Tiny standalone examples
104
+
105
+ ## Future
106
+ What would you like to see next in wgsl-test?
107
+ Test scaffolding for vertex shaders?
108
+ Annotations to put simple tests in WESL directly?
109
+ Something else?
110
+
111
+ Please file an [issue](https://github.com/wgsl-tooling-wg/wesl-js/issues) or talk about your ideas on the tooling group [discord chat](https://discord.gg/5UhkaSu4dt).
@@ -0,0 +1,230 @@
1
+ import { DeviceCache, RenderUniforms, RenderUniforms as RenderUniforms$1, SamplerOptions, checkerboardTexture, colorBarsTexture, createSampler, createUniformsVirtualLib, edgePatternTexture, fullscreenTriangleVertex, gradientTexture, noiseTexture, radialGradientTexture, renderUniformBuffer, simpleRender, solidTexture, updateRenderUniforms, withErrorScopes } from "wesl-gpu";
2
+ import { LinkParams, ModuleResolver, WeslBundle } from "wesl";
3
+ import { WgslElementType, WgslElementType as WgslElementType$1 } from "thimbleberry";
4
+ import { ImageData, ImageData as ImageData$1, MatchImageOptions } from "vitest-image-snapshot";
5
+
6
+ //#region src/CompileShader.d.ts
7
+ interface ShaderContext {
8
+ /** Dependency bundles for the shader. */
9
+ libs: WeslBundle[];
10
+ /** Resolver for lazy loading (when useSourceShaders is true). */
11
+ resolver?: ModuleResolver;
12
+ /** Package name for module resolution. */
13
+ packageName?: string;
14
+ }
15
+ interface ResolveContextParams {
16
+ /** WESL/WGSL shader source code. */
17
+ src: string;
18
+ /** Project directory for resolving dependencies. */
19
+ projectDir?: string;
20
+ /** Use source shaders instead of built bundles. Default: true. */
21
+ useSourceShaders?: boolean;
22
+ }
23
+ interface CompileShaderParams {
24
+ /** Project directory for resolving shader dependencies.
25
+ * Used to locate installed npm shader libraries.
26
+ * Optional: defaults to searching upward from cwd for package.json or wesl.toml. */
27
+ projectDir?: string;
28
+ /** GPU device to use for shader compilation. */
29
+ device: GPUDevice;
30
+ /** WESL/WGSL shader source code to compile. */
31
+ src: string;
32
+ /** Conditions for conditional compilation.
33
+ * Used to control `@if` directives in the shader. */
34
+ conditions?: LinkParams["conditions"];
35
+ /** Constants for shader compilation.
36
+ * Injects host-provided values via the `constants::` namespace. */
37
+ constants?: LinkParams["constants"];
38
+ /** Virtual libraries to include in the shader.
39
+ * Allows dynamic generation of shader code at runtime. */
40
+ virtualLibs?: LinkParams["virtualLibs"];
41
+ /** Use source shaders from current package instead of built bundles.
42
+ * Default: true for faster iteration during development.
43
+ * Set to false or use TEST_BUNDLES=true environment variable to test built bundles.
44
+ *
45
+ * Precedence: explicit parameter > TEST_BUNDLES env var > default (true)
46
+ */
47
+ useSourceShaders?: boolean;
48
+ }
49
+ /**
50
+ * Compiles a WESL shader source string into a GPUShaderModule with automatic dependency resolution.
51
+ *
52
+ * Parses the shader source to detect references to shader packages, then automatically
53
+ * includes the required npm package bundles. By default, loads source shaders from the
54
+ * current package for fast iteration without requiring rebuilds.
55
+ *
56
+ * @returns Compiled GPUShaderModule ready for use in render or compute pipelines
57
+ * @throws Error if shader compilation fails with compilation error details
58
+ */
59
+ declare function compileShader(params: CompileShaderParams): Promise<GPUShaderModule>;
60
+ /** Resolve project context for shader compilation: bundles, resolver, and package name. */
61
+ declare function resolveShaderContext(params: ResolveContextParams): Promise<ShaderContext>;
62
+ /** Create a project resolver for loading modules from the filesystem.
63
+ * Handles wesl.toml configuration and creates FileModuleResolver with correct baseDir.
64
+ *
65
+ * @param projectDir Project directory (defaults to cwd)
66
+ * @param packageName Package name for module resolution (optional)
67
+ * @returns FileModuleResolver configured for the project
68
+ */
69
+ declare function createProjectResolver(projectDir?: string, packageName?: string): Promise<ModuleResolver>;
70
+ //#endregion
71
+ //#region src/ExampleImages.d.ts
72
+ /** return a texture to the bundled lemur test image. */
73
+ declare function lemurTexture(device: GPUDevice, size?: 256 | 512): Promise<GPUTexture>;
74
+ /** Get the path to the bundled lemur test image. */
75
+ declare function lemurImagePath(size?: 256 | 512): string;
76
+ //#endregion
77
+ //#region src/ImageHelpers.d.ts
78
+ /** Load PNG file and create GPU texture. */
79
+ declare function pngToTexture(device: GPUDevice, imagePath: string): Promise<GPUTexture>;
80
+ //#endregion
81
+ //#region src/TestComputeShader.d.ts
82
+ interface ComputeTestParams {
83
+ /** WESL/WGSL source code for the compute shader to test.
84
+ * Either src or moduleName must be provided, but not both. */
85
+ src?: string;
86
+ /** Name of shader module to load from filesystem.
87
+ * Supports: bare name (sum.wgsl), path (algorithms/sum.wgsl), or module path (package::algorithms::sum).
88
+ * Either src or moduleName must be provided, but not both. */
89
+ moduleName?: string;
90
+ /** Project directory for resolving shader dependencies.
91
+ * Allows the shader to import from npm shader libraries.
92
+ * Optional: defaults to searching upward from cwd for package.json or wesl.toml.
93
+ * Typically use `import.meta.url`. */
94
+ projectDir?: string;
95
+ /** GPU device for running the tests.
96
+ * Typically use `getGPUDevice()` from wgsl-test. */
97
+ device: GPUDevice;
98
+ /** Format of the result buffer. Default: "u32" */
99
+ resultFormat?: WgslElementType$1;
100
+ /** Size of result buffer in elements. Default: 4 */
101
+ size?: number;
102
+ /** Flags for conditional compilation to test shader specialization.
103
+ * Useful for testing `@if` statements in the shader. */
104
+ conditions?: LinkParams["conditions"];
105
+ /** Constants for shader compilation.
106
+ * Injects host-provided values via the `constants::` namespace. */
107
+ constants?: LinkParams["constants"];
108
+ /** Use source shaders from current package instead of built bundles.
109
+ * Default: true for faster iteration during development. */
110
+ useSourceShaders?: boolean;
111
+ /** Number of workgroups to dispatch. Default: 1
112
+ * Can be a single number or [x, y, z] for multi-dimensional dispatch. */
113
+ dispatchWorkgroups?: number | [number, number, number];
114
+ }
115
+ /**
116
+ * Compiles and runs a compute shader on the GPU for testing.
117
+ *
118
+ * Provides a storage buffer available at `test::results` where the shader
119
+ * can write test output. After execution, the storage buffer is copied back
120
+ * to the CPU and returned for validation.
121
+ *
122
+ * Shader libraries mentioned in the source are automatically resolved from node_modules.
123
+ *
124
+ * @returns Array of numbers from the storage buffer (typically 4 elements for u32/f32 format)
125
+ */
126
+ declare function testCompute(params: ComputeTestParams): Promise<number[]>;
127
+ /**
128
+ * Runs a compiled compute shader and returns the result buffer.
129
+ *
130
+ * Creates a storage buffer at @group(0) @binding(0) where the shader can
131
+ * write output. The shader is invoked once, then the buffer is copied back
132
+ * to the CPU for reading.
133
+ *
134
+ * @param module - The compiled GPUShaderModule containing the compute shader
135
+ * @param resultFormat - Format for interpreting result buffer data (default: u32)
136
+ * @param size - Size of result buffer in bytes (default: 16)
137
+ * @param dispatchWorkgroups - Number of workgroups to dispatch (default: 1)
138
+ * @returns Array containing the shader's output from the storage buffer
139
+ */
140
+ declare function runCompute(device: GPUDevice, module: GPUShaderModule, resultFormat?: WgslElementType$1, size?: number, dispatchWorkgroups?: number | [number, number, number]): Promise<number[]>;
141
+ //#endregion
142
+ //#region src/TestFragmentShader.d.ts
143
+ interface FragmentTestParams {
144
+ /** WESL/WGSL source code for the fragment shader to test.
145
+ * Either src or moduleName must be provided, but not both. */
146
+ src?: string;
147
+ /** Name of shader module to load from filesystem.
148
+ * Supports: bare name (blur.wgsl), path (effects/blur.wgsl), or module path (package::effects::blur).
149
+ * Either src or moduleName must be provided, but not both. */
150
+ moduleName?: string;
151
+ /** Project directory for resolving shader dependencies.
152
+ * Allows the shader to import from npm shader libraries.
153
+ * Optional: defaults to searching upward from cwd for package.json or wesl.toml.
154
+ * Typically use `import.meta.url`. */
155
+ projectDir?: string;
156
+ /** GPU device for running the tests.
157
+ * Typically use `getGPUDevice()` from wgsl-test. */
158
+ device: GPUDevice;
159
+ /** Texture format for the output texture. Default: "rgba32float" */
160
+ textureFormat?: GPUTextureFormat;
161
+ /** Size of the output texture. Default: [1, 1] for simple color tests.
162
+ * Use [2, 2] for derivative tests (forms a complete 2x2 quad for dpdx/dpdy). */
163
+ size?: [width: number, height: number];
164
+ /** Flags for conditional compilation to test shader specialization.
165
+ * Useful for testing `@if` statements in the shader. */
166
+ conditions?: LinkParams["conditions"];
167
+ /** Constants for shader compilation.
168
+ * Injects host-provided values via the `constants::` namespace. */
169
+ constants?: LinkParams["constants"];
170
+ /** Uniform values for the shader (time, mouse).
171
+ * Resolution is auto-populated from the size parameter.
172
+ * Creates test::Uniforms struct available in the shader. */
173
+ uniforms?: RenderUniforms$1;
174
+ /** Input textures for the shader.
175
+ * Bindings: textures at [1..n], samplers at [n+1..n+m].
176
+ * Binding 0 is reserved for uniforms. */
177
+ textures?: GPUTexture[];
178
+ /** Samplers for the input textures.
179
+ * Must be length 1 (reused for all textures) or match textures.length exactly. */
180
+ samplers?: GPUSampler[];
181
+ /** Use source shaders from current package instead of built bundles.
182
+ * Default: true for faster iteration during development. */
183
+ useSourceShaders?: boolean;
184
+ }
185
+ interface FragmentImageTestParams extends Omit<FragmentTestParams, "src" | "moduleName" | "device"> {
186
+ /** Optional snapshot name override. If not provided, derived from shader name. */
187
+ snapshotName?: string;
188
+ }
189
+ /** Run a fragment shader test and validate image snapshot.
190
+ * @param device GPU device for rendering
191
+ * @param moduleName Shader name to load - supports:
192
+ * - Bare name: "blur.wgsl" → resolves to shaders/blur.wgsl
193
+ * - Relative path: "effects/blur.wgsl" → resolves to shaders/effects/blur.wgsl
194
+ * - Module path: "package::effects::blur" → same resolution
195
+ * @param opts Test parameters (size defaults to 256×256 for snapshots)
196
+ */
197
+ declare function expectFragmentImage(device: GPUDevice, moduleName: string, opts?: FragmentImageTestParams): Promise<void>;
198
+ /**
199
+ * Renders a fragment shader and returns pixel (0,0) color values for validation.
200
+ *
201
+ * Useful for simple color tests where you only need to check a single pixel result.
202
+ *
203
+ * @returns Array of color component values from pixel (0,0)
204
+ */
205
+ declare function testFragment(params: FragmentTestParams): Promise<number[]>;
206
+ /**
207
+ * Renders a fragment shader and returns the complete rendered image.
208
+ *
209
+ * Useful for image snapshot testing or when you need to validate the entire output.
210
+ * For snapshot testing shader files, consider using `expectFragmentImage` instead.
211
+ *
212
+ * @returns ImageData containing the full rendered output
213
+ */
214
+ declare function testFragmentImage(params: FragmentTestParams): Promise<ImageData$1>;
215
+ //#endregion
216
+ //#region src/WebGPUTestSetup.d.ts
217
+ /** get or create shared GPU device for testing */
218
+ declare function getGPUDevice(): Promise<GPUDevice>;
219
+ /** destroy globally shared GPU test device */
220
+ declare function destroySharedDevice(): void;
221
+ //#endregion
222
+ //#region src/index.d.ts
223
+ declare module "vitest" {
224
+ interface Matchers<T = any> {
225
+ toMatchImage(nameOrOptions?: string | MatchImageOptions): Promise<void>;
226
+ }
227
+ }
228
+ //#endregion
229
+ export { CompileShaderParams, ComputeTestParams, DeviceCache, FragmentImageTestParams, FragmentTestParams, type ImageData, type RenderUniforms, ResolveContextParams, type SamplerOptions, ShaderContext, type WgslElementType, checkerboardTexture, colorBarsTexture, compileShader, createProjectResolver, createSampler, createUniformsVirtualLib, destroySharedDevice, edgePatternTexture, expectFragmentImage, fullscreenTriangleVertex, getGPUDevice, gradientTexture, lemurImagePath, lemurTexture, noiseTexture, pngToTexture, radialGradientTexture, renderUniformBuffer, resolveShaderContext, runCompute, simpleRender, solidTexture, testCompute, testFragment, testFragmentImage, updateRenderUniforms, withErrorScopes };
230
+ //# sourceMappingURL=index.d.ts.map