diff options
Diffstat (limited to '')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts new file mode 100644 index 0000000000..64fcc4c284 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts @@ -0,0 +1,259 @@ +export const description = `Validation tests for entry point user-defined IO`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { ShaderValidationTest } from '../shader_validation_test.js'; + +import { generateShader } from './util.js'; + +export const g = makeTestGroup(ShaderValidationTest); + +const kValidLocationTypes = new Set([ + 'f16', + 'f32', + 'i32', + 'u32', + 'vec2<f32>', + 'vec2<i32>', + 'vec2<u32>', + 'vec3<f32>', + 'vec3<i32>', + 'vec3<u32>', + 'vec4<f32>', + 'vec4<i32>', + 'vec4<u32>', + 'vec2h', + 'vec2f', + 'vec2i', + 'vec2u', + 'vec3h', + 'vec3f', + 'vec3i', + 'vec3u', + 'vec4h', + 'vec4f', + 'vec4i', + 'vec4u', + 'MyAlias', +]); + +const kInvalidLocationTypes = new Set([ + 'bool', + 'vec2<bool>', + 'vec3<bool>', + 'vec4<bool>', + 'mat2x2<f32>', + 'mat2x3<f32>', + 'mat2x4<f32>', + 'mat3x2<f32>', + 'mat3x3<f32>', + 'mat3x4<f32>', + 'mat4x2<f32>', + 'mat4x3<f32>', + 'mat4x4<f32>', + 'mat2x2f', + 'mat2x3f', + 'mat2x4f', + 'mat3x2f', + 'mat3x3f', + 'mat3x4f', + 'mat4x2f', + 'mat4x3f', + 'mat4x4f', + 'mat2x2h', + 'mat2x3h', + 'mat2x4h', + 'mat3x2h', + 'mat3x3h', + 'mat3x4h', + 'mat4x2h', + 'mat4x3h', + 'mat4x4h', + 'array<f32, 12>', + 'array<i32, 12>', + 'array<u32, 12>', + 'array<bool, 12>', + 'atomic<i32>', + 'atomic<u32>', + 'MyStruct', + 'texture_1d<i32>', + 'texture_2d<f32>', + 'texture_2d_array<i32>', + 'texture_3d<f32>', + 'texture_cube<u32>', + 'texture_cube_array<i32>', + 'texture_multisampled_2d<i32>', + 'texture_external', + 'texture_storage_1d<rgba8unorm, write>', + 'texture_storage_2d<rg32float, write>', + 'texture_storage_2d_array<r32float, write>', + 'texture_storage_3d<r32float, write>', + 'texture_depth_2d', + 'texture_depth_2d_array', + 'texture_depth_cube', + 'texture_depth_cube_array', + 'texture_depth_multisampled_2d', + 'sampler', + 'sampler_comparison', +]); + +g.test('stage_inout') + .desc(`Test validation of user-defined IO stage and in/out usage`) + .params(u => + u + .combine('use_struct', [true, false] as const) + .combine('target_stage', ['vertex', 'fragment', 'compute'] as const) + .combine('target_io', ['in', 'out'] as const) + .beginSubcases() + ) + .fn(t => { + const code = generateShader({ + attribute: '@location(0)', + type: 'f32', + stage: t.params.target_stage, + io: t.params.target_io, + use_struct: t.params.use_struct, + }); + + // Expect to fail for compute shaders or when used as a non-struct vertex output (since the + // position built-in must also be specified). + const expectation = + t.params.target_stage === 'fragment' || + (t.params.target_stage === 'vertex' && (t.params.target_io === 'in' || t.params.use_struct)); + t.expectCompileResult(expectation, code); + }); + +g.test('type') + .desc(`Test validation of user-defined IO types`) + .params(u => + u + .combine('use_struct', [true, false] as const) + .combine('type', new Set([...kValidLocationTypes, ...kInvalidLocationTypes])) + .beginSubcases() + ) + .beforeAllSubcases(t => { + if ( + t.params.type === 'f16' || + ((t.params.type.startsWith('mat') || t.params.type.startsWith('vec')) && + t.params.type.endsWith('h')) + ) { + t.selectDeviceOrSkipTestCase('shader-f16'); + } + }) + .fn(t => { + let code = ''; + + if ( + t.params.type === 'f16' || + ((t.params.type.startsWith('mat') || t.params.type.startsWith('vec')) && + t.params.type.endsWith('h')) + ) { + code += 'enable f16;\n'; + } + + if (t.params.type === 'MyStruct') { + // Generate a struct that contains a valid type. + code += `struct MyStruct { + value : f32, + } + `; + } + if (t.params.type === 'MyAlias') { + code += 'type MyAlias = i32;\n'; + } + + code += generateShader({ + attribute: '@location(0) @interpolate(flat)', + type: t.params.type, + stage: 'fragment', + io: 'in', + use_struct: t.params.use_struct, + }); + + t.expectCompileResult(kValidLocationTypes.has(t.params.type), code); + }); + +g.test('nesting') + .desc(`Test validation of nested user-defined IO`) + .params(u => + u + .combine('target_stage', ['vertex', 'fragment', ''] as const) + .combine('target_io', ['in', 'out'] as const) + .beginSubcases() + ) + .fn(t => { + let code = ''; + + // Generate a struct that contains a valid type. + code += `struct Inner { + @location(0) value : f32, + } + struct Outer { + inner : Inner, + } + `; + + code += generateShader({ + attribute: '', + type: 'Outer', + stage: t.params.target_stage, + io: t.params.target_io, + use_struct: false, + }); + + // Expect to pass only if the struct is not used for entry point IO. + t.expectCompileResult(t.params.target_stage === '', code); + }); + +g.test('duplicates') + .desc(`Test that duplicated user-defined IO attributes are validated.`) + .params(u => + u + // Place two @location(0) attributes onto the entry point function. + // The function: + // - has two non-struct parameters (`p1` and `p2`) + // - has two struct parameters each with two members (`s1{a,b}` and `s2{a,b}`) + // - returns a struct with two members (`ra` and `rb`) + // By default, all of these user-defined IO variables will have unique location attributes. + .combine('first', ['p1', 's1a', 's2a', 'ra'] as const) + .combine('second', ['p2', 's1b', 's2b', 'rb'] as const) + .beginSubcases() + ) + .fn(t => { + const p1 = t.params.first === 'p1' ? '0' : '1'; + const p2 = t.params.second === 'p2' ? '0' : '2'; + const s1a = t.params.first === 's1a' ? '0' : '3'; + const s1b = t.params.second === 's1b' ? '0' : '4'; + const s2a = t.params.first === 's2a' ? '0' : '5'; + const s2b = t.params.second === 's2b' ? '0' : '6'; + const ra = t.params.first === 'ra' ? '0' : '1'; + const rb = t.params.second === 'rb' ? '0' : '2'; + const code = ` + struct S1 { + @location(${s1a}) a : f32, + @location(${s1b}) b : f32, + }; + struct S2 { + @location(${s2a}) a : f32, + @location(${s2b}) b : f32, + }; + struct R { + @location(${ra}) a : f32, + @location(${rb}) b : f32, + }; + @fragment + fn main(@location(${p1}) p1 : f32, + @location(${p2}) p2 : f32, + s1 : S1, + s2 : S2, + ) -> R { + return R(); + } + `; + + // The test should fail if both @location(0) attributes are on the input parameters or + // structures, or it they are both on the output struct. Otherwise it should pass. + const firstIsRet = t.params.first === 'ra'; + const secondIsRet = t.params.second === 'rb'; + const expectation = firstIsRet !== secondIsRet; + t.expectCompileResult(expectation, code); + }); |