From 0d1754f2f91ea5535f93b9fd46b91b494fa0032d Mon Sep 17 00:00:00 2001 From: alan-baker Date: Mon, 29 Jan 2024 10:37:26 -0500 Subject: [PATCH] Shader execution tests for memory layout (#3322) * Tests to read and write correct bytes for many layouts * add to listing meta --- src/webgpu/listing_meta.json | 2 + .../shader/execution/memory_layout.spec.ts | 1059 +++++++++++++++++ 2 files changed, 1061 insertions(+) create mode 100644 src/webgpu/shader/execution/memory_layout.spec.ts diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 3eff54ce5057..479c11ffb3e4 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1647,6 +1647,8 @@ "webgpu:shader,execution,flow_control,while:while_continue:*": { "subcaseMS": 5.650 }, "webgpu:shader,execution,flow_control,while:while_nested_break:*": { "subcaseMS": 12.701 }, "webgpu:shader,execution,flow_control,while:while_nested_continue:*": { "subcaseMS": 5.450 }, + "webgpu:shader,execution,memory_layout:read_layout:*": { "subcaseMS": 0 }, + "webgpu:shader,execution,memory_layout:write_layout:*": { "subcaseMS": 0 }, "webgpu:shader,execution,memory_model,adjacent:f16:*": { "subcaseMS": 23.625 }, "webgpu:shader,execution,memory_model,atomicity:atomicity:*": { "subcaseMS": 77.201 }, "webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*": { "subcaseMS": 65.850 }, diff --git a/src/webgpu/shader/execution/memory_layout.spec.ts b/src/webgpu/shader/execution/memory_layout.spec.ts new file mode 100644 index 000000000000..a58a31449ffa --- /dev/null +++ b/src/webgpu/shader/execution/memory_layout.spec.ts @@ -0,0 +1,1059 @@ +export const description = `Test memory layout requirements`; + +import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { keysOf } from '../../../common/util/data_tables.js'; +import { iterRange } from '../../../common/util/util.js'; +import { GPUTest } from '../../gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +interface LayoutCase { + type: string; + decl?: string; + read_assign: string; + write_assign: string; + offset: number; + f16?: boolean; + f32?: boolean; + skip_uniform?: boolean; +} + +const kLayoutCases: Record = { + vec2u_align8: { + type: `S_vec2u_align`, + decl: `struct S_vec2u_align { + x : u32, + y : vec2u, + }`, + read_assign: `out = in.y[1]`, + write_assign: `out.y[1] = in`, + offset: 12, + }, + vec3u_align16: { + type: `S_vec3u_align`, + decl: `struct S_vec3u_align { + x : u32, + y : vec3u, + }`, + read_assign: `out = in.y[2]`, + write_assign: `out.y[2] = in`, + offset: 24, + }, + vec4u_align16: { + type: `S_vec4u_align`, + decl: `struct S_vec4u_align { + x : u32, + y : vec4u, + }`, + read_assign: `out = in.y[0]`, + write_assign: `out.y[0] = in`, + offset: 16, + }, + struct_align32: { + type: `S_align32`, + decl: `struct S_align32 { + x : u32, + @align(32) y : u32, + }`, + read_assign: `out = in.y;`, + write_assign: `out.y = in`, + offset: 32, + }, + vec2h_align4: { + type: `S_vec2h_align`, + decl: `struct S_vec2h_align { + x : f16, + y : vec2h, + }`, + read_assign: `out = u32(in.y[0])`, + write_assign: `out.y[0] = f16(in)`, + offset: 4, + f16: true, + }, + vec3h_align8: { + type: `S_vec3h_align`, + decl: `struct S_vec3h_align { + x : f16, + y : vec3h, + }`, + read_assign: `out = u32(in.y[2])`, + write_assign: `out.y[2] = f16(in)`, + offset: 12, + f16: true, + }, + vec4h_align8: { + type: `S_vec4h_align`, + decl: `struct S_vec4h_align { + x : f16, + y : vec4h, + }`, + read_assign: `out = u32(in.y[2])`, + write_assign: `out.y[2] = f16(in)`, + offset: 12, + f16: true, + }, + vec2f_align8: { + type: `S_vec2f_align`, + decl: `struct S_vec2f_align { + x : u32, + y : vec2f, + }`, + read_assign: `out = u32(in.y[1])`, + write_assign: `out.y[1] = f32(in)`, + offset: 12, + f32: true, + }, + vec3f_align16: { + type: `S_vec3f_align`, + decl: `struct S_vec3f_align { + x : u32, + y : vec3f, + }`, + read_assign: `out = u32(in.y[2])`, + write_assign: `out.y[2] = f32(in)`, + offset: 24, + f32: true, + }, + vec4f_align16: { + type: `S_vec4f_align`, + decl: `struct S_vec4f_align { + x : u32, + y : vec4f, + }`, + read_assign: `out = u32(in.y[0])`, + write_assign: `out.y[0] = f32(in)`, + offset: 16, + f32: true, + }, + vec3i_size12: { + type: `S_vec3i_size`, + decl: `struct S_vec3i_size { + x : vec3i, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 12, + }, + vec3h_size6: { + type: `S_vec3h_size`, + decl: `struct S_vec3h_size { + x : vec3h, + y : f16, + z : f16, + }`, + read_assign: `out = u32(in.z)`, + write_assign: `out.z = f16(in)`, + offset: 8, + f16: true, + }, + size80: { + type: `S_size80`, + decl: `struct S_size80 { + @size(80) x : u32, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 80, + }, + atomic_align4: { + type: `S_atomic_align`, + decl: `struct S_atomic_align { + x : u32, + y : atomic, + }`, + read_assign: `out = atomicLoad(&in.y)`, + write_assign: `atomicStore(&out.y, in)`, + offset: 4, + }, + atomic_size4: { + type: `S_atomic_size`, + decl: `struct S_atomic_size { + x : atomic, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 4, + }, + mat2x2f_align8: { + type: `S_mat2x2f_align`, + decl: `struct S_mat2x2f_align { + x : u32, + y : mat2x2f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 8, + f32: true, + }, + mat3x2f_align8: { + type: `S_mat3x2f_align`, + decl: `struct S_mat3x2f_align { + x : u32, + y : mat3x2f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 8, + f32: true, + }, + mat4x2f_align8: { + type: `S_mat4x2f_align`, + decl: `struct S_mat4x2f_align { + x : u32, + y : mat4x2f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 8, + f32: true, + }, + mat2x3f_align16: { + type: `S_mat2x3f_align`, + decl: `struct S_mat2x3f_align { + x : u32, + y : mat2x3f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat3x3f_align16: { + type: `S_mat3x3f_align`, + decl: `struct S_mat3x3f_align { + x : u32, + y : mat3x3f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat4x3f_align16: { + type: `S_mat4x3f_align`, + decl: `struct S_mat4x3f_align { + x : u32, + y : mat4x3f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat2x4f_align16: { + type: `S_mat2x4f_align`, + decl: `struct S_mat2x4f_align { + x : u32, + y : mat2x4f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat3x4f_align16: { + type: `S_mat3x4f_align`, + decl: `struct S_mat3x4f_align { + x : u32, + y : mat3x4f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat4x4f_align16: { + type: `S_mat4x4f_align`, + decl: `struct S_mat4x4f_align { + x : u32, + y : mat4x4f, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f32(in)`, + offset: 16, + f32: true, + }, + mat2x2h_align4: { + type: `S_mat2x2h_align`, + decl: `struct S_mat2x2h_align { + x : u32, + y : mat2x2h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 4, + f16: true, + }, + mat3x2h_align4: { + type: `S_mat3x2h_align`, + decl: `struct S_mat3x2h_align { + x : u32, + y : mat3x2h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 4, + f16: true, + }, + mat4x2h_align4: { + type: `S_mat4x2h_align`, + decl: `struct S_mat4x2h_align { + x : u32, + y : mat4x2h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 4, + f16: true, + }, + mat2x3h_align8: { + type: `S_mat2x3h_align`, + decl: `struct S_mat2x3h_align { + x : u32, + y : mat2x3h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat3x3h_align8: { + type: `S_mat3x3h_align`, + decl: `struct S_mat3x3h_align { + x : u32, + y : mat2x3h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat4x3h_align8: { + type: `S_mat4x3h_align`, + decl: `struct S_mat4x3h_align { + x : u32, + y : mat4x3h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat2x4h_align8: { + type: `S_mat2x4h_align`, + decl: `struct S_mat2x4h_align { + x : u32, + y : mat2x4h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat3x4h_align8: { + type: `S_mat3x4h_align`, + decl: `struct S_mat3x4h_align { + x : u32, + y : mat3x4h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat4x4h_align8: { + type: `S_mat4x4h_align`, + decl: `struct S_mat4x4h_align { + x : u32, + y : mat4x4h, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + }, + mat2x2f_size: { + type: `S_mat2x2f_size`, + decl: `struct S_mat2x2f_size { + x : mat2x2f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 16, + }, + mat3x2f_size: { + type: `S_mat3x2f_size`, + decl: `struct S_mat3x2f_size { + x : mat3x2f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 24, + }, + mat4x2f_size: { + type: `S_mat4x2f_size`, + decl: `struct S_mat4x2f_size { + x : mat4x2f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 32, + }, + mat2x3f_size: { + type: `S_mat2x3f_size`, + decl: `struct S_mat2x3f_size { + x : mat2x3f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 32, + }, + mat3x3f_size: { + type: `S_mat3x3f_size`, + decl: `struct S_mat3x3f_size { + x : mat3x3f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 48, + }, + mat4x3f_size: { + type: `S_mat4x3f_size`, + decl: `struct S_mat4x3f_size { + x : mat4x3f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 64, + }, + mat2x4f_size: { + type: `S_mat2x4f_size`, + decl: `struct S_mat2x4f_size { + x : mat2x4f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 32, + }, + mat3x4f_size: { + type: `S_mat3x4f_size`, + decl: `struct S_mat3x4f_size { + x : mat3x4f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 48, + }, + mat4x4f_size: { + type: `S_mat4x4f_size`, + decl: `struct S_mat4x4f_size { + x : mat4x4f, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 64, + }, + mat2x2h_size: { + type: `S_mat2x2h_size`, + decl: `struct S_mat2x2h_size { + x : mat2x2h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 8, + f16: true, + }, + mat3x2h_size: { + type: `S_mat3x2h_size`, + decl: `struct S_mat3x2h_size { + x : mat3x2h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 12, + f16: true, + }, + mat4x2h_size: { + type: `S_mat4x2h_size`, + decl: `struct S_mat4x2h_size { + x : mat4x2h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 16, + f16: true, + }, + mat2x3h_size: { + type: `S_mat2x3h_size`, + decl: `struct S_mat2x3h_size { + x : mat2x3h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 16, + f16: true, + }, + mat3x3h_size: { + type: `S_mat3x3h_size`, + decl: `struct S_mat3x3h_size { + x : mat3x3h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 24, + f16: true, + }, + mat4x3h_size: { + type: `S_mat4x3h_size`, + decl: `struct S_mat4x3h_size { + x : mat4x3h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 32, + f16: true, + }, + mat2x4h_size: { + type: `S_mat2x4h_size`, + decl: `struct S_mat2x4h_size { + x : mat2x4h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 16, + f16: true, + }, + mat3x4h_size: { + type: `S_mat3x4h_size`, + decl: `struct S_mat3x4h_size { + x : mat3x4h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 24, + f16: true, + }, + mat4x4h_size: { + type: `S_mat4x4h_size`, + decl: `struct S_mat4x4h_size { + x : mat4x4h, + y : f16, + }`, + read_assign: `out = u32(in.y)`, + write_assign: `out.y = f16(in)`, + offset: 32, + f16: true, + }, + struct_align_vec2i: { + type: `S_struct_align_vec2i`, + decl: `struct Inner { + x : u32, + y : vec2i, + } + struct S_struct_align_vec2i { + x : u32, + y : Inner, + }`, + read_assign: `out = in.y.x`, + write_assign: `out.y.x = in`, + offset: 8, + skip_uniform: true, + }, + struct_align_vec3i: { + type: `S_struct_align_vec3i`, + decl: `struct Inner { + x : u32, + y : vec3i, + } + struct S_struct_align_vec3i { + x : u32, + y : Inner, + }`, + read_assign: `out = in.y.x`, + write_assign: `out.y.x = in`, + offset: 16, + }, + struct_align_vec4i: { + type: `S_struct_align_vec4i`, + decl: `struct Inner { + x : u32, + y : vec4i, + } + struct S_struct_align_vec4i { + x : u32, + y : Inner, + }`, + read_assign: `out = in.y.x`, + write_assign: `out.y.x = in`, + offset: 16, + }, + struct_align_vec2h: { + type: `S_struct_align_vec2h`, + decl: `struct Inner { + x : f16, + y : vec2h, + } + struct S_struct_align_vec2h { + x : f16, + y : Inner, + }`, + read_assign: `out = u32(in.y.x)`, + write_assign: `out.y.x = f16(in)`, + offset: 4, + f16: true, + skip_uniform: true, + }, + struct_align_vec3h: { + type: `S_struct_align_vec3h`, + decl: `struct Inner { + x : f16, + y : vec3h, + } + struct S_struct_align_vec3h { + x : f16, + y : Inner, + }`, + read_assign: `out = u32(in.y.x)`, + write_assign: `out.y.x = f16(in)`, + offset: 8, + f16: true, + skip_uniform: true, + }, + struct_align_vec4h: { + type: `S_struct_align_vec4h`, + decl: `struct Inner { + x : f16, + y : vec4h, + } + struct S_struct_align_vec4h { + x : f16, + y : Inner, + }`, + read_assign: `out = u32(in.y.x)`, + write_assign: `out.y.x = f16(in)`, + offset: 8, + f16: true, + skip_uniform: true, + }, + struct_size_roundup: { + type: `S_struct_size_roundup`, + decl: `struct Inner { + x : vec3u, + } + struct S_struct_size_roundup { + x : Inner, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 16, + }, + struct_inner_size: { + type: `S_struct_inner_size`, + decl: `struct Inner { + @size(112) x : u32, + } + struct S_struct_inner_size { + x : Inner, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 112, + }, + struct_inner_align: { + type: `S_struct_inner_align`, + decl: `struct Inner { + @align(64) x : u32, + } + struct S_struct_inner_align { + x : u32, + y : Inner, + }`, + read_assign: `out = in.y.x`, + write_assign: `out.y.x = in`, + offset: 64, + }, + struct_inner_size_and_align: { + type: `S_struct_inner_size_and_align`, + decl: `struct Inner { + @align(32) @size(33) x : u32, + } + struct S_struct_inner_size_and_align { + x : Inner, + y : Inner, + }`, + read_assign: `out = in.y.x`, + write_assign: `out.y.x = in`, + offset: 64, + }, + struct_override_size: { + type: `S_struct_override_size`, + decl: `struct Inner { + @size(32) x : u32, + } + struct S_struct_override_size { + @size(64) x : Inner, + y : u32, + }`, + read_assign: `out = in.y`, + write_assign: `out.y = in`, + offset: 64, + }, + struct_double_align: { + type: `S_struct_double_align`, + decl: `struct Inner { + x : u32, + @align(32) y : u32, + } + struct S_struct_double_align { + x : u32, + @align(64) y : Inner, + }`, + read_assign: `out = in.y.y`, + write_assign: `out.y.y = in`, + offset: 96, + }, + array_vec3u_align: { + type: `S_array_vec3u_align`, + decl: `struct S_array_vec3u_align { + x : u32, + y : array, + }`, + read_assign: `out = in.y[0][0]`, + write_assign: `out.y[0][0] = in`, + offset: 16, + }, + array_vec3h_align: { + type: `S_array_vec3h_align`, + decl: `struct S_array_vec3h_align { + x : f16, + y : array, + }`, + read_assign: `out = u32(in.y[0][0])`, + write_assign: `out.y[0][0] = f16(in)`, + offset: 8, + f16: true, + skip_uniform: true, + }, + array_vec3u_stride: { + type: `S_array_vec3u_stride`, + decl: `struct S_array_vec3u_stride { + x : array, + }`, + read_assign: `out = in.x[1][0]`, + write_assign: `out.x[1][0] = in`, + offset: 16, + }, + array_vec3h_stride: { + type: `S_array_vec3h_stride`, + decl: `struct S_array_vec3h_stride { + x : array, + }`, + read_assign: `out = u32(in.x[1][0])`, + write_assign: `out.x[1][0] = f16(in)`, + offset: 8, + f16: true, + skip_uniform: true, + }, + array_stride_size: { + type: `array`, + decl: `struct S_stride { + @size(16) x : u32, + }`, + read_assign: `out = in[2].x`, + write_assign: `out[2].x = in`, + offset: 32, + }, +}; + +g.test('read_layout') + .desc('Test reading memory layouts') + .params(u => + u + .combine('case', keysOf(kLayoutCases)) + .combine('aspace', ['storage', 'uniform', 'workgroup', 'function', 'private'] as const) + .beginSubcases() + ) + .beforeAllSubcases(t => { + const testcase = kLayoutCases[t.params.case]; + if (testcase.f16) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + // Don't test atomics in workgroup due to initialization boilerplate. + t.skipIf( + testcase.type.includes('atomic') && t.params.aspace !== 'storage', + `Skipping atomic test for non-storage address space` + ); + + t.skipIf( + testcase.skip_uniform === true && t.params.aspace === 'uniform', + `Uniform requires 16 byte alignment` + ); + }) + .fn(t => { + const testcase = kLayoutCases[t.params.case]; + let code = ` +${testcase.f16 ? 'enable f16;' : ''} +${testcase.decl} + +@group(0) @binding(1) +var out : u32; +`; + + if (t.params.aspace === 'uniform') { + code += `@group(0) @binding(0) + var<${t.params.aspace}> in : ${testcase.type};`; + } else if (t.params.aspace === 'storage') { + // Use read_write for input data to support atomics. + code += `@group(0) @binding(0) + var<${t.params.aspace}, read_write> in : ${testcase.type};`; + } else { + code += `@group(0) @binding(0) + var pre_in : ${testcase.type};`; + if (t.params.aspace === 'workgroup') { + code += ` + var in : ${testcase.type};`; + } else if (t.params.aspace === 'private') { + code += ` + var in : ${testcase.type};`; + } + } + + code += ` +@compute @workgroup_size(1,1,1) +fn main() { +`; + + if ( + t.params.aspace === 'workgroup' || + t.params.aspace === 'function' || + t.params.aspace === 'private' + ) { + if (t.params.aspace === 'function') { + code += `var in : ${testcase.type};\n`; + } + code += `in = pre_in;`; + if (t.params.aspace === 'workgroup') { + code += `workgroupBarrier();\n`; + } + } + + code += `\n${testcase.read_assign};\n}`; + + let usage = GPUBufferUsage.COPY_SRC; + if (t.params.aspace === 'uniform') { + usage |= GPUBufferUsage.UNIFORM; + } else { + usage |= GPUBufferUsage.STORAGE; + } + + // Magic number is 42 in various representations. + const inMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42; + const in_buffer = t.makeBufferWithContents( + new Uint32Array([ + ...iterRange(128, x => { + if (x * 4 === testcase.offset) { + return inMagicNumber; + } else { + return 0; + } + }), + ]), + usage + ); + t.trackForCleanup(in_buffer); + + const out_buffer = t.makeBufferWithContents( + new Uint32Array([...iterRange(1, x => 0)]), + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST + ); + t.trackForCleanup(out_buffer); + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: 'main', + }, + }); + + const bg = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: in_buffer, + }, + }, + { + binding: 1, + resource: { + buffer: out_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()]); + + t.expectGPUBufferValuesEqual(out_buffer, new Uint32Array([42])); + }); + +g.test('write_layout') + .desc('Test writing memory layouts') + .params(u => + u + .combine('case', keysOf(kLayoutCases)) + .combine('aspace', ['storage', 'workgroup', 'function', 'private'] as const) + .beginSubcases() + ) + .beforeAllSubcases(t => { + const testcase = kLayoutCases[t.params.case]; + if (testcase.f16) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + // Don't test atomics in workgroup due to initialization boilerplate. + t.skipIf( + testcase.type.includes('atomic') && t.params.aspace !== 'storage', + `Skipping atomic test for non-storage address space` + ); + }) + .fn(t => { + const testcase = kLayoutCases[t.params.case]; + let code = ` +${testcase.f16 ? 'enable f16;' : ''} +${testcase.decl} + +@group(0) @binding(0) +var in : u32; +`; + + if (t.params.aspace === 'storage') { + code += `@group(0) @binding(1) + var out : ${testcase.type};\n`; + } else { + code += `@group(0) @binding(1) + var post_out : ${testcase.type};\n`; + + if (t.params.aspace === 'workgroup') { + code += `var out : ${testcase.type};\n`; + } else if (t.params.aspace === 'private') { + code += `var out : ${testcase.type};\n`; + } + } + + code += ` +@compute @workgroup_size(1,1,1) +fn main() { +`; + + if (t.params.aspace === 'function') { + code += `var out : ${testcase.type};\n`; + } + + code += `${testcase.write_assign};\n`; + if ( + t.params.aspace === 'workgroup' || + t.params.aspace === 'function' || + t.params.aspace === 'private' + ) { + if (t.params.aspace === 'workgroup') { + code += `workgroupBarrier();\n`; + } + code += `post_out = out;`; + } + + code += `\n}`; + + const in_buffer = t.makeBufferWithContents( + new Uint32Array([42]), + GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE + ); + t.trackForCleanup(in_buffer); + + const out_buffer = t.makeBufferWithContents( + new Uint32Array([...iterRange(128, x => 0)]), + GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST + ); + t.trackForCleanup(out_buffer); + + const pipeline = t.device.createComputePipeline({ + layout: 'auto', + compute: { + module: t.device.createShaderModule({ + code, + }), + entryPoint: 'main', + }, + }); + + const bg = t.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: { + buffer: in_buffer, + }, + }, + { + binding: 1, + resource: { + buffer: out_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()]); + + // Magic number is 42 in various representations. + const outMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42; + const expect = new Uint32Array([ + ...iterRange(128, x => { + if (x * 4 === testcase.offset) { + return outMagicNumber; + } else { + return 0; + } + }), + ]); + + t.expectGPUBufferValuesEqual(out_buffer, expect); + });