diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index bac7c63ca75e..dcebf8fdadc7 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1692,6 +1692,8 @@ "webgpu:shader,execution,shadow:loop:*": { "subcaseMS": 4.901 }, "webgpu:shader,execution,shadow:switch:*": { "subcaseMS": 4.601 }, "webgpu:shader,execution,shadow:while:*": { "subcaseMS": 7.400 }, + "webgpu:shader,execution,stage:basic_compute:*": { "subcaseMS": 1.000 }, + "webgpu:shader,execution,stage:basic_render:*": { "subcaseMS": 1.000 }, "webgpu:shader,execution,statement,increment_decrement:frexp_exp_increment:*": { "subcaseMS": 4.700 }, "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement:*": { "subcaseMS": 20.301 }, "webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement_underflow:*": { "subcaseMS": 4.900 }, diff --git a/src/webgpu/shader/execution/stage.spec.ts b/src/webgpu/shader/execution/stage.spec.ts new file mode 100644 index 000000000000..6e06e67e3776 --- /dev/null +++ b/src/webgpu/shader/execution/stage.spec.ts @@ -0,0 +1,133 @@ +export const description = `Test trivial shaders for each shader stage kind`; + +// There are many many more shaders executed in other tests. + +import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { GPUTest } from '../../gpu_test.js'; +import { checkElementsEqual } from '../../util/check_contents.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('basic_compute') + .desc(`Test a trivial compute shader`) + .fn(async t => { + const code = ` + +@group(0) @binding(0) +var v : vec4u; + +@compute @workgroup_size(1) +fn main() { + v = vec4u(1,2,3,42); +}`; + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: 'main', + }, + }); + + const buffer = t.makeBufferWithContents( + new Uint32Array([0, 0, 0, 0]), + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST + ); + t.trackForCleanup(buffer); + + const bg = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer, + }, + }, + ], + }); + + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bg); + pass.dispatchWorkgroups(1, 1, 1); + pass.end(); + t.queue.submit([encoder.finish()]); + + const bufferReadback = await t.readGPUBufferRangeTyped(buffer, { + srcByteOffset: 0, + type: Uint32Array, + typedLength: 4, + method: 'copy', + }); + const got: Uint32Array = bufferReadback.data; + const expected = new Uint32Array([1, 2, 3, 42]); + + t.expectOK(checkElementsEqual(got, expected)); + }); + +g.test('basic_render') + .desc(`Test trivial vertex and fragment shaders`) + .fn(t => { + const code = ` +@vertex +fn vert_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4f { + // A right triangle covering the whole framebuffer. + const pos = array( + vec2f(-1,-3), + vec2f(3,1), + vec2f(-1,1)); + return vec4f(pos[idx], 0, 1); +} + +@fragment +fn frag_main() -> @location(0) vec4f { + return vec4(0, 1, 0, 1); // green +} +`; + const module = t.device.createShaderModule({ code }); + + const [width, height] = [8, 8] as const; + const format = 'rgba8unorm' as const; + const texture = t.device.createTexture({ + size: { width, height }, + usage: + GPUTextureUsage.RENDER_ATTACHMENT | + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_SRC, + format, + }); + + // We'll copy one pixel only. + const dst = t.device.createBuffer({ + size: 4, + usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + }); + + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vert_main' }, + fragment: { module, entryPoint: 'frag_main', targets: [{ format }] }, + }); + + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [{ view: texture.createView(), loadOp: 'clear', storeOp: 'store' }], + }); + pass.setPipeline(pipeline); + pass.draw(3); + pass.end(); + + encoder.copyTextureToBuffer( + { texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } }, + { buffer: dst, bytesPerRow: 256 }, + { width: 1, height: 1, depthOrArrayLayers: 1 } + ); + t.queue.submit([encoder.finish()]); + + // Expect one green pixel. + t.expectGPUBufferValuesEqual(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff])); + });