blob: b49277e34a02c884f65dcf89c9ab6c81163c8ca2 [file]
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);
});