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

export const description = `
Validation tests for bufferView
`;

import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import { WGSLLanguageFeature } from '../../../../../capability_info.js';
import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);

g.test('must_use')
  .desc('Tests that the builtin has the @must_use attribute')
  .params(u => u.combine('must_use', [true, false] as const))
  .fn(t => {
    t.skipIfLanguageFeatureNotSupported('buffer_view');
    const wgsl = `
@group(0) @binding(0) var<storage> v : buffer;
@compute @workgroup_size(1)
fn main() {
  ${t.params.must_use ? 'let p = ' : ''}bufferView<u32>(&v, 0);
}`;

    t.expectCompileResult(t.params.must_use, wgsl);
  });

const kTypes = objectsToRecord(kAllScalarsAndVectors);

g.test('return_store_type')
  .desc('Validates return type')
  .params(u =>
    u
      .combine('template_type', keysOf(kTypes))
      .filter(t => {
        const type = kTypes[t.template_type];
        const eleType = elementTypeOf(type);
        return (
          eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
        );
      })
      .combine('return_type', keysOf(kTypes))
      .filter(t => {
        const type = kTypes[t.return_type];
        const eleType = elementTypeOf(type);
        return (
          eleType !== Type.abstractInt && eleType !== Type.abstractFloat && eleType !== Type.bool
        );
      })
      .beginSubcases()
      .combine('ptr', [false, true] as const)
  )
  .fn(t => {
    t.skipIfLanguageFeatureNotSupported('buffer_view');
    const template_type = kTypes[t.params.template_type];
    const return_type = kTypes[t.params.return_type];
    let ret_type = return_type.toString();
    if (t.params.ptr) {
      ret_type = `ptr<storage, ${ret_type}>`;
    }
    let enables = ``;
    if (template_type.requiresF16() || return_type.requiresF16()) {
      enables = `enable f16;`;
    }
    const wgsl = `
${enables}
@group(0) @binding(0) var<storage> v : buffer;
@compute @workgroup_size(1)
fn main() {
  let res : ${ret_type} = bufferView<${template_type.toString()}>(&v, 0);
}`;

    t.expectCompileResult(t.params.ptr && template_type === return_type, wgsl);
  });

g.test('buffer_type')
  .desc('Validates the buffer parameter type')
  .params(u =>
    u
      .combine('type', [
        'unsized_ro_storage',
        'unsized_storage',
        'sized_ro_storage',
        'sized_storage',
        'sized_uniform',
        'sized_workgroup',
        'override_workgroup',
      ] as const)
      .beginSubcases()
      .combine('ptr', [false, true] as const)
  )
  .fn(t => {
    t.skipIfLanguageFeatureNotSupported('buffer_view');
    const aspace = t.params.type.substr(t.params.type.lastIndexOf('_') + 1);
    let access = '';
    switch (t.params.type) {
      case 'unsized_ro_storage':
      case 'sized_ro_storage':
        access = ', read';
        break;
      case 'unsized_storage':
      case 'sized_storage':
        access = ', read_write';
        break;
      default:
        break;
    }
    const wgsl = `
@group(0) @binding(0) var<storage> unsized_ro_storage : buffer;
@group(0) @binding(1) var<storage, read_write> unsized_storage : buffer;
@group(0) @binding(2) var<storage> sized_ro_storage : buffer<128>;
@group(0) @binding(3) var<storage, read_write> sized_storage : buffer<128>;
@group(0) @binding(4) var<uniform> sized_uniform : buffer<128>;
var<workgroup> sized_workgroup : buffer<128>;
override o : u32;
var<workgroup> override_workgroup : buffer<o>;

@compute @workgroup_size(1)
fn main() {
  let p : ptr<${aspace}, u32${access}> = bufferView<u32>(${t.params.ptr ? '&' : ''}${
    t.params.type
  }, 0);
}`;

    t.expectCompileResult(t.params.ptr, wgsl);
  });

g.test('offset_type')
  .desc('Validates the offset parameter type')
  .params(u => u.combine('type', keysOf(kTypes)))
  .fn(t => {
    t.skipIfLanguageFeatureNotSupported('buffer_view');
    const type = kTypes[t.params.type];
    const enables = type.requiresF16() ? 'enable f16;' : '';
    const wgsl = `
${enables}
@group(0) @binding(0) var<storage> v : buffer;
@compute @workgroup_size(1)
fn main() {
  let p = bufferView<u32>(&v, ${type.create(0).wgsl()});
}`;

    t.expectCompileResult(
      type === Type.abstractInt || type === Type.u32 || type === Type.i32,
      wgsl
    );
  });

interface EarlyEvalCase {
  code: string;
  valid: boolean | 'pipeline';
  constants?: Record<string, GPUPipelineConstantValue>;
  ptr_param?: boolean;
}

const kEarlyEvalCases: Record<string, EarlyEvalCase> = {
  ro_storage_buffer: {
    code: `
@group(0) @binding(0) var<storage> v : buffer<16>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: true,
  },
  storage_buffer: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer<16>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: true,
  },
  uniform_buffer: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<16>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: true,
  },
  workgroup_buffer: {
    code: `
var<workgroup> v : buffer<16>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: true,
  },
  ro_storage_buffer_too_small_for_type: {
    code: `
@group(0) @binding(0) var<storage> v : buffer<8>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: false,
  },
  uniform_buffer_too_small_with_const_offset: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<24>;
fn foo() {
  let p = bufferView<vec4u>(&v, 16);
}`,
    valid: false,
  },
  uniform_buffer_too_small_with_override_offset: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<24>;
override offset : u32;
fn foo() {
  let p = bufferView<vec4u>(&v, offset);
}`,
    valid: 'pipeline',
    constants: { offset: 16 },
  },
  workgroup_buffer_too_small_with_override: {
    code: `
override size : u32;
var<workgroup> v : buffer<size>;
fn foo() {
  let p = bufferView<vec4u>(&v, 0);
}`,
    valid: 'pipeline',
    constants: { size: 12 },
  },
  storage_buffer_too_small_for_one_array_element: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer<28>;
struct S {
  a: vec4u,
  b: vec4f,
}
fn foo() {
  let p = bufferView<array<S>>(&v, 0);
}`,
    valid: false,
  },
  storage_buffer_out_of_range_u32: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
struct S {
  a: vec4u,
  b: vec4f,
}
fn foo() {
  let p = buffer<S>(&v, 4294967279);
}`,
    valid: false,
  },
  uniform_buffer_too_small_through_unsized_function: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<28>;
fn bar(p : ptr<uniform, buffer>) {
  let q = bufferView<vec4u>(p, 16);
}
fn foo() {
  bar(&v);
}`,
    valid: false,
    ptr_param: true,
  },
  uniform_buffer_too_small_through_unsized_function_override: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<28>;
override offset : u32;
fn bar(p : ptr<uniform, buffer>) {
  let q = bufferView<vec4u>(p, offset);
}
fn foo() {
  bar(&v);
}`,
    valid: 'pipeline',
    constants: { offset: 16 },
    ptr_param: true,
  },
  workgroup_to_smaller_size_param: {
    code: `
var<workgroup> v : buffer<128>;
fn bar(p : ptr<workgroup, buffer<16>) {
  let q = bufferView<array<vec2u, 3>(p, 0);
}
fn foo() {
  bar(&v);
}`,
    valid: false,
    ptr_param: true,
  },
  ro_storage_unsized_buffer_to_sized_param: {
    code: `
@group(0) @binding(0) var<storage> v : buffer;
fn bar(p : ptr<storage, buffer<16>) {
  let q = bufferView<array<vec2u, 3>(p, 0);
}
fn foo() {
  bar(&v);
}`,
    valid: false,
    ptr_param: true,
  },
  offset_not_aligned: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<128>;
fn foo() {
  let p = bufferView<vec4u>(&v, 12);
}`,
    valid: false,
  },
  offset_not_aligned_override: {
    code: `
override offset : u32;
@group(0) @binding(0) var<uniform> v : buffer<128>;
fn foo() {
  let p = bufferView<vec4u>(&v, offset);
}`,
    valid: 'pipeline',
    constants: { offset: 12 },
  },
  offset_negative: {
    code: `
@group(0) @binding(0) var<uniform> v : buffer<128>;
fn foo() {
  let p = bufferView<vec4u>(&v, -16);
}`,
    valid: false,
  },
  offset_negative_override: {
    code: `
override offset : u32;
@group(0) @binding(0) var<uniform> v : buffer<128>;
fn foo() {
  let p = bufferView<vec4u>(&v, offset);
}`,
    valid: 'pipeline',
    constants: { offset: -16 },
  },
};

g.test('early_eval_errors')
  .desc('Test shader-creation and pipeline-creation errors')
  .params(u => u.combine('case', keysOf(kEarlyEvalCases)))
  .fn(t => {
    const testcase = kEarlyEvalCases[t.params.case];
    t.skipIfLanguageFeatureNotSupported('buffer_view');
    if (testcase.ptr_param === true) {
      t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
    }

    t.expectCompileResult(testcase.valid !== false, testcase.code);
    if (testcase.valid !== false) {
      t.expectPipelineResult({
        expectedResult: testcase.valid === true,
        code: testcase.code,
        constants: testcase.constants,
        statements: ['foo();'],
      });
    }
  });

interface LHSCallCase {
  code: string;
  requires?: WGSLLanguageFeature[];
}

const kLHSCallCases: Record<string, LHSCallCase> = {
  u32: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  *bufferView<u32>(&v, 0) = 123u;
}`,
  },
  u32_array: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  (*bufferView<array<u32, 4>>(&v, 0))[0] = 123u;
}`,
  },
  f32_vector_letter: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  (*bufferView<vec4f>(&v, 0)).x = 13.3f;
}`,
  },
  f32_struct_access: {
    code: `
struct S {
  member_a: f32,
}
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  (*bufferView<S>(&v, 0)).member_a = 13.3f;
}`,
  },
  u32_array_ptr_composite: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  bufferView<array<u32, 4>>(&v, 0)[0] = 123u;
}`,
    requires: ['pointer_composite_access'],
  },
  vec4u_swizzle_assign: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  (*bufferView<vec4u>(&v, 0)).zxy = vec3u(42);
}`,
    requires: ['swizzle_assignment'],
  },
  vec4u_ptr_composite_swizzle_assign: {
    code: `
@group(0) @binding(0) var<storage, read_write> v : buffer;
@compute @workgroup_size(1)
fn main() {
  bufferView<vec4u>(&v, 0).zxy = vec3u(42);
}`,
    requires: ['pointer_composite_access', 'swizzle_assignment'],
  },
  compound_assign: {
    code: `
var<workgroup> v : buffer<256>;
@compute @workgroup_size(1)
fn main() {
  *bufferView<u32>(&v, 0) += 123u;
}`,
  },
};

g.test('lhs_call')
  .desc('Validate that bufferView can be on the LHS of an assignment')
  .params(u => u.combine('case', keysOf(kLHSCallCases)))
  .fn(t => {
    t.skipIfLanguageFeatureNotSupported('buffer_view');

    const testcase = kLHSCallCases[t.params.case];
    let valid = true;
    const features = testcase.requires ?? [];
    features.forEach(f => {
      valid &&= t.hasLanguageFeature(f);
    });
    t.expectCompileResult(valid, testcase.code);
  });