export const description = ` This test dedicatedly tests validation of pipeline overridable constants of createRenderPipeline. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { kValue } from '../../../util/constants.js'; import * as vtu from '../validation_test_utils.js'; import { CreateRenderPipelineValidationTest } from './common.js'; export const g = makeTestGroup(CreateRenderPipelineValidationTest); g.test('identifier,vertex') .desc( ` Tests calling createRenderPipeline(Async) validation for overridable constants identifiers in vertex state. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { vertexConstants: {}, _success: true }, { vertexConstants: { x: 1, y: 1 }, _success: true }, { vertexConstants: { x: 1, y: 1, 1: 1, 1000: 1 }, _success: true }, { vertexConstants: { 'x\0': 1, y: 1 }, _success: false }, { vertexConstants: { xxx: 1 }, _success: false }, { vertexConstants: { 1: 1 }, _success: true }, { vertexConstants: { 2: 1 }, _success: false }, { vertexConstants: { z: 1 }, _success: false }, // pipeline constant id is specified for z { vertexConstants: { w: 1 }, _success: false }, // pipeline constant id is specified for w { vertexConstants: { 1: 1, z: 1 }, _success: false }, // pipeline constant id is specified for z { vertexConstants: { 数: 1 }, _success: true }, // test non-ASCII { vertexConstants: { séquençage: 0 }, _success: false }, // test unicode normalization ] as { vertexConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, vertexConstants, _success } = t.params; vtu.doCreateRenderPipelineTest(t, isAsync, _success, { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: ` override x: f32 = 0.0; override y: f32 = 0.0; override 数: f32 = 0.0; override séquençage: f32 = 0.0; @id(1) override z: f32 = 0.0; @id(1000) override w: f32 = 1.0; @vertex fn main() -> @builtin(position) vec4 { return vec4(x, y, z, w + 数 + séquençage); }`, }), entryPoint: 'main', constants: vertexConstants, }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [{ format: 'rgba8unorm' }], }, }); }); g.test('identifier,fragment') .desc( ` Tests calling createRenderPipeline(Async) validation for overridable constants identifiers in fragment state. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { fragmentConstants: {}, _success: true }, { fragmentConstants: { r: 1, g: 1 }, _success: true }, { fragmentConstants: { r: 1, g: 1, 1: 1, 1000: 1 }, _success: true }, { fragmentConstants: { 'r\0': 1 }, _success: false }, { fragmentConstants: { xxx: 1 }, _success: false }, { fragmentConstants: { 1: 1 }, _success: true }, { fragmentConstants: { 2: 1 }, _success: false }, { fragmentConstants: { b: 1 }, _success: false }, // pipeline constant id is specified for b { fragmentConstants: { a: 1 }, _success: false }, // pipeline constant id is specified for a { fragmentConstants: { 1: 1, b: 1 }, _success: false }, // pipeline constant id is specified for b { fragmentConstants: { 数: 1 }, _success: true }, // test non-ASCII { fragmentConstants: { séquençage: 0 }, _success: false }, // test unicode is not normalized ] as { fragmentConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, fragmentConstants, _success } = t.params; const descriptor = t.getDescriptor({ fragmentShaderCode: ` override r: f32 = 0.0; override g: f32 = 0.0; override 数: f32 = 0.0; override sequencage: f32 = 0.0; @id(1) override b: f32 = 0.0; @id(1000) override a: f32 = 0.0; @fragment fn main() -> @location(0) vec4 { return vec4(r, g, b, a + 数 + sequencage); }`, fragmentConstants, }); vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor); }); g.test('uninitialized,vertex') .desc( ` Tests calling createRenderPipeline(Async) validation for uninitialized overridable constants in vertex state. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { vertexConstants: {}, _success: false }, { vertexConstants: { x: 1, y: 1 }, _success: false }, // z is missing { vertexConstants: { x: 1, z: 1 }, _success: true }, { vertexConstants: { x: 1, y: 1, z: 1, w: 1 }, _success: true }, ] as { vertexConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, vertexConstants, _success } = t.params; vtu.doCreateRenderPipelineTest(t, isAsync, _success, { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: ` override x: f32; override y: f32 = 0.0; override z: f32; override w: f32 = 1.0; @vertex fn main() -> @builtin(position) vec4 { return vec4(x, y, z, w); }`, }), entryPoint: 'main', constants: vertexConstants, }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [{ format: 'rgba8unorm' }], }, }); }); g.test('uninitialized,fragment') .desc( ` Tests calling createRenderPipeline(Async) validation for uninitialized overridable constants in fragment state. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { fragmentConstants: {}, _success: false }, { fragmentConstants: { r: 1, g: 1 }, _success: false }, // b is missing { fragmentConstants: { r: 1, b: 1 }, _success: true }, { fragmentConstants: { r: 1, g: 1, b: 1, a: 1 }, _success: true }, ] as { fragmentConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, fragmentConstants, _success } = t.params; const descriptor = t.getDescriptor({ fragmentShaderCode: ` override r: f32; override g: f32 = 0.0; override b: f32; override a: f32 = 0.0; @fragment fn main() -> @location(0) vec4 { return vec4(r, g, b, a); } `, fragmentConstants, }); vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor); }); g.test('value,type_error,vertex') .desc( ` Tests calling createRenderPipeline(Async) validation for invalid constant values like inf, NaN will results in TypeError. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { vertexConstants: { cf: 1 }, _success: true }, // control { vertexConstants: { cf: NaN }, _success: false }, { vertexConstants: { cf: Number.POSITIVE_INFINITY }, _success: false }, { vertexConstants: { cf: Number.NEGATIVE_INFINITY }, _success: false }, ] as { vertexConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, vertexConstants, _success } = t.params; vtu.doCreateRenderPipelineTest( t, isAsync, _success, { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: ` override cf: f32 = 0.0; @vertex fn main() -> @builtin(position) vec4 { _ = cf; return vec4(0.0, 0.0, 0.0, 1.0); }`, }), entryPoint: 'main', constants: vertexConstants, }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [{ format: 'rgba8unorm' }], }, }, 'TypeError' ); }); g.test('value,type_error,fragment') .desc( ` Tests calling createRenderPipeline(Async) validation for invalid constant values like inf, NaN will results in TypeError. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { fragmentConstants: { cf: 1 }, _success: true }, // control { fragmentConstants: { cf: NaN }, _success: false }, { fragmentConstants: { cf: Number.POSITIVE_INFINITY }, _success: false }, { fragmentConstants: { cf: Number.NEGATIVE_INFINITY }, _success: false }, ] as const) ) .fn(t => { const { isAsync, fragmentConstants, _success } = t.params; const descriptor = t.getDescriptor({ fragmentShaderCode: ` override cf: f32 = 0.0; @fragment fn main() -> @location(0) vec4 { _ = cf; return vec4(1.0, 1.0, 1.0, 1.0); } `, fragmentConstants, }); vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor, 'TypeError'); }); g.test('value,validation_error,vertex') .desc( ` Tests calling createRenderPipeline(Async) validation for unrepresentable constant values in vertex stage. TODO(#2060): test with last_f64_castable. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { vertexConstants: { cu: kValue.u32.min }, _success: true }, { vertexConstants: { cu: kValue.u32.min - 1 }, _success: false }, { vertexConstants: { cu: kValue.u32.max }, _success: true }, { vertexConstants: { cu: kValue.u32.max + 1 }, _success: false }, { vertexConstants: { ci: kValue.i32.negative.min }, _success: true }, { vertexConstants: { ci: kValue.i32.negative.min - 1 }, _success: false }, { vertexConstants: { ci: kValue.i32.positive.max }, _success: true }, { vertexConstants: { ci: kValue.i32.positive.max + 1 }, _success: false }, { vertexConstants: { cf: kValue.f32.negative.min }, _success: true }, { vertexConstants: { cf: kValue.f32.negative.first_non_castable_pipeline_override }, _success: false, }, { vertexConstants: { cf: kValue.f32.positive.max }, _success: true }, { vertexConstants: { cf: kValue.f32.positive.first_non_castable_pipeline_override }, _success: false, }, // Conversion to boolean can't fail { vertexConstants: { cb: Number.MAX_VALUE }, _success: true }, { vertexConstants: { cb: kValue.i32.negative.min - 1 }, _success: true }, ] as { vertexConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, vertexConstants, _success } = t.params; vtu.doCreateRenderPipelineTest(t, isAsync, _success, { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: ` override cb: bool = false; override cu: u32 = 0u; override ci: i32 = 0; override cf: f32 = 0.0; @vertex fn main() -> @builtin(position) vec4 { _ = cb; _ = cu; _ = ci; _ = cf; return vec4(0.0, 0.0, 0.0, 1.0); }`, }), entryPoint: 'main', constants: vertexConstants, }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [{ format: 'rgba8unorm' }], }, }); }); g.test('value,validation_error,fragment') .desc( ` Tests calling createRenderPipeline(Async) validation for unrepresentable constant values in fragment stage. TODO(#2060): test with last_f64_castable. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { fragmentConstants: { cu: kValue.u32.min }, _success: true }, { fragmentConstants: { cu: kValue.u32.min - 1 }, _success: false }, { fragmentConstants: { cu: kValue.u32.max }, _success: true }, { fragmentConstants: { cu: kValue.u32.max + 1 }, _success: false }, { fragmentConstants: { ci: kValue.i32.negative.min }, _success: true }, { fragmentConstants: { ci: kValue.i32.negative.min - 1 }, _success: false }, { fragmentConstants: { ci: kValue.i32.positive.max }, _success: true }, { fragmentConstants: { ci: kValue.i32.positive.max + 1 }, _success: false }, { fragmentConstants: { cf: kValue.f32.negative.min }, _success: true }, { fragmentConstants: { cf: kValue.f32.negative.first_non_castable_pipeline_override }, _success: false, }, { fragmentConstants: { cf: kValue.f32.positive.max }, _success: true }, { fragmentConstants: { cf: kValue.f32.positive.first_non_castable_pipeline_override }, _success: false, }, // Conversion to boolean can't fail { fragmentConstants: { cb: Number.MAX_VALUE }, _success: true }, { fragmentConstants: { cb: kValue.i32.negative.min - 1 }, _success: true }, ] as { fragmentConstants: Record; _success: boolean }[]) ) .fn(t => { const { isAsync, fragmentConstants, _success } = t.params; const descriptor = t.getDescriptor({ fragmentShaderCode: ` override cb: bool = false; override cu: u32 = 0u; override ci: i32 = 0; override cf: f32 = 0.0; @fragment fn main() -> @location(0) vec4 { _ = cb; _ = cu; _ = ci; _ = cf; return vec4(1.0, 1.0, 1.0, 1.0); } `, fragmentConstants, }); vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor); }); g.test('value,validation_error,f16,vertex') .desc( ` Tests calling createRenderPipeline(Async) validation for unrepresentable f16 constant values in vertex stage. TODO(#2060): Tighten the cases around the valid/invalid boundary once we have WGSL spec clarity on whether values like f16.positive.last_f64_castable would be valid. See issue. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { vertexConstants: { cf16: kValue.f16.negative.min }, _success: true }, { vertexConstants: { cf16: kValue.f16.negative.first_non_castable_pipeline_override }, _success: false, }, { vertexConstants: { cf16: kValue.f16.positive.max }, _success: true }, { vertexConstants: { cf16: kValue.f16.positive.first_non_castable_pipeline_override }, _success: false, }, { vertexConstants: { cf16: kValue.f32.negative.min }, _success: false }, { vertexConstants: { cf16: kValue.f32.positive.max }, _success: false }, { vertexConstants: { cf16: kValue.f32.negative.first_non_castable_pipeline_override }, _success: false, }, { vertexConstants: { cf16: kValue.f32.positive.first_non_castable_pipeline_override }, _success: false, }, ] as const) ) .fn(t => { t.skipIfDeviceDoesNotHaveFeature('shader-f16'); const { isAsync, vertexConstants, _success } = t.params; vtu.doCreateRenderPipelineTest(t, isAsync, _success, { layout: 'auto', vertex: { module: t.device.createShaderModule({ code: ` enable f16; override cf16: f16 = 0.0h; @vertex fn main() -> @builtin(position) vec4 { _ = cf16; return vec4(0.0, 0.0, 0.0, 1.0); }`, }), entryPoint: 'main', constants: vertexConstants, }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [{ format: 'rgba8unorm' }], }, }); }); g.test('value,validation_error,f16,fragment') .desc( ` Tests calling createRenderPipeline(Async) validation for unrepresentable f16 constant values in fragment stage. TODO(#2060): Tighten the cases around the valid/invalid boundary once we have WGSL spec clarity on whether values like f16.positive.last_f64_castable would be valid. See issue. ` ) .params(u => u // .combine('isAsync', [true, false]) .combineWithParams([ { fragmentConstants: { cf16: kValue.f16.negative.min }, _success: true }, { fragmentConstants: { cf16: kValue.f16.negative.first_non_castable_pipeline_override }, _success: false, }, { fragmentConstants: { cf16: kValue.f16.positive.max }, _success: true }, { fragmentConstants: { cf16: kValue.f16.positive.first_non_castable_pipeline_override }, _success: false, }, { fragmentConstants: { cf16: kValue.f32.negative.min }, _success: false }, { fragmentConstants: { cf16: kValue.f32.positive.max }, _success: false }, { fragmentConstants: { cf16: kValue.f32.negative.first_non_castable_pipeline_override }, _success: false, }, { fragmentConstants: { cf16: kValue.f32.positive.first_non_castable_pipeline_override }, _success: false, }, ] as const) ) .fn(t => { t.skipIfDeviceDoesNotHaveFeature('shader-f16'); const { isAsync, fragmentConstants, _success } = t.params; const descriptor = t.getDescriptor({ fragmentShaderCode: ` enable f16; override cf16: f16 = 0.0h; @fragment fn main() -> @location(0) vec4 { _ = cf16; return vec4(1.0, 1.0, 1.0, 1.0); } `, fragmentConstants, }); vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor); });