| 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<string, GPUPipelineConstantValue>; _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<f32> { |
| return vec4<f32>(x, y, z, w + 数 + séquençage); |
| }`, |
| }), |
| entryPoint: 'main', |
| constants: vertexConstants, |
| }, |
| fragment: { |
| module: t.device.createShaderModule({ |
| code: `@fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| return vec4<f32>(x, y, z, w); |
| }`, |
| }), |
| entryPoint: 'main', |
| constants: vertexConstants, |
| }, |
| fragment: { |
| module: t.device.createShaderModule({ |
| code: `@fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| _ = cf; |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); |
| }`, |
| }), |
| entryPoint: 'main', |
| constants: vertexConstants, |
| }, |
| fragment: { |
| module: t.device.createShaderModule({ |
| code: `@fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<f32> { |
| _ = cf; |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| _ = cb; |
| _ = cu; |
| _ = ci; |
| _ = cf; |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); |
| }`, |
| }), |
| entryPoint: 'main', |
| constants: vertexConstants, |
| }, |
| fragment: { |
| module: t.device.createShaderModule({ |
| code: `@fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<string, GPUPipelineConstantValue>; _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<f32> { |
| _ = cb; |
| _ = cu; |
| _ = ci; |
| _ = cf; |
| return vec4<f32>(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<f32> { |
| _ = cf16; |
| return vec4<f32>(0.0, 0.0, 0.0, 1.0); |
| }`, |
| }), |
| entryPoint: 'main', |
| constants: vertexConstants, |
| }, |
| fragment: { |
| module: t.device.createShaderModule({ |
| code: `@fragment fn main() -> @location(0) vec4<f32> { |
| return vec4<f32>(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<f32> { |
| _ = cf16; |
| return vec4<f32>(1.0, 1.0, 1.0, 1.0); |
| } |
| `, |
| fragmentConstants, |
| }); |
| |
| vtu.doCreateRenderPipelineTest(t, isAsync, _success, descriptor); |
| }); |