summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts277
1 files changed, 277 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
new file mode 100644
index 0000000000..e57677a243
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
@@ -0,0 +1,277 @@
+export const description = `Validation tests for entry point built-in variables`;
+
+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);
+
+// List of all built-in variables and their stage, in|out usage, and type.
+// Taken from table in Section 15:
+// https://www.w3.org/TR/2021/WD-WGSL-20211013/#builtin-variables
+export const kBuiltins = [
+ { name: 'vertex_index', stage: 'vertex', io: 'in', type: 'u32' },
+ { name: 'instance_index', stage: 'vertex', io: 'in', type: 'u32' },
+ { name: 'position', stage: 'vertex', io: 'out', type: 'vec4<f32>' },
+ { name: 'position', stage: 'fragment', io: 'in', type: 'vec4<f32>' },
+ { name: 'front_facing', stage: 'fragment', io: 'in', type: 'bool' },
+ { name: 'frag_depth', stage: 'fragment', io: 'out', type: 'f32' },
+ { name: 'local_invocation_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+ { name: 'local_invocation_index', stage: 'compute', io: 'in', type: 'u32' },
+ { name: 'global_invocation_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+ { name: 'workgroup_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+ { name: 'num_workgroups', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+ { name: 'sample_index', stage: 'fragment', io: 'in', type: 'u32' },
+ { name: 'sample_mask', stage: 'fragment', io: 'in', type: 'u32' },
+ { name: 'sample_mask', stage: 'fragment', io: 'out', type: 'u32' },
+] as const;
+
+// List of types to test against.
+const kTestTypes = [
+ 'bool',
+ 'u32',
+ 'i32',
+ 'f32',
+ 'vec2<bool>',
+ 'vec2<u32>',
+ 'vec2<i32>',
+ 'vec2<f32>',
+ 'vec3<bool>',
+ 'vec3<u32>',
+ 'vec3<i32>',
+ 'vec3<f32>',
+ 'vec4<bool>',
+ 'vec4<u32>',
+ 'vec4<i32>',
+ 'vec4<f32>',
+ 'mat2x2<f32>',
+ 'mat2x3<f32>',
+ 'mat2x4<f32>',
+ 'mat3x2<f32>',
+ 'mat3x3<f32>',
+ 'mat3x4<f32>',
+ 'mat4x2<f32>',
+ 'mat4x3<f32>',
+ 'mat4x4<f32>',
+ 'atomic<u32>',
+ 'atomic<i32>',
+ 'array<bool,4>',
+ 'array<u32,4>',
+ 'array<i32,4>',
+ 'array<f32,4>',
+ 'MyStruct',
+] as const;
+
+g.test('stage_inout')
+ .desc(
+ `Test that each @builtin attribute is validated against the required stage and in/out usage for that built-in variable.`
+ )
+ .params(u =>
+ u
+ .combineWithParams(kBuiltins)
+ .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: `@builtin(${t.params.name})`,
+ type: t.params.type,
+ stage: t.params.target_stage,
+ io: t.params.target_io,
+ use_struct: t.params.use_struct,
+ });
+
+ // Expect to pass iff the built-in table contains an entry that matches.
+ const expectation = kBuiltins.some(
+ x =>
+ x.name === t.params.name &&
+ (x.stage === t.params.target_stage ||
+ (t.params.use_struct && t.params.target_stage === '')) &&
+ (x.io === t.params.target_io || t.params.target_stage === '') &&
+ x.type === t.params.type
+ );
+ t.expectCompileResult(expectation, code);
+ });
+
+g.test('type')
+ .desc(
+ `Test that each @builtin attribute is validated against the required type of that built-in variable.`
+ )
+ .params(u =>
+ u
+ .combineWithParams(kBuiltins)
+ .combine('use_struct', [true, false] as const)
+ .combine('target_type', kTestTypes)
+ .beginSubcases()
+ )
+ .fn(t => {
+ let code = '';
+
+ if (t.params.target_type === 'MyStruct') {
+ // Generate a struct that contains the correct built-in type.
+ code += 'struct MyStruct {\n';
+ code += ` value : ${t.params.type}\n`;
+ code += '};\n\n';
+ }
+
+ code += generateShader({
+ attribute: `@builtin(${t.params.name})`,
+ type: t.params.target_type,
+ stage: t.params.stage,
+ io: t.params.io,
+ use_struct: t.params.use_struct,
+ });
+
+ // Expect to pass iff the built-in table contains an entry that matches.
+ const expectation = kBuiltins.some(
+ x =>
+ x.name === t.params.name &&
+ x.stage === t.params.stage &&
+ x.io === t.params.io &&
+ x.type === t.params.target_type
+ );
+ t.expectCompileResult(expectation, code);
+ });
+
+g.test('nesting')
+ .desc(`Test validation of nested built-in variables`)
+ .params(u =>
+ u
+ .combine('target_stage', ['fragment', ''] as const)
+ .combine('target_io', ['in', 'out'] as const)
+ .beginSubcases()
+ )
+ .fn(t => {
+ // Generate a struct that contains a sample_mask builtin, nested inside another struct.
+ let code = `
+ struct Inner {
+ @builtin(sample_mask) value : u32
+ };
+ 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 built-in variables are validated.`)
+ .params(u =>
+ u
+ // Place two @builtin(sample_mask) attributes onto the entry point function.
+ // We use `sample_mask` as it is valid as both an input and output for the same entry point.
+ // 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 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' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)';
+ const p2 =
+ t.params.second === 'p2' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)';
+ const s1a =
+ t.params.first === 's1a' ? '@builtin(sample_mask)' : '@location(3) @interpolate(flat)';
+ const s1b =
+ t.params.second === 's1b' ? '@builtin(sample_mask)' : '@location(4) @interpolate(flat)';
+ const s2a =
+ t.params.first === 's2a' ? '@builtin(sample_mask)' : '@location(5) @interpolate(flat)';
+ const s2b =
+ t.params.second === 's2b' ? '@builtin(sample_mask)' : '@location(6) @interpolate(flat)';
+ const ra =
+ t.params.first === 'ra' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)';
+ const rb =
+ t.params.second === 'rb' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)';
+ const code = `
+ struct S1 {
+ ${s1a} a : u32,
+ ${s1b} b : u32,
+ };
+ struct S2 {
+ ${s2a} a : u32,
+ ${s2b} b : u32,
+ };
+ struct R {
+ ${ra} a : u32,
+ ${rb} b : u32,
+ };
+ @fragment
+ fn main(${p1} p1 : u32,
+ ${p2} p2 : u32,
+ s1 : S1,
+ s2 : S2,
+ ) -> R {
+ return R();
+ }
+ `;
+
+ // The test should fail if both @builtin(sample_mask) 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);
+ });
+
+g.test('missing_vertex_position')
+ .desc(`Test that vertex shaders are required to output @builtin(position).`)
+ .params(u =>
+ u
+ .combine('use_struct', [true, false] as const)
+ .combine('attribute', ['@builtin(position)', '@location(0)'] as const)
+ .beginSubcases()
+ )
+ .fn(t => {
+ const code = `
+ struct S {
+ ${t.params.attribute} value : vec4<f32>
+ };
+
+ @vertex
+ fn main() -> ${t.params.use_struct ? 'S' : `${t.params.attribute} vec4<f32>`} {
+ return ${t.params.use_struct ? 'S' : 'vec4<f32>'}();
+ }
+ `;
+
+ // Expect to pass only when using @builtin(position).
+ t.expectCompileResult(t.params.attribute === '@builtin(position)', code);
+ });
+
+g.test('reuse_builtin_name')
+ .desc(`Test that a builtin name can be used in different contexts`)
+ .params(u =>
+ u
+ .combineWithParams(kBuiltins)
+ .combine('use', ['type_name', 'struct', 'function', 'module-var', 'function-var'])
+ )
+ .fn(t => {
+ let code = '';
+ if (t.params.use === 'type_name') {
+ code += `type ${t.params.name} = i32;`;
+ } else if (t.params.use === `struct`) {
+ code += `struct ${t.params.name} { i: f32, }`;
+ } else if (t.params.use === `function`) {
+ code += `fn ${t.params.name}() {}`;
+ } else if (t.params.use === `module-var`) {
+ code += `const ${t.params.name} = 1;`;
+ } else if (t.params.use === `function-var`) {
+ code += `fn test() { let ${t.params.name} = 1; }`;
+ }
+ t.expectCompileResult(true, code);
+ });