Skip to content

Commit 9ed5e4f

Browse files
committed
add tests
1 parent 5383df1 commit 9ed5e4f

File tree

3 files changed

+211
-11
lines changed

3 files changed

+211
-11
lines changed

test/functional-tests.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import { create, globals } from '../index.js';
2-
3-
Object.assign(globalThis, globals);
4-
globalThis.navigator = { gpu: create([]) };
1+
import { isWin } from '../build/constants.js';
52

63
Promise.withResolvers = Promise.withResolvers ?? function() {
74
const o = {};
@@ -11,5 +8,7 @@ Promise.withResolvers = Promise.withResolvers ?? function() {
118
return o;
129
};
1310

14-
await import('./tests/basic-tests.js');
15-
//await import('./tests/reference-count-tests.js');
11+
if (isWin || isMac) {
12+
await import('./tests/basic-tests.js');
13+
//await import('./tests/reference-count-tests.js');
14+
}

test/tests/basic-tests.js

Lines changed: 194 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,202 @@
1-
import { describe, it } from 'node:test';
1+
import { describe, it, before, after } from 'node:test';
22
import { strict as assert } from 'node:assert';
3+
import { getGPU } from '../webgpu.js';
34

45
await describe('basic tests', async () => {
5-
await it('creates a device', async () => {
6-
const device = await(await navigator.gpu.requestAdapter()).requestDevice();
6+
let device;
7+
let navigator;
8+
let adapter
9+
let globals;
10+
11+
before(async () => {
12+
const { gpu, globals: g } = getGPU();
13+
globals = g;
14+
navigator = { gpu };
15+
adapter = await navigator.gpu.requestAdapter();
16+
assert(adapter, 'got adapter');
17+
18+
device = await adapter.requestDevice({label: "test-device"});
19+
});
20+
21+
after(async () => {
22+
device.destroy();
23+
device = undefined;
24+
navigator = undefined;
25+
adapter = undefined;
26+
27+
await new Promise(r => setTimeout(r, 1000));
28+
})
29+
30+
function withErrorScope(fn) {
31+
return async () => {
32+
device.pushErrorScope('validation');
33+
await fn();
34+
const error = await device.popErrorScope();
35+
assert(!error, `device error: ${error?.message || error}`);
36+
};
37+
}
38+
39+
await it('creates a device', withErrorScope(async () => {
40+
console.log('adapter.info.description:', adapter.info.description);
41+
console.log('adapter.info.vendor:', adapter.info.vendor);
42+
console.log('adapter.info.architecture:', adapter.info.architecture);
743
assert(!!device, 'got device');
844
assert(!!device.limits, 'have limits');
945
assert(device.limits.maxBindGroups > 0, 'have maxBindGroups');
10-
device.destroy();
11-
});
46+
}));
47+
48+
await it('computes', withErrorScope(async () => {
49+
const { GPUBufferUsage, GPUMapMode } = globals;
50+
const module = device.createShaderModule({
51+
label: 'doubling compute module',
52+
code: `
53+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
54+
55+
@compute @workgroup_size(1) fn computeSomething(
56+
@builtin(global_invocation_id) id: vec3u
57+
) {
58+
let i = id.x;
59+
data[i] = data[i] * 2.0;
60+
}
61+
`,
62+
});
63+
64+
const pipeline = device.createComputePipeline({
65+
label: 'doubling compute pipeline',
66+
layout: 'auto',
67+
compute: {
68+
module,
69+
},
70+
});
71+
72+
const input = new Float32Array([1, 3, 5]);
73+
const workBuffer = device.createBuffer({
74+
label: 'work buffer',
75+
size: input.byteLength,
76+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
77+
});
78+
device.queue.writeBuffer(workBuffer, 0, input);
79+
80+
const resultBuffer = device.createBuffer({
81+
label: 'result buffer',
82+
size: input.byteLength,
83+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
84+
});
85+
86+
const bindGroup = device.createBindGroup({
87+
label: 'bindGroup for work buffer',
88+
layout: pipeline.getBindGroupLayout(0),
89+
entries: [
90+
{ binding: 0, resource: { buffer: workBuffer } },
91+
],
92+
});
93+
94+
const encoder = device.createCommandEncoder({
95+
label: 'doubling encoder',
96+
});
97+
const pass = encoder.beginComputePass({
98+
label: 'doubling compute pass',
99+
});
100+
pass.setPipeline(pipeline);
101+
pass.setBindGroup(0, bindGroup);
102+
pass.dispatchWorkgroups(input.length);
103+
pass.end();
104+
105+
encoder.copyBufferToBuffer(workBuffer, 0, resultBuffer, 0, resultBuffer.size);
106+
107+
const commandBuffer = encoder.finish();
108+
device.queue.submit([commandBuffer]);
109+
110+
await resultBuffer.mapAsync(GPUMapMode.READ);
111+
const result = new Float32Array(resultBuffer.getMappedRange().slice());
112+
resultBuffer.unmap();
113+
114+
assert.deepEqual(result, input.map(x => x * 2), 'correct result');
115+
}));
116+
117+
await it('renders', withErrorScope(async () => {
118+
const { GPUTextureUsage, GPUBufferUsage, GPUMapMode } = globals;
119+
const format = 'r8unorm';
120+
const module = device.createShaderModule({
121+
label: 'our hardcoded red triangle shaders',
122+
code: `
123+
@vertex fn vs(
124+
@builtin(vertex_index) vertexIndex : u32
125+
) -> @builtin(position) vec4f {
126+
let pos = array(
127+
vec2f(-1.1, -1.0),
128+
vec2f(-1.1, 1.1),
129+
vec2f( 1.0, 1.1),
130+
);
131+
132+
return vec4f(pos[vertexIndex], 0.0, 1.0);
133+
}
134+
135+
@fragment fn fs() -> @location(0) vec4f {
136+
return vec4f(1, 0, 0, 1);
137+
}
138+
`,
139+
});
140+
141+
const pipeline = device.createRenderPipeline({
142+
label: 'our hardcoded red triangle pipeline',
143+
layout: 'auto',
144+
vertex: {
145+
module,
146+
},
147+
fragment: {
148+
module,
149+
targets: [{ format }],
150+
},
151+
});
152+
153+
const texture = device.createTexture({
154+
size: [4, 4],
155+
format,
156+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
157+
});
158+
159+
const renderPassDescriptor = {
160+
label: 'our basic canvas renderPass',
161+
colorAttachments: [
162+
{
163+
view: texture,
164+
loadOp: 'clear',
165+
storeOp: 'store',
166+
},
167+
],
168+
};
169+
170+
const buffer = device.createBuffer({
171+
label: 'readback buffer',
172+
size: 256 * 4,
173+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
174+
})
175+
176+
const encoder = device.createCommandEncoder({ label: 'our encoder' });
177+
const pass = encoder.beginRenderPass(renderPassDescriptor);
178+
pass.setPipeline(pipeline);
179+
pass.draw(3); // call our vertex shader 3 times.
180+
pass.end();
181+
encoder.copyTextureToBuffer(
182+
{ texture },
183+
{ buffer, bytesPerRow: 256 },
184+
[4, 4],
185+
);
186+
const commandBuffer = encoder.finish();
187+
device.queue.submit([commandBuffer]);
188+
189+
await buffer.mapAsync(GPUMapMode.READ);
190+
const data = new Uint8Array(buffer.getMappedRange()).slice();
191+
buffer.unmap();
192+
193+
const expected = new Uint8Array(256 * 4);
194+
expected.set([255, 255, 255, 0], 0);
195+
expected.set([255, 255, 0, 0], 256);
196+
expected.set([255, 0, 0, 0], 512);
197+
expected.set([ 0, 0, 0, 0], 768);
198+
assert.deepEqual(data, expected, 'correct result');
199+
}));
200+
12201
});
13202

test/webgpu.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { create, globals } from '../index.js';
2+
import { isWin } from '../build/constants.js';
3+
import { addElemIf } from '../build/utils.js';
4+
5+
export function getGPU() {
6+
const options = [];
7+
if (!!process.env.WEBGPU_USE_CI_AVAILABLE_RENDERER) {
8+
options.push(...addElemIf(isWin, 'adapter=Microsoft'));
9+
}
10+
return { gpu: create(options), globals };
11+
}
12+

0 commit comments

Comments
 (0)