export const description = `Validation tests for the interpolate attribute`; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { keysOf } from '../../../../common/util/data_tables.js'; import { ShaderValidationTest } from '../shader_validation_test.js'; import { generateShader } from './util.js'; export const g = makeTestGroup(ShaderValidationTest); // List of valid interpolation attributes. const kValidInterpolationAttributes = new Set([ '', '@interpolate(perspective)', '@interpolate(perspective, center)', '@interpolate(perspective, centroid)', '@interpolate(flat)', '@interpolate(flat, first)', '@interpolate(flat, either)', '@interpolate(perspective)', '@interpolate(perspective, center)', '@interpolate(perspective, centroid)', '@interpolate(perspective, sample)', '@interpolate(linear)', '@interpolate(linear, center)', '@interpolate(linear, centroid)', '@interpolate(linear, sample)', ]); g.test('type_and_sampling') .desc(`Test that all combinations of interpolation type and sampling are validated correctly.`) .params(u => u .combine('stage', ['vertex', 'fragment'] as const) .combine('io', ['in', 'out'] as const) .combine('use_struct', [true, false] as const) .combine('type', [ '', 'flat', 'perspective', 'linear', 'center', // Invalid as first param 'centroid', // Invalid as first param 'sample', // Invalid as first param 'first', // Invalid as first param 'either', // Invalid as first param ] as const) // vertex output must include a position builtin, so must use a struct .filter(t => !(t.stage === 'vertex' && t.use_struct === false)) .combine('sampling', [ '', 'center', 'centroid', 'sample', 'first', 'either', 'flat', // Invalid as second param 'perspective', // Invalid as second param 'linear', // Invalid as second param ] as const) .beginSubcases() ) .fn(t => { let interpolate = ''; if (t.params.type !== '' || t.params.sampling !== '') { interpolate = '@interpolate('; if (t.params.type !== '') { interpolate += `${t.params.type}`; } if (t.params.sampling !== '') { interpolate += `, ${t.params.sampling}`; } interpolate += `)`; } const code = generateShader({ attribute: '@location(0)' + interpolate, type: 'f32', stage: t.params.stage, io: t.params.io, use_struct: t.params.use_struct, }); t.expectCompileResult(kValidInterpolationAttributes.has(interpolate), code); }); g.test('require_location') .desc(`Test that the interpolate attribute is only accepted with user-defined IO.`) .params(u => u .combine('stage', ['vertex', 'fragment'] as const) .combine('attribute', ['@location(0)', '@builtin(position)'] as const) .combine('use_struct', [true, false] as const) .beginSubcases() ) .fn(t => { if ( t.params.stage === 'vertex' && t.params.use_struct === false && !t.params.attribute.includes('position') ) { t.skip('vertex output must include a position builtin, so must use a struct'); } const code = generateShader({ attribute: t.params.attribute + `@interpolate(flat, either)`, type: 'vec4', stage: t.params.stage, io: t.params.stage === 'fragment' ? 'in' : 'out', use_struct: t.params.use_struct, }); t.expectCompileResult(t.params.attribute === '@location(0)', code); }); g.test('integral_types') .desc(`Test that the implementation requires @interpolate(flat) for integral user-defined IO.`) .params(u => u .combine('stage', ['vertex', 'fragment'] as const) .combine('type', ['i32', 'u32', 'vec2', 'vec4'] as const) .combine('use_struct', [true, false] as const) .combine('attribute', kValidInterpolationAttributes) .beginSubcases() ) .fn(t => { if (t.params.stage === 'vertex' && t.params.use_struct === false) { t.skip('vertex output must include a position builtin, so must use a struct'); } const code = generateShader({ attribute: '@location(0)' + t.params.attribute, type: t.params.type, stage: t.params.stage, io: t.params.stage === 'vertex' ? 'out' : 'in', use_struct: t.params.use_struct, }); const expectSuccess = t.params.attribute.startsWith('@interpolate(flat'); t.expectCompileResult(expectSuccess, code); }); g.test('duplicate') .desc(`Test that the interpolate attribute can only be applied once.`) .params(u => u.combine('attr', ['', '@interpolate(flat)'] as const)) .fn(t => { const code = generateShader({ attribute: `@location(0) @interpolate(flat, either) ${t.params.attr}`, type: 'vec4', stage: 'fragment', io: 'in', use_struct: false, }); t.expectCompileResult(t.params.attr === '', code); }); const kValidationTests: { [key: string]: { src: string; pass: boolean } } = { valid: { src: `@interpolate(perspective)`, pass: true, }, no_space: { src: `@interpolate(perspective,center)`, pass: true, }, trailing_comma_one_arg: { src: `@interpolate(flat,)`, pass: true, }, trailing_comma_two_arg: { src: `@interpolate(perspective, center,)`, pass: true, }, newline: { src: '@\ninterpolate(perspective)', pass: true, }, comment: { src: `@/* comment */interpolate(perspective)`, pass: true, }, no_params: { src: `@interpolate()`, pass: false, }, missing_left_paren: { src: `@interpolate perspective)`, pass: false, }, missing_value_and_left_paren: { src: `@interpolate)`, pass: false, }, missing_right_paren: { src: `@interpolate(perspective`, pass: false, }, missing_parens: { src: `@interpolate`, pass: false, }, missing_comma: { src: `@interpolate(perspective center)`, pass: false, }, numeric: { src: `@interpolate(1)`, pass: false, }, numeric_second_param: { src: `@interpolate(perspective, 1)`, pass: false, }, }; g.test('interpolation_validation') .desc(`Test validation of interpolation`) .params(u => u.combine('attr', keysOf(kValidationTests))) .fn(t => { const code = ` @vertex fn main(${kValidationTests[t.params.attr].src} @location(0) b: f32) -> @builtin(position) vec4 { return vec4f(0); }`; const expectSuccess = kValidationTests[t.params.attr].pass; t.expectCompileResult(expectSuccess, code); });