export const description = ` Execution Tests for preservation of padding bytes in structures and arrays. `; import { makeTestGroup } from '../../../common/framework/test_group.js'; import { iterRange } from '../../../common/util/util.js'; import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../gpu_test.js'; export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest); /** * Run a shader and check that the buffer output matches expectations. * * @param t The test object * @param wgsl The shader source * @param expected The array of expected values after running the shader */ function runShaderTest(t: GPUTest, wgsl: string, expected: Uint32Array): void { const pipeline = t.device.createComputePipeline({ layout: 'auto', compute: { module: t.device.createShaderModule({ code: wgsl }), entryPoint: 'main', }, }); // Allocate a buffer and fill it with 0xdeadbeef words. const outputBuffer = t.makeBufferWithContents( new Uint32Array([...iterRange(expected.length, _i => 0xdeadbeef)]), GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC ); const bindGroup = t.device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: outputBuffer } }], }); // Run the shader. const encoder = t.device.createCommandEncoder(); const pass = encoder.beginComputePass(); pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.dispatchWorkgroups(1); pass.end(); t.queue.submit([encoder.finish()]); // Check that only the non-padding bytes were modified. t.expectGPUBufferValuesEqual(outputBuffer, expected); } g.test('struct_implicit') .desc( `Test that padding bytes in between structure members are preserved. This test defines a structure that has implicit padding and creates a read-write storage buffer with that structure type. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { const wgsl = ` struct S { a : u32, // 12 bytes of padding b : vec3, // 4 bytes of padding c : vec2, // 8 bytes of padding } @group(0) @binding(0) var buffer : S; @compute @workgroup_size(1) fn main() { buffer = S(0x12345678, vec3(0xabcdef01), vec2(0x98765432)); } `; runShaderTest( t, wgsl, new Uint32Array([ // a : u32 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // b : vec3 0xabcdef01, 0xabcdef01, 0xabcdef01, 0xdeadbeef, // c : vec2 0x98765432, 0x98765432, 0xdeadbeef, 0xdeadbeef, ]) ); }); g.test('struct_explicit') .desc( `Test that padding bytes in between structure members are preserved. This test defines a structure with explicit padding attributes and creates a read-write storage buffer with that structure type. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { const wgsl = ` struct S { a : u32, // 12 bytes of padding @align(16) @size(20) b : u32, // 16 bytes of padding @size(12) c : u32, // 8 bytes of padding } @group(0) @binding(0) var buffer : S; @compute @workgroup_size(1) fn main() { buffer = S(0x12345678, 0xabcdef01, 0x98765432); } `; runShaderTest( t, wgsl, new Uint32Array([ // a : u32 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // @align(16) @size(20) b : u32 0xabcdef01, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // @size(12) c : u32 0x98765432, 0xdeadbeef, 0xdeadbeef, ]) ); }); g.test('struct_nested') .desc( `Test that padding bytes in nested structures are preserved. This test defines a set of nested structures that have padding and creates a read-write storage buffer with the root structure type. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { const wgsl = ` // Size of S1 is 48 bytes. // Alignment of S1 is 16 bytes. struct S1 { a : u32, // 12 bytes of padding b : vec3, // 4 bytes of padding c : vec2, // 8 bytes of padding } // Size of S2 is 112 bytes. // Alignment of S2 is 48 bytes. struct S2 { a2 : u32, // 12 bytes of padding b2 : S1, c2 : S1, } // Size of S3 is 144 bytes. // Alignment of S3 is 48 bytes. struct S3 { a3 : S1, b3 : S2, c3 : S2, } @group(0) @binding(0) var buffer : S3; @compute @workgroup_size(1) fn main() { buffer = S3(); } `; runShaderTest( t, wgsl, new Uint32Array([ // a3 : S1 // a3.a1 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // a3.b1 : vec3 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef, // a3.c1 : vec2 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef, // b3 : S2 // b3.a2 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // b3.b2 : S1 // b3.b2.a1 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // b3.b2.b1 : vec3 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef, // b3.b2.c1 : vec2 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef, // b3.c2 : S1 // b3.c2.a1 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // b3.c2.b1 : vec3 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef, // b3.c2.c1 : vec2 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef, // c3 : S2 // c3.a2 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // c3.b2 : S1 // c3.b2.a1 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // c3.b2.b1 : vec3 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef, // c3.b2.c1 : vec2 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef, // c3.c2 : S1 // c3.c2.a1 : u32 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, // c3.c2.b1 : vec3 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef, // c3.c2.c1 : vec2 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef, ]) ); }); g.test('array_of_vec3') .desc( `Test that padding bytes in between array elements are preserved. This test defines creates a read-write storage buffer with type array. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { const wgsl = ` @group(0) @binding(0) var buffer : array, 4>; @compute @workgroup_size(1) fn main() { buffer = array, 4>( vec3(0x12345678), vec3(0xabcdef01), vec3(0x98765432), vec3(0x0f0f0f0f), ); } `; runShaderTest( t, wgsl, new Uint32Array([ // buffer[0] 0x12345678, 0x12345678, 0x12345678, 0xdeadbeef, // buffer[1] 0xabcdef01, 0xabcdef01, 0xabcdef01, 0xdeadbeef, // buffer[2] 0x98765432, 0x98765432, 0x98765432, 0xdeadbeef, // buffer[2] 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0xdeadbeef, ]) ); }); g.test('array_of_vec3h') .desc( `Test that padding bytes in between array elements are preserved when f16 elements are used. This test defines creates a read-write storage buffer with type array. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { t.skipIfDeviceDoesNotHaveFeature('shader-f16'); const wgsl = ` enable f16; @group(0) @binding(0) var buffer : array, 4>; @compute @workgroup_size(1) fn main() { buffer = array, 4>( vec3(1h), vec3(2h), vec3(3h), vec3(4h), ); } `; runShaderTest( t, wgsl, new Uint32Array([ // buffer[0] 0x3c003c00, 0xdead3c00, // buffer[1] 0x40004000, 0xdead4000, // buffer[2] 0x42004200, 0xdead4200, // buffer[2] 0x44004400, 0xdead4400, ]) ); }); g.test('array_of_vec3h,elementwise') .desc( `Test that padding bytes in between array elements are preserved when f16 elements are used. This test defines creates a read-write storage buffer with type array. The shader assigns one element per thread, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { t.skipIfDeviceDoesNotHaveFeature('shader-f16'); const wgsl = ` enable f16; @group(0) @binding(0) var buffer : array>; @compute @workgroup_size(4) fn main(@builtin(local_invocation_index) lid : u32) { buffer[lid] = vec3h(f16(lid + 1)); } `; runShaderTest( t, wgsl, new Uint32Array([ // buffer[0] 0x3c003c00, 0xdead3c00, // buffer[1] 0x40004000, 0xdead4000, // buffer[2] 0x42004200, 0xdead4200, // buffer[2] 0x44004400, 0xdead4400, ]) ); }); g.test('array_of_struct') .desc( `Test that padding bytes in between array elements are preserved. This test defines creates a read-write storage buffer with type array, where S is a structure that contains padding bytes. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .fn(t => { const wgsl = ` struct S { a : u32, b : vec3, } @group(0) @binding(0) var buffer : array; @compute @workgroup_size(1) fn main() { buffer = array( S(0x12345678, vec3(0x0f0f0f0f)), S(0xabcdef01, vec3(0x7c7c7c7c)), S(0x98765432, vec3(0x18181818)), ); } `; runShaderTest( t, wgsl, new Uint32Array([ // buffer[0] 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0xdeadbeef, // buffer[1] 0xabcdef01, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x7c7c7c7c, 0x7c7c7c7c, 0x7c7c7c7c, 0xdeadbeef, // buffer[2] 0x98765432, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x18181818, 0x18181818, 0x18181818, 0xdeadbeef, ]) ); }); g.test('vec3') .desc( `Test padding bytes are preserved when assigning to a variable of type vec3 (without a struct). ` ) .fn(t => { const wgsl = ` @group(0) @binding(0) var buffer : vec3; @compute @workgroup_size(1) fn main() { buffer = vec3(0x12345678, 0xabcdef01, 0x98765432); } `; runShaderTest(t, wgsl, new Uint32Array([0x12345678, 0xabcdef01, 0x98765432, 0xdeadbeef])); }); g.test('matCx3') .desc( `Test padding bytes are preserved when assigning to a variable of type matCx3. ` ) .params(u => u .combine('columns', [2, 3, 4] as const) .combine('use_struct', [true, false] as const) .beginSubcases() ) .fn(t => { const cols = t.params.columns; const wgsl = ` alias Mat = mat${cols}x3; ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`} @group(0) @binding(0) var buffer : Type; @compute @workgroup_size(1) fn main() { var m : Mat; for (var c = 0u; c < ${cols}; c++) { m[c] = vec3(f32(c*3 + 1), f32(c*3 + 2), f32(c*3 + 3)); } buffer = Type(m); } `; const f_values = new Float32Array(cols * 4); const u_values = new Uint32Array(f_values.buffer); for (let c = 0; c < cols; c++) { f_values[c * 4 + 0] = c * 3 + 1; f_values[c * 4 + 1] = c * 3 + 2; f_values[c * 4 + 2] = c * 3 + 3; u_values[c * 4 + 3] = 0xdeadbeef; } runShaderTest(t, wgsl, u_values); }); g.test('array_of_matCx3') .desc( `Test that padding bytes in between array elements are preserved. This test defines creates a read-write storage buffer with type array, 4>. The shader assigns the whole variable at once, and we then test that data in the padding bytes was preserved. ` ) .params(u => u .combine('columns', [2, 3, 4] as const) .combine('use_struct', [true, false] as const) .beginSubcases() ) .fn(t => { const cols = t.params.columns; const wgsl = ` alias Mat = mat${cols}x3; ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`} @group(0) @binding(0) var buffer : array; @compute @workgroup_size(1) fn main() { var m : Mat; for (var c = 0u; c < ${cols}; c++) { m[c] = vec3(f32(c*3 + 1), f32(c*3 + 2), f32(c*3 + 3)); } buffer = array(Type(m), Type(m * 2), Type(m * 3), Type(m * 4)); } `; const f_values = new Float32Array(cols * 4 * 4); const u_values = new Uint32Array(f_values.buffer); for (let i = 0; i < 4; i++) { for (let c = 0; c < cols; c++) { f_values[i * (cols * 4) + c * 4 + 0] = (c * 3 + 1) * (i + 1); f_values[i * (cols * 4) + c * 4 + 1] = (c * 3 + 2) * (i + 1); f_values[i * (cols * 4) + c * 4 + 2] = (c * 3 + 3) * (i + 1); u_values[i * (cols * 4) + c * 4 + 3] = 0xdeadbeef; } } runShaderTest(t, wgsl, u_values); });