🏡 index : cold-air-networks/mirror-webgpu-cts.git

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<u32>,
        // 4 bytes of padding
        c : vec2<u32>,
        // 8 bytes of padding
      }
      @group(0) @binding(0) var<storage, read_write> 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<u32>
        0xabcdef01, 0xabcdef01, 0xabcdef01, 0xdeadbeef,
        // c : vec2<u32>
        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<storage, read_write> 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<u32>,
        // 4 bytes of padding
        c : vec2<u32>,
        // 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<storage, read_write> 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<u32>
        0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
        // a3.c1 : vec2<u32>
        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<u32>
        0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
        // b3.b2.c1 : vec2<u32>
        0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
        // b3.c2 : S1
        // b3.c2.a1 : u32
        0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
        // b3.c2.b1 : vec3<u32>
        0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
        // b3.c2.c1 : vec2<u32>
        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<u32>
        0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
        // c3.b2.c1 : vec2<u32>
        0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
        // c3.c2 : S1
        // c3.c2.a1 : u32
        0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
        // c3.c2.b1 : vec3<u32>
        0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
        // c3.c2.c1 : vec2<u32>
        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<vec3, 4>. 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<storage, read_write> buffer : array<vec3<u32>, 4>;

      @compute @workgroup_size(1)
      fn main() {
        buffer = array<vec3<u32>, 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<vec3h, 4>. 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<storage, read_write> buffer : array<vec3<f16>, 4>;

      @compute @workgroup_size(1)
      fn main() {
        buffer = array<vec3<f16>, 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<vec3h, 4>. 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<storage, read_write> buffer : array<vec3<f16>>;

      @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<S, 4>, 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<u32>,
      }
      @group(0) @binding(0) var<storage, read_write> buffer : array<S, 3>;

      @compute @workgroup_size(1)
      fn main() {
        buffer = array<S, 3>(
          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<storage, read_write> buffer : vec3<u32>;

      @compute @workgroup_size(1)
      fn main() {
        buffer = vec3<u32>(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<f32>;
      ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`}
      @group(0) @binding(0) var<storage, read_write> 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<matCx3<f32>, 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<f32>;
    ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`}
    @group(0) @binding(0) var<storage, read_write> buffer : array<Type, 4>;

    @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, 4>(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);
  });